Subversion Repositories pub

Compare Revisions

No changes between revisions

Ignore whitespace Rev 63 → Rev 64

/pluggablejs/branches/1.1.1build34/src/net/outlyer/plugins/PluginEnvironment.java
0,0 → 1,354
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.io.LineNumberReader;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
 
/**
* Provides the environment for {@link net.outlyer.plugins.Sandbox}es.
*/
public class PluginEnvironment {
static class UnsupportedAPIException extends PluginException {
public UnsupportedAPIException(int providedApi) {
super("Plugin has unsupported API ("+providedApi+")");
}
}
static class UnsupportedTypeException extends PluginException {
public UnsupportedTypeException(String pluginType) {
super("Plugin has unsupported type ("+pluginType+")");
}
}
private final Map<String, Object> exportedObjects;
private BasePluginObject pluginObject;
private StringBuilder boilerPlate;
private List<String> acceptedTypes = null;
private int maxApi=API.VERSION;
// Since these lines are evaluated they'll be executed twice
// so the lower the number the better to reduce unexpected side effects
private static int linesToCheckForSupport = 1;
 
{
exportedObjects = new HashMap();
pluginObject = null;
boilerPlate = new StringBuilder();
 
// plugin is guaranteed to be a SandboxAccessor, regardless of which
// exact object it ends up mapping to, see enableSandboxAccess()
boilerPlate.append("plugin.sandboxGetter = new java.util.concurrent.Callable(function() {")
.append(" return ")
.append(Internal.getRuntimeObjectName())
.append(".sandbox; });\n");
}
 
////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////
 
/**
* Creates a sandbox associated to the script found in uri
* @param uri Plugin uri
* @return Sandbox associated to uri
* @throws net.outlyer.plugins.PluginException
*/
public Sandbox createSandbox(final URI forUri) throws PluginException {
final ScriptEngine rhino = new ScriptEngineManager().getEngineByName("rhino");
PluginProperties pp;
if (null != forUri) {
pp = checkForSupport(forUri);
}
else {
pp = new PluginProperties();
pp.apiVersion = API.VERSION;
pp.name = "none";
pp.type = "";
}
 
if (null == pluginObject) {
pluginObject = new PluginObject();
}
 
try {
final PluginObject po = (PluginObject) pluginObject.clone();
 
final Bindings bindings = rhino.createBindings();
 
for (final String objectName : exportedObjects.keySet()) {
bindings.put(objectName, exportedObjects.get(objectName));
}
bindings.put("plugin", po);
//enableSandboxAccess("plugin", po);
 
return new SandboxImpl(this, forUri, bindings, pp, boilerPlate.toString());
}
catch (final CloneNotSupportedException e) {
assert false;
throw new IllegalStateException("Incorrect implementation");
}
}
 
/**
* Makes an object accessible from the plugin.
* Note the object will be shared amongst any sandboxes created after
* the exporting it.
* @param name Name with which to access the object
* @param object Object to export
* @throws IllegalArgumentException If name is <code>null</code>
* @throws IllegalArgumentException If name is "<code>plugin</code>" (use
* {@link #setPluginObject()} instead).
*/
public void exportObject(final String name, final Object object) {
if (null == name) {
throw new IllegalArgumentException("Can't export an object with" +
"a null name");
}
// Since the plugin object rules' are special (it will be cloned)
// being nice could (?) lead to confusion
if (name.equals("plugin")) {
throw new IllegalArgumentException("Can't export an object named " +
"plugin, use setPluginObject() instead");
}
exportedObjects.put(name, object);
if (object instanceof SandboxAccessor) {
enableSandboxAccess(name, (SandboxAccessor)object);
}
}
 
/**
* Makes a package's contents available in the global namespace.
* (<em>Imports</em> it into JavaScript).
* @param pkg Package to export
* @see #exportPackage(String)
*/
public void exportPackage(final Package pkg) {
if (null == pkg) {
throw new IllegalArgumentException("Can't export a null package");
}
 
boilerPlate.append("importPackage("+pkg.getName()+")");
}
 
/**
* Makes a package's contents available in the global namespace.
* Like {@see #exportPackage(Package)} but exporting by name.
* @param packageName
* @see #exportPackage(Package)
*/
public void exportPackage(final String packageName) {
if (null == packageName) {
throw new IllegalArgumentException("Can't export a null-named package");
}
final Package pkg = Package.getPackage(packageName);
if (null == pkg) {
throw new IllegalArgumentException("Failed to find package "+packageName);
}
exportPackage(pkg);
}
 
/**
* Defines the object to be exported as <code>plugin</code>.
* The <code>plugin</code> object is the only one exported by default.
* If none is set, a default one will be provided.
* <br />
* <b>Note</b> that, unlike for other objects, plugin objects aren't
* shared amongst sandboxes, a clone of the one provided will be used.
* @param pluginObject Object to export, set to null to use a default one.
*/
public void setPluginObject(BasePluginObject pObject) {
pluginObject = pObject;
}
 
/**
* Restricts the set of supported plugins to the ones with the given plugin
* type (by default no restriction is applied).
* @see PluginProperties#type
* @param pluginTypes Set of types to allow; use null to allow all types
*/
public void setAcceptedTypes(final String ... pluginTypes) {
if (null != pluginTypes) {
for (final String type : pluginTypes) {
if (null == type) {
throw new IllegalArgumentException("The set of plugin types" +
" must be either null, or a set of non-null values");
}
}
}
acceptedTypes = Arrays.asList(pluginTypes);
}
 
////////////////////////////////////////////////////////
// Non-public interface
////////////////////////////////////////////////////////
 
/**
* Makes a {@link SanboxAccessor}'s field <code>getSandbox</code>
* be evaluable as a <code>Callable&lt;Sandbox&gt;</code>, i.e.,
* from Java it will be possible to use:<br />
* <pre>
* Sandbox sb = ( (Callable&lt;Sandbox&gt;) object.getSandbox ).call()
* </pre>
* to retrieve the sandbox, while from JavaScript it will be enough
* with:
* <pre>
* var sb = object.getSandbox()
* </pre>
* @param name Exported object's name
* @param sa Exported object
*/
private void enableSandboxAccess(final String name, final SandboxAccessor sa) {
try {
Field f = sa.getClass().getField("sandboxGetter");
}
catch (final NoSuchFieldException e) {
throw new IllegalArgumentException("object " + name + " must comply " +
"with the contract of SandboxAccessor");
}
 
boilerPlate.append(name).append(".sandboxGetter=new java.util.concurrent.Callable(function() {")
.append(" return ")
.append(Internal.getRuntimeObjectName())
.append(".sandbox").append("; });\n");
}
PluginProperties checkForSupport(final URI uri) throws PluginException {
assert null != uri;
 
final PluginProperties pp = fetchPluginProperties(uri);
if (null == pp) {
throw new PluginExecutionException("Failed while inspecting plugin "+uri);
}
 
if (null != acceptedTypes) {
if (!acceptedTypes.contains(pp.type)) {
throw new UnsupportedTypeException(pp.type);
}
}
if (pp.apiVersion > maxApi) {
throw new UnsupportedAPIException(pp.apiVersion);
}
return pp;
}
 
// TODO: ? Possibly cache this, so that the first line is only eval'ed
// once more than the # of executions
static PluginProperties fetchPluginProperties(final URI uri) {
assert null != uri;
 
final ScriptEngine rhino = new ScriptEngineManager().getEngineByName("rhino");
 
try {
// Define a Plugin object with a field named type
rhino.eval("var plugin={ type: null, apiVersion: null };");
}
catch (ScriptException e) {
assert false;
throw new IllegalStateException("Unknown error encountered");
}
// First of all check the script for support
Object type = null, name = null;
Object apiVersion = null;
// Try to eval each line, since it executes in a sandbox this is
// *relatively* safe
String line;
try {
final LineNumberReader script = new LineNumberReader(new PluginReader(uri));
try {
while ((null == type || null == apiVersion || null == name) &&
(null != (line = script.readLine())) &&
script.getLineNumber() <= linesToCheckForSupport) {
try {
rhino.eval(line);
// Note that undefined doesn't convert to null!
if (null == type) {
rhino.eval("$_1_$ = (undefined===plugin.type)?null:plugin.type;");
type = rhino.get("$_1_$");
}
if (null == apiVersion) {
rhino.eval("$_1_$ = (undefined===plugin.apiVersion)?null:plugin.apiVersion;");
apiVersion = rhino.get("$_1_$");
}
if (null == name) {
rhino.eval("$_1_$ = (undefined===plugin.name)?null:plugin.name;");
name = rhino.get("$_1_$");
}
}
catch (final ScriptException e) {
// Exceptions are to be expected since none of the guaranteed
// plugin tools are provided in this context they'll try to
// access unexisting objects
}
}
}
finally {
script.close();
}
}
catch (final IOException e) {
// XXX: Evil silent failure
return null;
}
 
if (null == type) {
type = "";
}
if (null == apiVersion) {
apiVersion = 0;
}
// name can be null
final PluginProperties pp = new PluginProperties();
try {
pp.apiVersion = Double.valueOf(apiVersion.toString()).intValue();
pp.type = type.toString();
pp.name = (null != name) ? name.toString(): null;
}
catch (final NumberFormatException e) {
// XXX: Evil silent failure
return null;
}
return pp;
}
}
Property changes:
Added: svn:keywords
+Rev Id Date
\ No newline at end of property