/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.sql.script;
import com.foundationdb.ais.model.Parameter;
import com.foundationdb.qp.operator.QueryBindings;
import com.foundationdb.sql.server.ServerCallExplainer;
import com.foundationdb.sql.server.ServerJavaRoutine;
import com.foundationdb.sql.server.ServerJavaValues;
import com.foundationdb.sql.server.ServerQueryContext;
import com.foundationdb.sql.server.ServerRoutineInvocation;
import com.foundationdb.server.explain.Attributes;
import com.foundationdb.server.explain.CompoundExplainer;
import com.foundationdb.server.explain.ExplainContext;
import com.foundationdb.server.explain.Label;
import com.foundationdb.server.explain.PrimitiveExplainer;
import com.foundationdb.server.service.routines.ScriptEvaluator;
import com.foundationdb.server.service.routines.ScriptPool;
import javax.script.Bindings;
import java.sql.ResultSet;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.List;
import java.util.Map;
/** Implementation of the <code>SCRIPT_BINDINGS</code> calling convention.
* Inputs are passed as named (script engine scope) variables.
* Outputs can be received in the same way, or (since that is not
* possible in all languages) via the scripts return value, which can
* be a single value or a list or a dictionary.
*/
public class ScriptBindingsRoutine extends ServerJavaRoutine
{
private ScriptPool<ScriptEvaluator> pool;
private ScriptEvaluator evaluator;
private Bindings bindings;
private Object evalResult;
public ScriptBindingsRoutine(ServerQueryContext context,
QueryBindings queryBindings,
ServerRoutineInvocation invocation,
ScriptPool<ScriptEvaluator> pool) {
super(context, queryBindings, invocation);
this.pool = pool;
}
@Override
public void push() {
super.push();
evaluator = pool.get();
bindings = evaluator.getBindings();
}
@Override
public void setInParameter(Parameter parameter, ServerJavaValues values, int index) {
String var = parameter.getName();
if (var == null)
var = String.format("arg%d", index+1);
bindings.put(var, values.getObject(index));
}
@Override
public void invokeShielded() {
evalResult = evaluator.eval(bindings);
}
@Override
public Object getOutParameter(Parameter parameter, int index) {
if (parameter.getDirection() == Parameter.Direction.RETURN) {
return evalResult;
}
String var = parameter.getName();
if (var == null)
var = String.format("arg%d", index+1);
if (bindings.containsKey(var)) {
// Rhino Bindings exposes internal objects directly.
return getRhino17Interface().unwrap(bindings.get(var));
}
// Not bound, try to find in result.
if (parameter.getRoutine().isProcedure()) {
// Unless FUNCTION, can usurp return value.
if (evalResult instanceof Map) {
Map mresult = (Map)evalResult;
if (mresult.containsKey(var))
return mresult.get(var);
Integer jndex = getParameterArrayPosition(parameter);
if (mresult.containsKey(jndex))
return mresult.get(jndex);
}
else if (evalResult instanceof List) {
List lresult = (List)evalResult;
int jndex = getParameterArrayPosition(parameter);
if (jndex < lresult.size())
return lresult.get(jndex);
}
else {
for (Parameter otherParam : parameter.getRoutine().getParameters()) {
if (otherParam == parameter) continue;
if (otherParam.getDirection() != Parameter.Direction.IN)
return null; // Too many outputs.
}
return evalResult;
}
}
return null;
}
protected static int getParameterArrayPosition(Parameter parameter) {
int index = 0;
for (Parameter otherParam : parameter.getRoutine().getParameters()) {
if (otherParam == parameter) break;
if (otherParam.getDirection() != Parameter.Direction.IN)
index++;
}
return index;
}
@Override
public Queue<ResultSet> getDynamicResultSets() {
Queue<ResultSet> result = new ArrayDeque<>();
if (evalResult instanceof ResultSet) {
result.add((ResultSet)evalResult);
}
else if (evalResult instanceof List) {
for (Object obj : (List)evalResult) {
if (obj instanceof ResultSet) {
result.add((ResultSet)obj);
}
}
}
else if (evalResult instanceof Map) {
for (Object obj : ((Map)evalResult).values()) {
if (obj instanceof ResultSet) {
result.add((ResultSet)obj);
}
}
}
return result;
}
@Override
public void pop(boolean success) {
pool.put(evaluator, success);
evaluator = null;
super.pop(success);
}
/** In Rhino (1.7), internal Java object wrappers can leak out.
* TODO: Needed until completely migrated to Nashorn (Java 8).
*/
@SuppressWarnings("unchecked")
static class Rhino17Interface {
private final Class nativeJavaObject;
private final java.lang.reflect.Method unwrap;
public Rhino17Interface() {
Class clazz = null;
java.lang.reflect.Method meth = null;
try {
clazz = Class.forName("sun.org.mozilla.javascript.NativeJavaObject");
}
catch (Exception ex) {
}
if (clazz == null) {
try {
clazz = Class.forName("sun.org.mozilla.javascript.internal.NativeJavaObject");
}
catch (Exception ex) {
}
}
if (clazz != null) {
try {
meth = clazz.getMethod("unwrap");
}
catch (Exception ex) {
clazz = null;
}
}
this.nativeJavaObject = clazz;
this.unwrap = meth;
}
public Object unwrap(Object obj) {
try {
if ((nativeJavaObject != null) &&
nativeJavaObject.isInstance(obj)) {
obj = unwrap.invoke(obj);
}
}
catch (Exception ex) {
}
return obj;
}
}
private static Rhino17Interface rhino17Interface = null;
private static Rhino17Interface getRhino17Interface() {
if (rhino17Interface == null) {
synchronized (ScriptBindingsRoutine.class) {
if (rhino17Interface == null) {
rhino17Interface = new Rhino17Interface();
}
}
}
return rhino17Interface;
}
@Override
public CompoundExplainer getExplainer(ExplainContext context) {
Attributes atts = new Attributes();
ScriptEvaluator evaluator = pool.get();
atts.put(Label.PROCEDURE_IMPLEMENTATION,
PrimitiveExplainer.getInstance(evaluator.getEngineName()));
if (evaluator.isCompiled())
atts.put(Label.PROCEDURE_IMPLEMENTATION,
PrimitiveExplainer.getInstance("compiled"));
if (evaluator.isShared())
atts.put(Label.PROCEDURE_IMPLEMENTATION,
PrimitiveExplainer.getInstance("shared"));
pool.put(evaluator, true);
return new ServerCallExplainer(getInvocation(), atts, context);
}
}