Subversion Repositories pub

Compare Revisions

Ignore whitespace Rev 61 → Rev 62

/pluggablejs/trunk/src/net/outlyer/plugins/SandboxImpl.java
26,6 → 26,9
 
// $Id$
 
// Related reference:
// http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/
 
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
50,11 → 53,24
private final String boilerPlate;
private int execution = 0;
private final PluginProperties properties;
private transient ScriptEngine currentEngine = null;
private static final String END_KEY = "e";
private static final boolean debug;
private final PluginEnvironment pE;
private static final String END_KEY = "e";
private static class ExecResult {
final ScriptEngine rhino;
final Object evalResult;
ExecResult(final ScriptEngine rhino_, final Object evalResult_) {
rhino = rhino_;
evalResult = evalResult_;
}
}
 
static {
nextUniqueVarNum = (int) (1193 * (1+Math.random()));
debug = System.getProperty("debug", "0").equals("1");
}
{
62,16 → 78,19
hooks.put(END_KEY, new LinkedList<Runnable>());
}
protected SandboxImpl(final URI plugin, final Bindings bs, final PluginProperties pp) {
this(plugin, bs, pp, null);
protected SandboxImpl(final PluginEnvironment pe, final URI plugin,
final Bindings bs, final PluginProperties pp) {
this(pe, plugin, bs, pp, null);
}
protected SandboxImpl(final URI plugin, final Bindings bs,
protected SandboxImpl(final PluginEnvironment pe, final URI plugin,
final Bindings bs,
final PluginProperties pp, final String boilerPlate) {
bindings = bs;
pluginUri = plugin;
pluginUri = plugin; // if null => sandbox without file
this.boilerPlate = boilerPlate;
properties = pp;
pE = pe;
}
public void addEndHook(Runnable r) {
82,7 → 101,7
* Namespace for variables used by the enviroment
*/
private static String namespace() {
return "$net.outlyer.runtime";
return "$net.outlyer.runtime.internal";
}
 
/**
101,44 → 120,34
return new StringBuilder(namespace()).append(".")
.append(uniqueVarName()).toString();
}
 
/**
* Gets an engine.
* If the system property "debug" is set to "1" the returned engine
* will dump extra information to the console
* @return New engine
*/
private static ScriptEngine getEngine() {
return new ScriptEngineManager().getEngineByName("rhino");
final ScriptEngine se = new ScriptEngineManager().getEngineByName("rhino");
if (debug) {
return new EngineSink(se);
}
return se;
}
private ScriptEngine execute(final String prependText,
 
private ExecResult execute(final String prependText,
final String appendText,
final Map<String,Object> extraBindings)
throws PluginExecutionException {
final ScriptEngine rhino = getEngine();
currentEngine = rhino;
// First, export in the global namespace the reference to this
// sandbox. This variable isn't meant to be used directly, it's only
// set to refer to it from the boilerplate code (AFAIK there's no
// way to add a binding with a FQ name).
// TODO: Can a fully qualified name be bound directly?
final String globalSanboxVarName = uniqueVarName();
bindings.put(globalSanboxVarName, this);
if (null != extraBindings) {
for (final String key : extraBindings.keySet()) {
bindings.put(key, extraBindings.get(key));
}
bindings.putAll(extraBindings);
}
 
Object result = null;
// Will contain some biolerplate code used to provide access
// to the runtime wrapped-environment
final StringBuilder runtime = new StringBuilder();
// $net.outlyer.runtime.sandbox Contains a reference to this object
// Implementation note: PluginEnvironment.EXPORTED_SANDBOX_VARIABLE should have the same name
// TODO: ? $net.outlyer.runtime.pluginEnvironment
runtime.append("var $net = {\n")
.append(" outlyer : {\n")
.append(" runtime : {\n")
.append(" sandbox : ").append(globalSanboxVarName).append("\n")
.append(" }\n")
.append(" }\n")
.append("};\n");
 
try {
rhino.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
 
147,15 → 156,27
if (null != boilerPlate) {
rhino.eval(boilerPlate);
}
// Create the $net.outlyer.runtime pseudo-namespace...
rhino.eval(runtime.toString());
 
// $net.outlyer.runtime.sandbox Contains a reference to this object
// Implementation note: PluginEnvironment.EXPORTED_SANDBOX_VARIABLE
// should have the same name
final NamespaceContainer.$Net $net = new NamespaceContainer.$Net(pE, this);
assert this == $net.outlyer.runtime.sandbox;
rhino.put("$net", $net);
// ....internal can be used to store random internal data, it's
// a dynamic object so that fields can be created as needed
rhino.eval("$net.outlyer.runtime.internal={};");
 
if (null != prependText) {
rhino.eval(prependText);
}
rhino.eval(new PluginReader(pluginUri));
if (null != pluginUri) {
result = rhino.eval(new PluginReader(pluginUri));
}
else {
result = null;
}
if (null != appendText) {
rhino.eval(appendText);
172,24 → 193,42
catch (final ScriptException e) {
throw new PluginExecutionException("Script exception: " + e.getMessage(), e);
}
finally {
currentEngine = null;
}
return rhino;
return new ExecResult(rhino, result);
}
public void execute() throws PluginExecutionException {
execute(null, null, null);
}
ScriptEngine executeAndGetEngine() throws PluginExecutionException {
return execute(null, null, null).rhino;
}
 
public <T> T createDelayedImplementation(final Class<T> c,
final String objectName)
throws PluginExecutionException {
final String varImpl = uniqueVarName();
 
if (null == c) {
throw new IllegalArgumentException("Can't instantiate null");
}
if (!c.isInterface()) {
throw new IllegalArgumentException("Can only create delayed " +
"implementations for interfaces");
}
if (null == objectName) {
throw new IllegalArgumentException("Implementor's name can't be null");
}
final StringBuilder preCode = new StringBuilder();
 
if (debug) {
preCode.append("// Forward declaration for delayed implementation:\n");
}
preCode.append("var ").append(objectName).append(" = {\n");
for (final Method method : c.getMethods()) {
if (method.getDeclaringClass() != c) {
if (method.getDeclaringClass() == Object.class) { // Implement everything, including hierarchies
continue;
}
preCode.append(method.getName()).append(": null,\n");
196,104 → 235,104
}
preCode.append("};\n");
 
// Could be safely instantiated before actually being assigned IF
// plugins were required to define their methods with
// objectName.methodName = function() {} ...
// but would file with
// objectName = { methodName: function() {}, ... }
// Creating the instance as postCode does the job for both
final StringBuilder postCode = new StringBuilder();
postCode.append(varImpl).append(" = new ")
final ScriptEngine rhino = execute(preCode.toString(), null, null).rhino;
try {
final StringBuilder retriever = new StringBuilder();
retriever.append("new ")
.append(c.getCanonicalName()).append("(").append(objectName).append(");");
if (debug) {
retriever.append("// <== Actual delayed implementation creation");
}
 
final ScriptEngine rhino = execute(preCode.toString(), postCode.toString(), null);
// Could be safely instantiated before actually being assigned IF
// plugins were required to define their methods with
// objectName.methodName = function() {} ...
// but would file with
// objectName = { methodName: function() {}, ... }
// Creating the instance as postCode does the job for both
 
assert (c.isInstance(rhino.get(varImpl)));
return (T) rhino.get(varImpl);
try {
currentEngine = rhino;
// eval() returns the value of the last evaluated line/command
assert (c.isInstance(rhino.eval(retriever.toString())));
return (T) rhino.eval(retriever.toString());
}
finally {
currentEngine = null;
}
}
catch (final ScriptException e) {
throw new PluginExecutionException("Failed to create the delayed " +
"implementation instance: " + e.getMessage(), e);
}
}
 
public <T> T createDelayedImplementation(final Class<T> c)
/**
* {@inheritDoc}
*/
public <T> T createDelayedImplementation(final Class<T> c,
final Map<Method, String> map)
throws PluginExecutionException {
return createDelayedImplementation(c, false);
}
 
public <T> T createDelayedImplementation(final Class<T> c, boolean allowPartial)
throws PluginExecutionException {
return createDelayedImplementation(c, null, allowPartial);
}
 
public <T> T createDelayedImplementation(final Class<T> interfaceClass,
final T fallbackObject)
throws PluginExecutionException {
if (null == fallbackObject) {
throw new IllegalArgumentException("Can't use a null fallback object");
if (null == c) {
throw new IllegalArgumentException("Can't instantiate null");
}
return createDelayedImplementation(interfaceClass, fallbackObject, true);
}
if (!c.isInterface()) {
throw new IllegalArgumentException("Can only create delayed " +
"implementations for interfaces");
}
if (null == map || map.isEmpty()) {
throw new IllegalArgumentException("No mappings; creating a delayed " +
"implementation with no mappings " +
"is equivalent to creating" +
"the object from Java");
}
 
private <T> T createDelayedImplementation(final Class<T> c,
final T fallback,
boolean allowPartial)
throws PluginExecutionException {
final StringBuilder hack = new StringBuilder();
 
final StringBuffer preCode = new StringBuffer();
final String var = uniqueFQVarName();
// Apparently only global namespace objects can be retrieved with get()
// so to simplify retrieval a global variable is used
final String varImpl = uniqueVarName();
 
hack.append(var).append("={\n");
 
final StringBuilder script = new StringBuilder();
 
final String varFallback = uniqueVarName();
 
for (final Method m : c.getMethods()) {
if (m.getDeclaringClass() != c) {
continue;
}
 
final String implFuncName = c.getSimpleName()+"_"+m.getName();
if (allowPartial) {
// This makes undefined functions to be mapped to undefined
// variables (otherwise the interface instantation would fail)
// Apparently defining a var with the same name as a function
// produces no error, while checking for an undefined
// variable/method does fail.
// FIXME: Might be cleaner by handling "undefined"
script.append("var ").append(implFuncName).append(";\n");
if (null != fallback) {
script.append("if (!").append(implFuncName).append(")\n\t")
.append(implFuncName).append("=")
.append(varFallback).append(".").append(m.getName())
.append("\n");
}
// script.append("if (!").append(implFuncName).append(") ")
// .append(implFuncName).append("=null;\n");
}
hack.append("\t").append(m.getName()).append(" : ").append(implFuncName).append(",\n");
preCode.append(var).append(" = {\n");
for (final Method method : map.keySet()) {
preCode.append(method.getName()).append(": ")
.append(map.get(method))
.append("\n,\n");
}
hack.append("};\nvar ")
.append(varImpl).append("=new ")
.append(c.getCanonicalName()).append("(").append(var).append(");");
preCode.append("};\n");
script.append(hack.toString());
final ScriptEngine rhino = execute(preCode.toString(), null, null).rhino;
final HashMap<String, Object> bn = new HashMap();
bn.put(varFallback, fallback);
try {
final StringBuilder retriever = new StringBuilder();
retriever.append("new ")
.append(c.getCanonicalName()).append("(").append(var).append(");");
if (debug) {
retriever.append("// <== Actual delayed implementation creation");
}
 
if (null != fallback) {
script.append("\n// " + bn.get(varFallback).getClass());
// Could be safely instantiated before actually being assigned IF
// plugins were required to define their methods with
// objectName.methodName = function() {} ...
// but would file with
// objectName = { methodName: function() {}, ... }
// Creating the instance as postCode does the job for both
 
try {
currentEngine = rhino;
// eval() returns the value of the last evaluated line/command
assert (c.isInstance(rhino.eval(retriever.toString())));
return (T) rhino.eval(retriever.toString());
}
finally {
currentEngine = null;
}
}
//System.err.println(script.toString());
 
final ScriptEngine rhino = execute(null, script.toString(), bn);
assert (c.isInstance(rhino.get(varImpl)));
return (T) rhino.get(varImpl);
catch (final ScriptException e) {
throw new PluginExecutionException("Failed to create the delayed " +
"implementation instance: " + e.getMessage(), e);
}
}
 
public BasePluginObject getPluginObject() throws PluginExecutionException {
323,6 → 362,8
public URI loadedFrom() {
return pluginUri.normalize(); // FIXME: Return a copy
}
 
public ScriptEngine getCurrentEngine() {
return currentEngine;
}
}