0,0 → 1,328 |
package net.outlyer.plugins; |
|
/* |
* Copyright (c) 2008, Toni Corvera. All rights reserved. |
* |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions are met: |
* |
* * Redistributions of source code must retain the above copyright |
* notice, this list of conditions and the following disclaimer. |
* * Redistributions in binary form must reproduce the above copyright |
* notice, this list of conditions and the following disclaimer in the |
* documentation and/or other materials provided with the distribution. |
* |
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, |
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
*/ |
|
// $Id$ |
|
import java.io.IOException; |
import java.lang.reflect.Method; |
import java.net.URI; |
import java.util.Collection; |
import java.util.HashMap; |
import java.util.LinkedList; |
import java.util.Map; |
import javax.script.Bindings; |
import javax.script.ScriptContext; |
import javax.script.ScriptEngine; |
import javax.script.ScriptEngineManager; |
import javax.script.ScriptException; |
|
/** |
* Implementation of the Sandbox interface |
*/ |
class SandboxImpl implements Sandbox { |
private final Bindings bindings; |
private final URI pluginUri; |
private static int nextUniqueVarNum; |
private Map<String, Collection<Runnable>> hooks; |
private final String boilerPlate; |
private int execution = 0; |
private final PluginProperties properties; |
|
private static final String END_KEY = "e"; |
|
static { |
nextUniqueVarNum = (int) (1193 * (1+Math.random())); |
} |
|
{ |
hooks = new HashMap<String, Collection<Runnable>>(); |
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 URI plugin, final Bindings bs, |
final PluginProperties pp, final String boilerPlate) { |
bindings = bs; |
pluginUri = plugin; |
this.boilerPlate = boilerPlate; |
properties = pp; |
} |
|
public void addEndHook(Runnable r) { |
hooks.get(END_KEY).add(r); |
} |
|
/** |
* Namespace for variables used by the enviroment |
*/ |
private static String namespace() { |
return "$net.outlyer.runtime"; |
} |
|
/** |
* Creates a unique variable name. |
*/ |
private static String uniqueVarName() { |
return new StringBuilder("$_").append(nextUniqueVarNum+=1193).append("_$").toString(); |
} |
|
/** |
* Creates a unique variable name including namespace |
* @see #namespace |
* @see #uniqueVarName |
*/ |
private static String uniqueFQVarName() { |
return new StringBuilder(namespace()).append(".") |
.append(uniqueVarName()).toString(); |
} |
|
private static ScriptEngine getEngine() { |
return new ScriptEngineManager().getEngineByName("rhino"); |
} |
|
private ScriptEngine execute(final String prependText, |
final String appendText, |
final Map<String,Object> extraBindings) |
throws PluginExecutionException { |
final ScriptEngine rhino = getEngine(); |
|
// 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)); |
} |
} |
|
// 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); |
|
execution++; |
|
if (null != boilerPlate) { |
rhino.eval(boilerPlate); |
} |
|
// Create the $net.outlyer.runtime pseudo-namespace... |
rhino.eval(runtime.toString()); |
|
if (null != prependText) { |
rhino.eval(prependText); |
} |
|
rhino.eval(new PluginReader(pluginUri)); |
|
if (null != appendText) { |
rhino.eval(appendText); |
} |
|
// Execute any end hooks |
for (final Runnable hook : hooks.get(END_KEY)) { |
hook.run(); |
} |
} |
catch (final IOException e) { |
throw new PluginExecutionException("I/O Error: " + e.getMessage(), e); |
} |
catch (final ScriptException e) { |
throw new PluginExecutionException("Script exception: " + e.getMessage(), e); |
} |
|
return rhino; |
} |
|
public void execute() throws PluginExecutionException { |
execute(null, null, null); |
} |
|
public <T> T createDelayedImplementation(final Class<T> c, |
final String objectName) |
throws PluginExecutionException { |
final String varImpl = uniqueVarName(); |
|
final StringBuilder preCode = new StringBuilder(); |
|
preCode.append("var ").append(objectName).append(" = {\n"); |
for (final Method method : c.getMethods()) { |
if (method.getDeclaringClass() != c) { |
continue; |
} |
preCode.append(method.getName()).append(": null,\n"); |
} |
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 ") |
.append(c.getCanonicalName()).append("(").append(objectName).append(");"); |
|
final ScriptEngine rhino = execute(preCode.toString(), postCode.toString(), null); |
|
assert (c.isInstance(rhino.get(varImpl))); |
return (T) rhino.get(varImpl); |
} |
|
public <T> T createDelayedImplementation(final Class<T> c) |
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"); |
} |
return createDelayedImplementation(interfaceClass, fallbackObject, true); |
} |
|
private <T> T createDelayedImplementation(final Class<T> c, |
final T fallback, |
boolean allowPartial) |
throws PluginExecutionException { |
final StringBuilder hack = new StringBuilder(); |
|
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"); |
} |
|
hack.append("};\nvar ") |
.append(varImpl).append("=new ") |
.append(c.getCanonicalName()).append("(").append(var).append(");"); |
|
script.append(hack.toString()); |
|
final HashMap<String, Object> bn = new HashMap(); |
bn.put(varFallback, fallback); |
|
if (null != fallback) { |
script.append("\n// " + bn.get(varFallback).getClass()); |
} |
//System.err.println(script.toString()); |
|
final ScriptEngine rhino = execute(null, script.toString(), bn); |
assert (c.isInstance(rhino.get(varImpl))); |
return (T) rhino.get(varImpl); |
} |
|
public BasePluginObject getPluginObject() throws PluginExecutionException { |
execute(); |
assert bindings.get("plugin") instanceof BasePluginObject; |
return (BasePluginObject) bindings.get("plugin"); |
} |
|
public int getExecution() { |
return execution; |
} |
|
/** |
* {@inheritDoc} |
*/ |
public String getPluginName() { |
if (null == properties.name) { |
final String[] elements = loadedFrom().toString().split("/"); |
return elements[elements.length-1]; |
} |
return properties.name; |
} |
|
/** |
* {@inheritDoc} |
*/ |
public URI loadedFrom() { |
return pluginUri.normalize(); // FIXME: Return a copy |
} |
|
|
} |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |