helma/src/helma/scripting/rhino/RhinoEngine.java
hns bebce2ad3d Make HopObject implement the Rhino Wrapper interface.
Replace NativeJavaObject with Wrapper when unwrapping wrapped Java objects.
2003-08-07 11:26:44 +00:00

461 lines
14 KiB
Java

/*
* Helma License Notice
*
* The contents of this file are subject to the Helma License
* Version 2.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://adele.helma.org/download/helma/license.txt
*
* Copyright 1998-2003 Helma Software. All Rights Reserved.
*
* $RCSfile$
* $Author$
* $Revision$
* $Date$
*/
package helma.scripting.rhino;
import helma.doc.DocApplication;
import helma.extensions.ConfigurationException;
import helma.extensions.HelmaExtension;
import helma.framework.*;
import helma.framework.core.*;
import helma.main.Server;
import helma.objectmodel.*;
import helma.objectmodel.db.DbMapping;
import helma.objectmodel.db.Relation;
import helma.scripting.*;
import helma.scripting.rhino.debug.Tracer;
import helma.util.CacheMap;
import helma.util.Updatable;
import org.mozilla.javascript.*;
import java.io.*;
import java.util.*;
/**
* This is the implementation of ScriptingEnvironment for the Mozilla Rhino EcmaScript interpreter.
*/
public class RhinoEngine implements ScriptingEngine {
// map for Application to RhinoCore binding
static Map coreMap;
// the application we're running in
public Application app;
// The Rhino context
Context context;
// the global object
Scriptable global;
// the request evaluator instance owning this fesi evaluator
RequestEvaluator reval;
RhinoCore core;
// remember global variables from last invokation to be able to
// do lazy cleanup
Map lastGlobals = null;
// the global vars set by extensions
HashMap extensionGlobals;
// the thread currently running this engine
Thread thread;
// the introspector that provides documentation for this application
DocApplication doc = null;
/**
* Zero argument constructor.
*/
public RhinoEngine() {
}
/**
* Init the scripting engine with an application and a request evaluator
*/
public void init(Application app, RequestEvaluator reval) {
this.app = app;
this.reval = reval;
core = getRhinoCore(app);
context = Context.enter();
context.setCompileFunctionsWithDynamicScope(true);
try {
global = new GlobalObject(core, app); // context.newObject(core.global);
global.setPrototype(core.global);
global.setParentScope(null);
// context.putThreadLocal ("reval", reval);
// context.putThreadLocal ("engine", this);
extensionGlobals = new HashMap();
Vector extVec = Server.getServer().getExtensions();
for (int i = 0; i < extVec.size(); i++) {
HelmaExtension ext = (HelmaExtension) extVec.get(i);
try {
HashMap tmpGlobals = ext.initScripting(app, this);
if (tmpGlobals != null) {
extensionGlobals.putAll(tmpGlobals);
}
} catch (ConfigurationException e) {
app.logEvent("Couldn't initialize extension " + ext.getName() + ": " +
e.getMessage());
}
}
// context.removeThreadLocal ("reval");
// context.removeThreadLocal ("engine");
} catch (Exception e) {
System.err.println("Cannot initialize interpreter");
System.err.println("Error: " + e);
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} finally {
Context.exit ();
}
}
static synchronized RhinoCore getRhinoCore(Application app) {
RhinoCore core = null;
if (coreMap == null) {
coreMap = new WeakHashMap();
} else {
core = (RhinoCore) coreMap.get(app);
}
if (core == null) {
core = new RhinoCore(app);
coreMap.put(app, core);
}
return core;
}
/**
* This method is called before an execution context is entered to let the
* engine know it should update its prototype information.
*/
public void updatePrototypes() {
context = Context.enter();
context.setCompileFunctionsWithDynamicScope(true);
context.setWrapFactory(core.wrapper);
boolean trace = "true".equals(app.getProperty("rhino.trace"));
if (trace) {
context.setDebugger(new Tracer(getResponse()), null);
}
int optLevel = 0;
try {
optLevel = Integer.parseInt(app.getProperty("rhino.optlevel"));
} catch (Exception ignore) {
}
context.setOptimizationLevel(optLevel);
core.updatePrototypes();
context.putThreadLocal("reval", reval);
context.putThreadLocal("engine", this);
}
/**
* This method is called when an execution context for a request
* evaluation is entered. The globals parameter contains the global values
* to be applied during this execution context.
*/
public void enterContext(HashMap globals) throws ScriptingException {
// set the thread filed in the FESI evaluator
// evaluator.thread = Thread.currentThread ();
// set globals on the global object
// context = Context.enter (context);
globals.putAll(extensionGlobals);
thread = Thread.currentThread();
if ((globals != null) && (globals != lastGlobals)) {
// loop through global vars and set them
for (Iterator i = globals.keySet().iterator(); i.hasNext();) {
String k = (String) i.next();
Object v = globals.get(k);
Scriptable scriptable = null;
try {
// create a special wrapper for the path object.
// other objects are wrapped in the default way.
if (v instanceof RequestPath) {
scriptable = new PathWrapper((RequestPath) v, core);
scriptable.setPrototype(core.pathProto);
} else {
scriptable = Context.toObject(v, global);
}
global.put(k, global, scriptable);
} catch (Exception x) {
app.logEvent("Error setting global variable " + k + ": " + x);
}
}
}
// remember the globals set on this evaluator
lastGlobals = globals;
}
/**
* This method is called to let the scripting engine know that the current
* execution context has terminated.
*/
public void exitContext() {
context.removeThreadLocal("reval");
context.removeThreadLocal("engine");
Context.exit();
thread = null;
// loop through previous globals and unset them, if necessary.
if (lastGlobals != null) {
for (Iterator i=lastGlobals.keySet().iterator(); i.hasNext(); ) {
String g = (String) i.next ();
try {
global.delete (g);
} catch (Exception x) {
System.err.println ("Error resetting global property: "+g);
}
}
lastGlobals = null;
}
}
/**
* Invoke a function on some object, using the given arguments and global vars.
*/
public Object invoke(Object thisObject, String functionName, Object[] args,
boolean xmlrpc) throws ScriptingException {
Scriptable eso = null;
if (thisObject == null) {
eso = global;
} else {
eso = Context.toObject(thisObject, global);
}
try {
for (int i = 0; i < args.length; i++) {
// XML-RPC requires special argument conversion
if (xmlrpc) {
args[i] = core.processXmlRpcArgument (args[i]);
} else if (args[i] != null) {
args[i] = Context.toObject(args[i], global);
}
}
Object f = ScriptableObject.getProperty(eso, functionName.replace('.', '_'));
if ((f == ScriptableObject.NOT_FOUND) || !(f instanceof Function)) {
return null;
}
Object retval = ((Function) f).call(context, global, eso, args);
if (xmlrpc) {
return core.processXmlRpcResponse (retval);
} else if ((retval == null) || (retval == Undefined.instance)) {
return null;
} else if (retval instanceof Wrapper) {
return ((Wrapper) retval).unwrap();
} else {
return retval;
}
} catch (RedirectException redirect) {
throw redirect;
} catch (TimeoutException timeout) {
throw timeout;
} catch (ConcurrencyException concur) {
throw concur;
} catch (Exception x) {
// check if this is a redirect exception, which has been converted by fesi
// into an EcmaScript exception, which is why we can't explicitly catch it
if (reval.res.getRedirect() != null) {
throw new RedirectException(reval.res.getRedirect());
}
// do the same for not-modified responses
if (reval.res.getNotModified()) {
throw new RedirectException(null);
}
// has the request timed out? If so, throw TimeoutException
if (thread != Thread.currentThread())
throw new TimeoutException ();
// create and throw a ScriptingException with the right message
String msg;
if (x instanceof JavaScriptException) {
msg = ((JavaScriptException) x).getValue().toString();
} else {
msg = x.toString();
}
if (app.debug()) {
System.err.println("Error in Script: " + msg);
x.printStackTrace();
}
throw new ScriptingException(msg);
}
}
/**
* Let the evaluator know that the current evaluation has been
* aborted.
*/
public void abort() {
// current request has been aborted.
Thread t = thread;
if (t != null && t.isAlive()) {
t.interrupt();
try {
Thread.sleep(5000);
if (t.isAlive()) {
// thread is still running, gotta stop it.
t.stop();
}
} catch (InterruptedException i) {
}
}
}
/**
* Check if an object has a function property (public method if it
* is a java object) with that name.
*/
public boolean hasFunction(Object obj, String fname) {
// System.err.println ("HAS_FUNC: "+obj+"."+fname);
return core.hasFunction(app.getPrototypeName(obj), fname.replace('.', '_'));
}
/**
* Check if an object has a defined property (public field if it
* is a java object) with that name.
*/
public Object get(Object obj, String propname) {
// System.err.println ("GET: "+propname);
if ((obj == null) || (propname == null)) {
return null;
}
String prototypeName = app.getPrototypeName(obj);
if ("user".equalsIgnoreCase(prototypeName) &&
"password".equalsIgnoreCase(propname)) {
return "[macro access to password property not allowed]";
}
// if this is a HopObject, check if the property is defined
// in the type.properties db-mapping.
if (obj instanceof INode) {
DbMapping dbm = app.getDbMapping(prototypeName);
if (dbm != null) {
Relation rel = dbm.propertyToRelation(propname);
if ((rel == null) || !rel.isPrimitive()) {
return "[property \"" + propname + "\" is not defined for " +
prototypeName + "]";
}
}
}
Scriptable so = Context.toObject(obj, global);
try {
Object prop = so.get(propname, so);
if ((prop == null) || (prop == Undefined.instance)) {
return null;
} else if (prop instanceof Wrapper) {
return ((Wrapper) prop).unwrap();
} else {
return prop;
}
} catch (Exception esx) {
// System.err.println ("Error in getProperty: "+esx);
return null;
}
}
/**
* Get an introspector to this engine.
*/
public IPathElement getIntrospector() {
if (doc == null) {
doc = new DocApplication(app.getName(), app.getAppDir().toString());
doc.readApplication();
}
return doc;
}
/**
* Return the application we're running in
*/
public Application getApplication() {
return app;
}
/**
* Return the RequestEvaluator owning and driving this FESI evaluator.
*/
public RequestEvaluator getRequestEvaluator() {
return reval;
}
/**
* Return the Response object of the current evaluation context.
* Proxy method to RequestEvaluator.
*/
public ResponseTrans getResponse() {
return reval.res;
}
/**
* Return the Request object of the current evaluation context.
* Proxy method to RequestEvaluator.
*/
public RequestTrans getRequest() {
return reval.req;
}
/**
* Return the RhinoCore object for the application this engine belongs to.
*
* @return this engine's RhinoCore instance
*/
public RhinoCore getCore() {
return core;
}
/**
* Get a skin for the given prototype and skin name. This method considers the
* skinpath set in the current response object and does per-response skin
* caching.
*/
public Skin getSkin(String protoName, String skinName) {
SkinKey key = new SkinKey(protoName, skinName);
Skin skin = reval.res.getCachedSkin(key);
if (skin == null) {
// retrieve res.skinpath, an array of objects that tell us where to look for skins
// (strings for directory names and INodes for internal, db-stored skinsets)
Object[] skinpath = reval.res.getSkinpath();
RhinoCore.unwrapSkinpath(skinpath);
skin = app.getSkin(protoName, skinName, skinpath);
reval.res.cacheSkin(key, skin);
}
return skin;
}
}