Subversion Repositories pub

Compare Revisions

No changes between revisions

Ignore whitespace Rev 63 → Rev 64

/pluggablejs/branches/1.1.1build34/src/net/outlyer/plugins/SandboxImpl.java
0,0 → 1,361
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$
 
// Related reference:
// http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/
 
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 transient ScriptEngine currentEngine = null;
private static final String END_KEY = "e";
private static final boolean debug;
private final PluginEnvironment pE;
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");
}
{
hooks = new HashMap<String, Collection<Runnable>>();
hooks.put(END_KEY, new LinkedList<Runnable>());
}
protected SandboxImpl(final PluginEnvironment pe, final URI plugin,
final Bindings bs, final PluginProperties pp) {
this(pe, plugin, bs, pp, null);
}
protected SandboxImpl(final PluginEnvironment pe, final URI plugin,
final Bindings bs,
final PluginProperties pp, final String boilerPlate) {
bindings = bs;
pluginUri = plugin; // if null => sandbox without file
this.boilerPlate = boilerPlate;
properties = pp;
pE = pe;
}
public void addEndHook(Runnable r) {
hooks.get(END_KEY).add(r);
}
 
/**
* 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 Internal.getInternalObjectFieldName(uniqueVarName());
}
 
/**
* 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() {
final ScriptEngine se = new ScriptEngineManager().getEngineByName("rhino");
if (debug) {
return new EngineSink(se);
}
return se;
}
 
private ExecResult execute(final String prependText,
final String appendText,
final Map<String,Object> extraBindings)
throws PluginExecutionException {
final ScriptEngine rhino = getEngine();
currentEngine = rhino;
if (null != extraBindings) {
bindings.putAll(extraBindings);
}
 
Object result = null;
try {
rhino.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
 
execution++;
if (null != boilerPlate) {
rhino.eval(boilerPlate);
}
 
// $net.outlyer.runtime.sandbox Contains a reference to this object
// Implementation note: PluginEnvironment.EXPORTED_SANDBOX_VARIABLE
// should have the same name
final NamespaceContainer.$Net_Outlyer $nspc = new NamespaceContainer.$Net_Outlyer(pE, this);
assert this == $nspc.runtime.sandbox;
rhino.put(Internal.getReservedObjectName(), $nspc);
// ....internal can be used to store random internal data, it's
// a dynamic object so that fields can be created as needed
rhino.eval(Internal.getInternalObjectName()+Internal.getInternalInitialisation());
 
if (null != prependText) {
rhino.eval(prependText);
}
if (null != pluginUri) {
result = rhino.eval(new PluginReader(pluginUri));
}
else {
result = null;
}
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);
}
finally {
currentEngine = null;
}
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 {
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() == Object.class) { // Implement everything, including hierarchies
continue;
}
preCode.append(method.getName()).append(": null,\n");
}
preCode.append("};\n");
 
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");
}
 
// 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;
}
}
catch (final ScriptException e) {
throw new PluginExecutionException("Failed to create the delayed " +
"implementation instance: " + e.getMessage(), e);
}
}
 
/**
* {@inheritDoc}
*/
public <T> T createDelayedImplementation(final Class<T> c,
final Map<Method, String> map)
throws PluginExecutionException {
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 == map || map.isEmpty()) {
throw new IllegalArgumentException("No mappings; creating a delayed " +
"implementation with no mappings " +
"is equivalent to creating" +
"the object from Java");
}
 
final StringBuffer preCode = new StringBuffer();
final String var = uniqueFQVarName();
preCode.append(var).append(" = {\n");
for (final Method method : map.keySet()) {
preCode.append(method.getName()).append(": ")
.append(map.get(method))
.append("\n,\n");
}
preCode.append("};\n");
final ScriptEngine rhino = execute(preCode.toString(), null, null).rhino;
try {
final StringBuilder retriever = new StringBuilder();
retriever.append("new ")
.append(c.getCanonicalName()).append("(").append(var).append(");");
if (debug) {
retriever.append("// <== Actual delayed implementation creation");
}
 
// 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;
}
}
catch (final ScriptException e) {
throw new PluginExecutionException("Failed to create the delayed " +
"implementation instance: " + e.getMessage(), e);
}
}
 
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
}
 
public ScriptEngine getCurrentEngine() {
return currentEngine;
}
}
Property changes:
Added: svn:keywords
+Rev Id Date
\ No newline at end of property