/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tuscany.container.javascript.rhino;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
/**
* A RhinoScript represents a compiled JavaScript script
*/
public class RhinoScript {
protected String scriptName;
protected String script;
protected Scriptable scriptScope;
protected Map<String, Class> responseClasses;
protected ClassLoader classLoader;
/*
* Enable dynamic scopes so a script can be used concurrently with a global shared scope and individual execution scopes. See
* http://www.mozilla.org/rhino/scopes.html
*/
private static class MyFactory extends ContextFactory {
protected boolean hasFeature(Context cx, int featureIndex) {
if (featureIndex == Context.FEATURE_DYNAMIC_SCOPE) {
return true;
}
return super.hasFeature(cx, featureIndex);
}
}
static {
ContextFactory.initGlobal(new MyFactory());
}
/**
* Create a new RhinoScript.
*
* @param scriptName
* the name of the script. Can be anything, only used in messages to identify the script
* @param script
* the complete script
*/
public RhinoScript(String scriptName, String script) {
this(scriptName, script, (Map) null, null);
}
/**
* Create a new RhinoInvoker.
*
* @param scriptName
* the name of the script. Can be anything, only used in messages to identify the script
* @param script
* the complete script
* @param context
* name-value pairs that are added in to the scope where the script is compiled. May be null. The value objects are made available to
* the script by using a variable with the name.
* @param classLoader
* the ClassLoader Rhino should use to locate any user Java classes used in the script
*/
public RhinoScript(String scriptName, String script, Map context, ClassLoader classLoader) {
this.scriptName = scriptName;
this.script = script;
this.responseClasses = new HashMap<String, Class>();
this.classLoader = classLoader;
initScriptScope(scriptName, script, context, classLoader);
}
/**
* Create a new invokeable instance of the script
*
* @return a RhinoScriptInstance
*/
public RhinoScriptInstance createRhinoScriptInstance() {
return createRhinoScriptInstance(null);
}
/**
* Create a new invokeable instance of the script
*
* @param context
* objects to add to scope of the script instance
* @return a RhinoScriptInstance
*/
public RhinoScriptInstance createRhinoScriptInstance(Map<String, Object> context) {
Scriptable instanceScope = createInstanceScope(context);
RhinoScriptInstance rsi = new RhinoScriptInstance(scriptScope, instanceScope, context, responseClasses);
return rsi;
}
/**
* Initialize the Rhino Scope for this script instance
*/
public Scriptable createInstanceScope(Map<String, Object> context) {
Context cx = Context.enter();
try {
Scriptable instanceScope = cx.newObject(scriptScope);
instanceScope.setPrototype(scriptScope);
instanceScope.setParentScope(null);
addContexts(instanceScope, context);
return instanceScope;
} finally {
Context.exit();
}
}
/**
* Create a Rhino scope and compile the script into it
*/
public void initScriptScope(String fileName, String scriptCode, Map context, ClassLoader cl) {
Context cx = Context.enter();
try {
if (cl != null) {
// TODO: broken with the way the tuscany launcher now uses class loaders
// cx.setApplicationClassLoader(cl);
}
this.scriptScope = new ImporterTopLevel(cx, true);
Script compiledScript = cx.compileString(scriptCode, fileName, 1, null);
compiledScript.exec(cx, scriptScope);
addContexts(scriptScope, context);
} finally {
Context.exit();
}
}
/**
* Add the context to the scope. This will make the objects available to a script by using the name it was added with.
*/
protected void addContexts(Scriptable scope, Map contexts) {
if (contexts != null) {
for (Iterator i = contexts.keySet().iterator(); i.hasNext();) {
String name = (String) i.next();
Object value = contexts.get(name);
if (value != null) {
scope.put(name, scope, Context.toObject(value, scope));
}
}
}
}
public String getScript() {
return script;
}
public String getScriptName() {
return scriptName;
}
public Scriptable getScriptScope() {
return scriptScope;
}
public Map<String, Class> getResponseClasses() {
return responseClasses;
}
public ClassLoader getClassLoader() {
return classLoader;
}
/**
* Set the Java type of a response value. JavaScript is dynamically typed so Rhino cannot always work out what the intended Java type of a
* response should be, for example should the statement "return 42" be a Java int, or Integer or Double etc. When Rhino can't determine the type
* it will default to returning a String, using this method enables overriding the Rhino default to use a specific Java type.
*/
public void setResponseClass(String functionName, Class responseClasses) {
this.responseClasses.put(functionName, responseClasses);
}
public RhinoSCAConfig getSCAConfig() {
return new RhinoSCAConfig(getScriptScope());
}
public void setScript(String script) {
this.script = script;
}
}