461 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			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;
 | 
						|
    }
 | 
						|
 | 
						|
}
 |