merged in changes from "lazy_typing" branch, prototypes

are only initialized on demand.
This commit is contained in:
hns 2002-06-07 19:15:51 +00:00
parent f1a304f122
commit aa4c0389d4
10 changed files with 460 additions and 461 deletions

View file

@ -25,16 +25,13 @@ import java.rmi.server.*;
* requests from the Web server or XML-RPC port and dispatches them to * requests from the Web server or XML-RPC port and dispatches them to
* the evaluators. * the evaluators.
*/ */
public class Application extends UnicastRemoteObject implements IRemoteApp, IPathElement, IReplicatedApp, Runnable { public final class Application extends UnicastRemoteObject implements IRemoteApp, IPathElement, IReplicatedApp, Runnable {
private String name; private String name;
SystemProperties props, dbProps; SystemProperties props, dbProps;
File home, appDir, dbDir; File home, appDir, dbDir;
protected NodeManager nmgr; protected NodeManager nmgr;
// the class name of the scripting environment implementation
ScriptingEnvironment scriptingEngine;
// the root of the website, if a custom root object is defined. // the root of the website, if a custom root object is defined.
// otherwise this is managed by the NodeManager and not cached here. // otherwise this is managed by the NodeManager and not cached here.
Object rootObject = null; Object rootObject = null;
@ -111,9 +108,6 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
// Map of extensions allowed for public skins // Map of extensions allowed for public skins
Properties skinExtensions; Properties skinExtensions;
// a cache for parsed skin objects
public CacheMap skincache = new CacheMap (200, 0.80f);
// DocApplication used for introspection // DocApplication used for introspection
public DocApplication docApp; public DocApplication docApp;
@ -228,29 +222,29 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
* Get the application ready to run, initializing the evaluators and type manager. * Get the application ready to run, initializing the evaluators and type manager.
*/ */
public void init () throws DatabaseException, ScriptingException { public void init () throws DatabaseException, ScriptingException {
scriptingEngine = new helma.scripting.fesi.FesiScriptingEnvironment (); // scriptingEngine = new helma.scripting.fesi.FesiScriptingEnvironment ();
scriptingEngine.init (this, props); // scriptingEngine.init (this, props);
eval = new RequestEvaluator (this);
logEvent ("Starting evaluators for "+name);
int maxThreads = 12;
try {
maxThreads = Integer.parseInt (props.getProperty ("maxThreads"));
} catch (Exception ignore) {}
freeThreads = new Stack ();
allThreads = new Vector ();
allThreads.addElement (eval);
for (int i=0; i<maxThreads; i++) {
RequestEvaluator ev = new RequestEvaluator (this);
freeThreads.push (ev);
allThreads.addElement (ev);
}
activeRequests = new Hashtable ();
typemgr = new TypeManager (this); typemgr = new TypeManager (this);
typemgr.createPrototypes (); typemgr.createPrototypes ();
// logEvent ("Started type manager for "+name); // logEvent ("Started type manager for "+name);
// eval = new RequestEvaluator (this);
logEvent ("Starting evaluators for "+name);
freeThreads = new Stack ();
allThreads = new Vector ();
// allThreads.addElement (eval);
/* int maxThreads = 12;
try {
maxThreads = Integer.parseInt (props.getProperty ("maxThreads"));
} catch (Exception ignore) {}
for (int i=0; i<maxThreads; i++) {
RequestEvaluator ev = new RequestEvaluator (this);
freeThreads.push (ev);
allThreads.addElement (ev);
} */
activeRequests = new Hashtable ();
skinmgr = new SkinManager (this); skinmgr = new SkinManager (this);
rootMapping = getDbMapping ("root"); rootMapping = getDbMapping ("root");
@ -272,11 +266,10 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
if (xmlrpc != null) if (xmlrpc != null)
xmlrpc.addHandler (xmlrpcHandlerName, new XmlRpcInvoker (this)); xmlrpc.addHandler (xmlrpcHandlerName, new XmlRpcInvoker (this));
// typemgr.start ();
} }
/** /**
* Create request evaluators and start scheduler and cleanup thread * Create and start scheduler and cleanup thread
*/ */
public void start () { public void start () {
starttime = System.currentTimeMillis(); starttime = System.currentTimeMillis();
@ -320,6 +313,9 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
System.err.println ("Error shutting down embedded db: "+dbx); System.err.println ("Error shutting down embedded db: "+dbx);
} }
// null out type manager
typemgr = null;
// stop logs if they exist // stop logs if they exist
if (eventLog != null) { if (eventLog != null) {
eventLog.close (); eventLog.close ();
@ -339,6 +335,20 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
try { try {
return (RequestEvaluator) freeThreads.pop (); return (RequestEvaluator) freeThreads.pop ();
} catch (EmptyStackException nothreads) { } catch (EmptyStackException nothreads) {
synchronized (this) {
int maxThreads = 12;
try {
maxThreads = Integer.parseInt (props.getProperty ("maxThreads"));
} catch (Exception ignore) {
// property not set, use default value
}
if (allThreads.size() < maxThreads) {
logEvent ("Starting evaluator "+(allThreads.size()+1) +" for application "+name);
RequestEvaluator ev = new RequestEvaluator (this);
allThreads.addElement (ev);
return (ev);
}
}
throw new RuntimeException ("Maximum Thread count reached."); throw new RuntimeException ("Maximum Thread count reached.");
} }
} }
@ -347,8 +357,10 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
* Returns an evaluator back to the pool when the work is done. * Returns an evaluator back to the pool when the work is done.
*/ */
protected void releaseEvaluator (RequestEvaluator ev) { protected void releaseEvaluator (RequestEvaluator ev) {
if (ev != null) if (ev != null) {
freeThreads.push (ev); ev.recycle ();
freeThreads.push (ev);
}
} }
/** /**
@ -435,6 +447,9 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
// response needs to be closed/encoded before sending it back // response needs to be closed/encoded before sending it back
try { try {
res.close (charset); res.close (charset);
// reset data fields for garbage collection (may hold references to evaluator)
res.data = null;
req.data = null;
} catch (UnsupportedEncodingException uee) { } catch (UnsupportedEncodingException uee) {
logEvent ("Unsupported response encoding: "+uee.getMessage()); logEvent ("Unsupported response encoding: "+uee.getMessage());
} }
@ -442,7 +457,6 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
res.waitForClose (); res.waitForClose ();
} }
} }
return res; return res;
} }
@ -845,6 +859,10 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
return debug; return debug;
} }
/**
* Utiliti function invoker for the methods below. This *must* be called
* by an active RequestEvaluator thread.
*/
private Object invokeFunction (Object obj, String func, Object[] args) { private Object invokeFunction (Object obj, String func, Object[] args) {
Thread thread = Thread.currentThread (); Thread thread = Thread.currentThread ();
RequestEvaluator reval = null; RequestEvaluator reval = null;
@ -858,13 +876,13 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
return reval.invokeDirectFunction (obj, func, args); return reval.invokeDirectFunction (obj, func, args);
} catch (Exception x) { } catch (Exception x) {
if (debug) if (debug)
System.err.println ("ERROR invoking function "+func+": "+x); System.err.println ("Error in Application.invokeFunction ("+func+"): "+x);
} }
return null; return null;
} }
////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////
/// The following methods mimic the IPathElement interface. This allows as /// The following methods mimic the IPathElement interface. This allows us
/// to script any Java object: If the object implements IPathElement (as does /// to script any Java object: If the object implements IPathElement (as does
/// the Node class in Helma's internal objectmodel) then the corresponding /// the Node class in Helma's internal objectmodel) then the corresponding
/// method is called in the object itself. Otherwise, a corresponding script function /// method is called in the object itself. Otherwise, a corresponding script function
@ -999,14 +1017,6 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
} }
/**
* Get scripting environment for this application
*/
public ScriptingEnvironment getScriptingEnvironment () {
return scriptingEngine;
}
/** /**
* The run method performs periodic tasks like executing the scheduler method and * The run method performs periodic tasks like executing the scheduler method and
* kicking out expired user sessions. * kicking out expired user sessions.
@ -1014,12 +1024,19 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
public void run () { public void run () {
long cleanupSleep = 60000; // thread sleep interval (fixed) long cleanupSleep = 60000; // thread sleep interval (fixed)
long scheduleSleep = 60000; // interval for scheduler invocation long scheduleSleep = 60000; // interval for scheduler invocation
long lastScheduler = 0; long lastScheduler = 0; // run scheduler immediately
long lastCleanup = System.currentTimeMillis (); long lastCleanup = System.currentTimeMillis ();
// logEvent ("Starting scheduler for "+name); // logEvent ("Starting scheduler for "+name);
// as first thing, invoke function onStart in the root object // as first thing, invoke function onStart in the root object
eval = new RequestEvaluator (this);
allThreads.addElement (eval);
// read in standard prototypes to make first request go faster
typemgr.updatePrototype ("root");
typemgr.updatePrototype ("global");
try { try {
eval.invokeFunction ((INode) null, "onStart", new Object[0]); eval.invokeFunction ((INode) null, "onStart", new Object[0]);
} catch (Exception ignore) { } catch (Exception ignore) {
@ -1032,8 +1049,8 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
try { try {
sessionTimeout = Math.max (0, Integer.parseInt (props.getProperty ("sessionTimeout", "30"))); sessionTimeout = Math.max (0, Integer.parseInt (props.getProperty ("sessionTimeout", "30")));
} catch (Exception ignore) { } catch (Exception ignore) {
System.out.println(ignore.toString()); System.out.println(ignore.toString());
} }
long now = System.currentTimeMillis (); long now = System.currentTimeMillis ();
@ -1044,9 +1061,12 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
for (Enumeration e = cloned.elements (); e.hasMoreElements (); ) { for (Enumeration e = cloned.elements (); e.hasMoreElements (); ) {
Session session = (Session) e.nextElement (); Session session = (Session) e.nextElement ();
if (now - session.lastTouched () > sessionTimeout * 60000) { if (now - session.lastTouched () > sessionTimeout * 60000) {
// if (session.uid != null) { INode usernode = session.getUserNode ();
// FIXME onlogout()! try {eval.invokeFunction (u, "onLogout", new Object[0]);} catch (Exception ignore) {} if (usernode != null) {
// } try {
eval.invokeFunction (usernode, "onLogout", new Object[0]);
} catch (Exception ignore) {}
}
destroySession(session); destroySession(session);
} }
} }
@ -1080,10 +1100,7 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
worker = null; worker = null;
break; break;
} }
} }
logEvent ("Scheduler for "+name+" exiting"); logEvent ("Scheduler for "+name+" exiting");
} }

View file

@ -13,22 +13,22 @@ import helma.util.Updatable;
/** /**
* The Prototype class represents JavaScript prototypes defined in HOP * The Prototype class represents Script prototypes/type defined in a Helma
* applications. This manages a prototypes templates, functions and actions * application. This class manages a prototypes templates, functions and actions
* as well as optional information about the mapping of this type to a * as well as optional information about the mapping of this type to a
* relational database table. * relational database table.
*/ */
public class Prototype { public final class Prototype {
String id; String id;
String name; String name;
Application app; Application app;
public HashMap templates, functions, actions, skins, updatables; public HashMap templates, functions, actions, skins, updatables;
long lastUpdate; long lastUpdate, lastCheck;
Prototype parent; private Prototype parent;
// Tells us whether this prototype is used to script a generic Java object, // Tells us whether this prototype is used to script a generic Java object,
// as opposed to a Helma objectmodel node object. // as opposed to a Helma objectmodel node object.
@ -39,7 +39,7 @@ public class Prototype {
this.app = app; this.app = app;
this.name = name; this.name = name;
isJavaPrototype = app.isJavaPrototype (name); isJavaPrototype = app.isJavaPrototype (name);
lastUpdate = 0; // System.currentTimeMillis (); lastUpdate = lastCheck = 0;
} }
/** /**
@ -57,29 +57,7 @@ public class Prototype {
// this is not allowed for the hopobject and global prototypes // this is not allowed for the hopobject and global prototypes
if ("hopobject".equalsIgnoreCase (name) || "global".equalsIgnoreCase (name)) if ("hopobject".equalsIgnoreCase (name) || "global".equalsIgnoreCase (name))
return; return;
Prototype old = this.parent;
this.parent = parent; this.parent = parent;
// if parent has changed, update ES-prototypes in request evaluators
if (parent != old) {
/* Iterator evals = app.typemgr.getRegisteredRequestEvaluators ();
while (evals.hasNext ()) {
try {
RequestEvaluator reval = (RequestEvaluator) evals.next ();
ObjectPrototype op = reval.getPrototype (getName());
// use hopobject (node) as prototype even if prototype is null -
// this is the case if no hopobject directory exists
ObjectPrototype opp = parent == null ?
reval.esNodePrototype : reval.getPrototype (parent.getName ());
// don't think this is possible, but check anyway
if (opp == null)
opp = reval.esNodePrototype;
op.setPrototype (opp);
} catch (Exception ignore) {
}
} */
}
} }
public Prototype getParentPrototype () { public Prototype getParentPrototype () {
@ -125,10 +103,23 @@ public class Prototype {
} }
} }
return upd; return upd;
} }
public long getLastUpdate () {
return lastUpdate;
}
public void markUpdated () {
lastUpdate = System.currentTimeMillis ();
}
public long getLastCheck () {
return lastCheck;
}
public void markChecked () {
lastCheck = System.currentTimeMillis ();
}
public String toString () { public String toString () {
return "[Prototype "+ app.getName()+"/"+name+"]"; return "[Prototype "+ app.getName()+"/"+name+"]";

View file

@ -18,11 +18,12 @@ import java.util.*;
* is killed and an error message is returned. * is killed and an error message is returned.
*/ */
public class RequestEvaluator implements Runnable { public final class RequestEvaluator implements Runnable {
public Application app; public final Application app;
protected boolean initialized;
protected final ScriptingEngine scriptingEngine;
public RequestTrans req; public RequestTrans req;
public ResponseTrans res; public ResponseTrans res;
@ -44,7 +45,7 @@ public class RequestEvaluator implements Runnable {
// the object path of the request we're evaluating // the object path of the request we're evaluating
List requestPath; List requestPath;
// the result of the // the result of the operation
Object result; Object result;
// the exception thrown by the evaluator, if any. // the exception thrown by the evaluator, if any.
@ -63,7 +64,7 @@ public class RequestEvaluator implements Runnable {
*/ */
public RequestEvaluator (Application app) { public RequestEvaluator (Application app) {
this.app = app; this.app = app;
initialized = false; scriptingEngine = helma.scripting.fesi.FesiEngineFactory.getEngine (app, this);
} }
@ -79,9 +80,6 @@ public class RequestEvaluator implements Runnable {
// long startCheck = System.currentTimeMillis (); // long startCheck = System.currentTimeMillis ();
app.typemgr.checkPrototypes (); app.typemgr.checkPrototypes ();
// evaluators are only initialized as needed, so we need to check that here
// if (!initialized)
// app.typemgr.initRequestEvaluator (this);
// System.err.println ("Type check overhead: "+(System.currentTimeMillis ()-startCheck)+" millis"); // System.err.println ("Type check overhead: "+(System.currentTimeMillis ()-startCheck)+" millis");
// object refs to ressolve request path // object refs to ressolve request path
@ -225,13 +223,16 @@ public class RequestEvaluator implements Runnable {
try { try {
localrtx.timer.beginEvent (txname+" execute"); localrtx.timer.beginEvent (txname+" execute");
// enter execution context
scriptingEngine.enterContext (globals);
// set the req.action property, cutting off the _action suffix // set the req.action property, cutting off the _action suffix
req.action = action.substring (0, action.length()-7); req.action = action.substring (0, action.length()-7);
// try calling onRequest() function on object before // try calling onRequest() function on object before
// calling the actual action // calling the actual action
try { try {
app.scriptingEngine.invoke (currentElement, "onRequest", new Object[0], globals, this); scriptingEngine.invoke (currentElement, "onRequest", new Object[0]);
} catch (RedirectException redir) { } catch (RedirectException redir) {
throw redir; throw redir;
} catch (Exception ignore) { } catch (Exception ignore) {
@ -239,7 +240,7 @@ public class RequestEvaluator implements Runnable {
} }
// do the actual action invocation // do the actual action invocation
app.scriptingEngine.invoke (currentElement, action, new Object[0], globals, this); scriptingEngine.invoke (currentElement, action, new Object[0]);
localrtx.timer.endEvent (txname+" execute"); localrtx.timer.endEvent (txname+" execute");
} catch (RedirectException redirect) { } catch (RedirectException redirect) {
@ -324,6 +325,8 @@ public class RequestEvaluator implements Runnable {
globals.put ("res", res); globals.put ("res", res);
globals.put ("app", app); globals.put ("app", app);
scriptingEngine.enterContext (globals);
currentElement = root; currentElement = root;
if (method.indexOf (".") > -1) { if (method.indexOf (".") > -1) {
@ -343,7 +346,7 @@ public class RequestEvaluator implements Runnable {
String proto = app.getPrototypeName (currentElement); String proto = app.getPrototypeName (currentElement);
app.checkXmlRpcAccess (proto, method); app.checkXmlRpcAccess (proto, method);
result = app.scriptingEngine.invoke (currentElement, method, args, globals, this); result = scriptingEngine.invoke (currentElement, method, args);
commitTransaction (); commitTransaction ();
} catch (Exception wrong) { } catch (Exception wrong) {
@ -366,14 +369,12 @@ public class RequestEvaluator implements Runnable {
// avoid going into transaction if called function doesn't exist // avoid going into transaction if called function doesn't exist
boolean functionexists = true; boolean functionexists = true;
if (thisObject == null) try { functionexists = scriptingEngine.hasFunction (thisObject, method);
functionexists = app.scriptingEngine.hasFunction (null, method, this);
} catch (ScriptingException ignore) {}
if (!functionexists) if (!functionexists) {
// global function doesn't exist, nothing to do here. // function doesn't exist, nothing to do here.
reqtype = NONE; reqtype = NONE;
else try { } else try {
localrtx.begin (funcdesc); localrtx.begin (funcdesc);
root = app.getDataRoot (); root = app.getDataRoot ();
@ -383,7 +384,9 @@ public class RequestEvaluator implements Runnable {
globals.put ("res", res); globals.put ("res", res);
globals.put ("app", app); globals.put ("app", app);
app.scriptingEngine.invoke (thisObject, method, args, globals, this); scriptingEngine.enterContext (globals);
result = scriptingEngine.invoke (thisObject, method, args);
commitTransaction (); commitTransaction ();
} catch (Exception wrong) { } catch (Exception wrong) {
@ -403,6 +406,9 @@ public class RequestEvaluator implements Runnable {
} }
// exit execution context
scriptingEngine.exitContext ();
// make sure there is only one thread running per instance of this class // make sure there is only one thread running per instance of this class
// if localrtx != rtx, the current thread has been aborted and there's no need to notify // if localrtx != rtx, the current thread has been aborted and there's no need to notify
if (localrtx != rtx) { if (localrtx != rtx) {
@ -454,8 +460,13 @@ public class RequestEvaluator implements Runnable {
// wait for request, max 10 min // wait for request, max 10 min
wait (1000*60*10); wait (1000*60*10);
// if no request arrived, release ressources and thread // if no request arrived, release ressources and thread
if (reqtype == NONE && rtx == localrtx) if (reqtype == NONE && rtx == localrtx) {
// comment this in to release not just the thread, but also the scripting engine.
// currently we don't do this because of the risk of memory leaks (objects from
// framework referencing into the scripting engine)
// scriptingEngine = null;
rtx = null; rtx = null;
}
} catch (InterruptedException ir) {} } catch (InterruptedException ir) {}
} }
@ -469,13 +480,12 @@ public class RequestEvaluator implements Runnable {
checkThread (); checkThread ();
wait (app.requestTimeout); wait (app.requestTimeout);
if (reqtype != NONE) { if (reqtype != NONE) {
app.logEvent ("Stopping Thread for Request "+app.getName()+"/"+req.path); app.logEvent ("Stopping Thread for Request "+app.getName()+"/"+req.path);
stopThread (); stopThread ();
res.reset (); res.reset ();
res.write ("<b>Error in application '"+app.getName()+"':</b> <br><br><pre>Request timed out.</pre>"); res.write ("<b>Error in application '"+app.getName()+"':</b> <br><br><pre>Request timed out.</pre>");
} }
return res; return res;
} }
@ -505,17 +515,19 @@ public class RequestEvaluator implements Runnable {
checkThread (); checkThread ();
wait (app.requestTimeout); wait (app.requestTimeout);
if (reqtype != NONE) { if (reqtype != NONE) {
stopThread (); stopThread ();
} }
// reset res for garbage collection (res.data may hold reference to evaluator)
res = null;
if (exception != null) if (exception != null)
throw (exception); throw (exception);
return result; return result;
} }
protected Object invokeDirectFunction (Object obj, String functionName, Object[] args) throws Exception { protected Object invokeDirectFunction (Object obj, String functionName, Object[] args) throws Exception {
return app.scriptingEngine.invoke (obj, functionName, args, null, this); return scriptingEngine.invoke (obj, functionName, args);
} }
public synchronized Object invokeFunction (Object object, String functionName, Object[] args) public synchronized Object invokeFunction (Object object, String functionName, Object[] args)
@ -532,10 +544,12 @@ public class RequestEvaluator implements Runnable {
checkThread (); checkThread ();
wait (60000l*15); // give internal call more time (15 minutes) to complete wait (60000l*15); // give internal call more time (15 minutes) to complete
if (reqtype != NONE) { if (reqtype != NONE) {
stopThread (); stopThread ();
} }
// reset res for garbage collection (res.data may hold reference to evaluator)
res = null;
if (exception != null) if (exception != null)
throw (exception); throw (exception);
return result; return result;
@ -555,10 +569,12 @@ public class RequestEvaluator implements Runnable {
checkThread (); checkThread ();
wait (app.requestTimeout); wait (app.requestTimeout);
if (reqtype != NONE) { if (reqtype != NONE) {
stopThread (); stopThread ();
} }
// reset res for garbage collection (res.data may hold reference to evaluator)
res = null;
if (exception != null) if (exception != null)
throw (exception); throw (exception);
return result; return result;
@ -603,7 +619,18 @@ public class RequestEvaluator implements Runnable {
} }
} }
/**
* Null out some fields, mostly for the sake of garbage collection.
*/
public void recycle () {
res = null;
req = null;
session = null;
args = null;
requestPath = null;
result = null;
exception = null;
}
/** /**
* Check if an action with a given name is defined for a scripted object. If it is, * Check if an action with a given name is defined for a scripted object. If it is,
@ -613,12 +640,8 @@ public class RequestEvaluator implements Runnable {
if (obj == null) if (obj == null)
return null; return null;
String act = action == null ? "main_action" : action+"_action"; String act = action == null ? "main_action" : action+"_action";
try { if (scriptingEngine.hasFunction (obj, act))
if (app.scriptingEngine.hasFunction (obj, act, this)) return act;
return act;
} catch (ScriptingException x) {
return null;
}
return null; return null;
} }

View file

@ -18,19 +18,24 @@ import java.util.*;
* from the RequestEvaluator object to resolve Macro handlers by type name. * from the RequestEvaluator object to resolve Macro handlers by type name.
*/ */
public class Skin { public final class Skin {
private Macro[] parts;
private Application app;
private char[] source;
private int sourceLength;
private HashSet sandbox;
Macro[] parts;
Application app;
char[] source;
int sourceLength;
HashSet sandbox;
/** /**
* Create a skin without any restrictions on which macros are allowed to be called from it * Create a skin without any restrictions on which macros are allowed to be called from it
*/ */
public Skin (String content, Application app) { public Skin (String content, Application app) {
this (content, app, null); this.app = app;
sandbox = null;
source = content.toCharArray ();
sourceLength = source.length;
parse ();
} }
/** /**
@ -50,8 +55,8 @@ public class Skin {
public Skin (char[] content, int length, Application app) { public Skin (char[] content, int length, Application app) {
this.app = app; this.app = app;
this.sandbox = null; this.sandbox = null;
this.source = content; source = content;
this.sourceLength = length; sourceLength = length;
parse (); parse ();
} }
@ -315,12 +320,12 @@ public class Skin {
Object v = null; Object v = null;
// remember length of response buffer before calling macro // remember length of response buffer before calling macro
int oldLength = reval.res.getBufferLength (); int oldLength = reval.res.getBufferLength ();
if (app.scriptingEngine.hasFunction (handlerObject, name+"_macro", reval)) { if (reval.scriptingEngine.hasFunction (handlerObject, name+"_macro")) {
// System.err.println ("Getting macro from function"); // System.err.println ("Getting macro from function");
v = app.scriptingEngine.invoke (handlerObject, name+"_macro", arguments, null, reval); v = reval.scriptingEngine.invoke (handlerObject, name+"_macro", arguments);
} else { } else {
// System.err.println ("Getting macro from property"); // System.err.println ("Getting macro from property");
v = app.scriptingEngine.get (handlerObject, name, reval); v = reval.scriptingEngine.get (handlerObject, name);
} }
// check if macro wrote out to response buffer // check if macro wrote out to response buffer
int newLength = reval.res.getBufferLength (); int newLength = reval.res.getBufferLength ();

View file

@ -16,7 +16,7 @@ import java.io.*;
* applications and updates the evaluators if anything has changed. * applications and updates the evaluators if anything has changed.
*/ */
public class TypeManager { public final class TypeManager {
Application app; Application app;
File appDir; File appDir;
@ -26,36 +26,23 @@ public class TypeManager {
long idleSeconds = 120; // if idle for longer than 5 minutes, slow down long idleSeconds = 120; // if idle for longer than 5 minutes, slow down
boolean rewire; boolean rewire;
// this contains only those evaluatores which have already been initialized static String[] standardTypes = {"user", "global", "root", "hopobject"};
// and thus need to get updates
List registeredEvaluators;
static HashSet standardTypes;
static {
standardTypes = new HashSet ();
standardTypes.add ("user");
standardTypes.add ("global");
standardTypes.add ("root");
standardTypes.add ("hopobject");
}
public TypeManager (Application app) { public TypeManager (Application app) {
this.app = app; this.app = app;
appDir = app.appDir; appDir = app.appDir;
// make sure the directories for the standard prototypes exist, and lament otherwise // make sure the directories for the standard prototypes exist, and lament otherwise
if (appDir.list().length == 0) { if (appDir.list().length == 0) {
for (Iterator it=standardTypes.iterator (); it.hasNext (); ) { for (int i=0; i<standardTypes.length; i++) {
File f = new File (appDir, (String) it.next ()); File f = new File (appDir, standardTypes[i]);
if (!f.exists() && !f.mkdir ()) if (!f.exists() && !f.mkdir ())
app.logEvent ("Warning: directory "+f.getAbsolutePath ()+" could not be created."); app.logEvent ("Warning: directory "+f.getAbsolutePath ()+" could not be created.");
else if (!f.isDirectory ()) else if (!f.isDirectory ())
app.logEvent ("Warning: "+f.getAbsolutePath ()+" is not a directory."); app.logEvent ("Warning: "+f.getAbsolutePath ()+" is not a directory.");
} }
} }
prototypes = new HashMap (); prototypes = new HashMap ();
zipfiles = new HashMap (); zipfiles = new HashMap ();
registeredEvaluators = Collections.synchronizedList (new ArrayList ());
} }
@ -64,7 +51,17 @@ public class TypeManager {
* compile or evaluate any scripts. * compile or evaluate any scripts.
*/ */
public void createPrototypes () { public void createPrototypes () {
check (false); checkFiles ();
// check if standard prototypes have been created
// if not, create them.
for (int i=0; i<standardTypes.length; i++) {
String pname = standardTypes[i];
if (prototypes.get (pname) == null) {
Prototype proto = new Prototype (pname, app);
registerPrototype (pname, new File (appDir, pname), proto);
prototypes.put (pname, proto);
}
}
} }
@ -73,10 +70,10 @@ public class TypeManager {
* If so, update prototypes and scripts. * If so, update prototypes and scripts.
*/ */
public synchronized void checkPrototypes () { public synchronized void checkPrototypes () {
if (System.currentTimeMillis () - lastCheck < 500l) if (System.currentTimeMillis () - lastCheck < 1000l)
return; return;
try { try {
check (true); checkFiles ();
} catch (Exception ignore) {} } catch (Exception ignore) {}
lastCheck = System.currentTimeMillis (); lastCheck = System.currentTimeMillis ();
} }
@ -84,7 +81,7 @@ public class TypeManager {
/** /**
* Run through application's prototype directories and check if anything has been updated. * Run through application's prototype directories and check if anything has been updated.
*/ */
public void check (boolean update) { public void checkFiles () {
// long now = System.currentTimeMillis (); // long now = System.currentTimeMillis ();
// System.out.print ("checking "+Thread.currentThread ()); // System.out.print ("checking "+Thread.currentThread ());
File[] list = appDir.listFiles (); File[] list = appDir.listFiles ();
@ -96,12 +93,12 @@ public class TypeManager {
if (proto != null) { if (proto != null) {
// check if existing prototype needs update // check if existing prototype needs update
// app.logEvent (protoDir.lastModified ()); // app.logEvent (protoDir.lastModified ());
updatePrototype (filename, list[i], proto); // updatePrototype (filename, list[i], proto);
} else if (list[i].isDirectory () && isValidTypeName (filename)) { } else if (list[i].isDirectory () && isValidTypeName (filename)) {
// leave out ".." and other directories that contain "." // leave out ".." and other directories that contain "."
// create new prototype // create new prototype
proto = new Prototype (filename, app); proto = new Prototype (filename, app);
registerPrototype (filename, list[i], proto, update); registerPrototype (filename, list[i], proto);
prototypes.put (filename, proto); prototypes.put (filename, proto);
// give logger thread a chance to tell what's going on // give logger thread a chance to tell what's going on
// Thread.yield(); // Thread.yield();
@ -122,19 +119,6 @@ public class TypeManager {
} }
} }
// check if standard prototypes have been created
// as a performance hack, we only do this when update is false, i.e. the first time we're called.
if (!update) {
for (Iterator it=standardTypes.iterator (); it.hasNext (); ) {
String pname = (String) it.next();
if (prototypes.get (pname) == null) {
Prototype proto = new Prototype (pname, app);
registerPrototype (pname, new File (appDir, pname), proto, false);
prototypes.put (pname, proto);
}
}
}
if (rewire) { if (rewire) {
// there have been changes in the DbMappings // there have been changes in the DbMappings
app.rewireDbMappings (); app.rewireDbMappings ();
@ -182,7 +166,8 @@ public class TypeManager {
/** /**
* Create a prototype from a directory containing scripts and other stuff * Create a prototype from a directory containing scripts and other stuff
*/ */
public void registerPrototype (String name, File dir, Prototype proto, boolean update) { public void registerPrototype (String name, File dir, Prototype proto) {
// System.err.println ("REGISTER PROTO: "+app.getName()+"/"+name);
// app.logEvent ("registering prototype "+name); // app.logEvent ("registering prototype "+name);
// show the type checker thread that there has been type activity // show the type checker thread that there has been type activity
@ -194,84 +179,38 @@ public class TypeManager {
HashMap nskins = new HashMap (); HashMap nskins = new HashMap ();
HashMap updatables = new HashMap (); HashMap updatables = new HashMap ();
if (update) {
String list[] = dir.list();
for (int i=0; i<list.length; i++) {
File tmpfile = new File (dir, list[i]);
int dot = list[i].indexOf (".");
if (dot < 0)
continue;
String tmpname = list[i].substring(0, dot);
if (list[i].endsWith (app.templateExtension)) {
try {
Template t = new Template (tmpfile, tmpname, proto);
updatables.put (list[i], t);
ntemp.put (tmpname, t);
} catch (Throwable x) {
app.logEvent ("Error creating prototype: "+x);
}
} else if (list[i].endsWith (app.scriptExtension) && tmpfile.length () > 0) {
try {
FunctionFile ff = new FunctionFile (tmpfile, tmpname, proto);
updatables.put (list[i], ff);
nfunc.put (tmpname, ff);
} catch (Throwable x) {
app.logEvent ("Error creating prototype: "+x);
}
} else if (list[i].endsWith (app.actionExtension) && tmpfile.length () > 0) {
try {
ActionFile af = new ActionFile (tmpfile, tmpname, proto);
updatables.put (list[i], af);
nact.put (tmpname, af);
} catch (Throwable x) {
app.logEvent ("Error creating prototype: "+x);
}
} else if (list[i].endsWith (app.skinExtension)) {
try {
SkinFile sf = new SkinFile (tmpfile, tmpname, proto);
updatables.put (list[i], sf);
nskins.put (tmpname, sf);
} catch (Throwable x) {
app.logEvent ("Error creating prototype: "+x);
}
}
}
}
// Create and register type properties file // Create and register type properties file
File propfile = new File (dir, "type.properties"); File propfile = new File (dir, "type.properties");
SystemProperties props = new SystemProperties (propfile.getAbsolutePath ()); SystemProperties props = new SystemProperties (propfile.getAbsolutePath ());
DbMapping dbmap = new DbMapping (app, name, props); DbMapping dbmap = new DbMapping (app, name, props);
updatables.put ("type.properties", dbmap); updatables.put ("type.properties", dbmap);
proto.templates = ntemp; proto.templates = ntemp;
proto.functions = nfunc; proto.functions = nfunc;
proto.actions = nact; proto.actions = nact;
proto.skins = nskins; proto.skins = nskins;
proto.updatables = updatables; proto.updatables = updatables;
// init prototype on evaluators that are already initialized. // app.scriptingEngine.updatePrototype (proto);
/* Iterator evals = getRegisteredRequestEvaluators ();
while (evals.hasNext ()) {
RequestEvaluator reval = (RequestEvaluator) evals.next ();
proto.initRequestEvaluator (reval);
}*/
app.scriptingEngine.updatePrototype (proto);
} }
/** /**
* Update a prototype based on the directory which defines it. * Update a prototype based on the directory which defines it.
*/ */
public void updatePrototype (String name, File dir, Prototype proto) { public void updatePrototype (String name) {
// System.err.println ("UPDATE PROTO: "+app.getName()+"/"+name);
Prototype proto = getPrototype (name);
if (proto == null)
return;
if (System.currentTimeMillis() - proto.getLastCheck() < 1000)
return;
synchronized (proto) {
if (System.currentTimeMillis() - proto.getLastCheck() < 1000)
return;
File dir = new File (appDir, name);
boolean needsUpdate = false; boolean needsUpdate = false;
HashSet updatables = null; HashSet updatables = null;
@ -290,22 +229,22 @@ public class TypeManager {
if (proto.lastUpdate < dir.lastModified ()) { if (proto.lastUpdate < dir.lastModified ()) {
String[] list = dir.list(); String[] list = dir.list();
for (int i=0; i<list.length; i++) { for (int i=0; i<list.length; i++) {
String fn = list[i]; String fn = list[i];
if (!proto.updatables.containsKey (fn)) { if (!proto.updatables.containsKey (fn)) {
if (fn.endsWith (app.templateExtension) || fn.endsWith (app.scriptExtension) || if (fn.endsWith (app.templateExtension) || fn.endsWith (app.scriptExtension) ||
fn.endsWith (app.actionExtension) || fn.endsWith (app.skinExtension) || fn.endsWith (app.actionExtension) || fn.endsWith (app.skinExtension) ||
"type.properties".equalsIgnoreCase (fn)) { "type.properties".equalsIgnoreCase (fn)) {
needsUpdate = true; needsUpdate = true;
// updatables.add ("[new:"+proto.getName()+"/"+fn+"]"); // updatables.add ("[new:"+proto.getName()+"/"+fn+"]");
} }
} }
} }
} }
if (!needsUpdate) if (!needsUpdate) {
return; proto.markChecked ();
return;
proto.lastUpdate = System.currentTimeMillis (); }
// let the thread know we had to do something. // let the thread know we had to do something.
idleSeconds = 0; idleSeconds = 0;
@ -381,31 +320,12 @@ public class TypeManager {
} }
} }
} }
app.scriptingEngine.updatePrototype (proto); proto.markUpdated();
} // end of synchronized (proto)
// app.scriptingEngine.updatePrototype (proto);
} }
/*public void initRequestEvaluator (RequestEvaluator reval) {
if (!registeredEvaluators.contains (reval))
registeredEvaluators.add (reval);
for (Iterator it = prototypes.values().iterator(); it.hasNext(); ) {
Prototype p = (Prototype) it.next ();
p.initRequestEvaluator (reval);
}
reval.initialized = true;
}
public void unregisterRequestEvaluator (RequestEvaluator reval) {
registeredEvaluators.remove (reval);
}
public Iterator getRegisteredRequestEvaluators () {
return registeredEvaluators.iterator ();
}
public int countRegisteredRequestEvaluators () {
return registeredEvaluators.size ();
} */
} }

View file

@ -74,6 +74,8 @@ public class ZippedAppFile implements Updatable {
// System.err.println ("["+content+"]"); // System.err.println ("["+content+"]");
ActionFile act = new ActionFile (content, name, proto); ActionFile act = new ActionFile (content, name, proto);
proto.actions.put (name, act); proto.actions.put (name, act);
// mark prototype as updated
proto.markUpdated ();
} }
else if (fname.endsWith (".hsp")) { else if (fname.endsWith (".hsp")) {
String name = fname.substring (0, fname.lastIndexOf (".")); String name = fname.substring (0, fname.lastIndexOf ("."));
@ -81,6 +83,8 @@ public class ZippedAppFile implements Updatable {
// System.err.println ("["+content+"]"); // System.err.println ("["+content+"]");
Template tmp = new Template (content, name, proto); Template tmp = new Template (content, name, proto);
proto.templates.put (name, tmp); proto.templates.put (name, tmp);
// mark prototype as updated
proto.markUpdated ();
} }
else if (fname.endsWith (".skin")) { else if (fname.endsWith (".skin")) {
String name = fname.substring (0, fname.lastIndexOf (".")); String name = fname.substring (0, fname.lastIndexOf ("."));
@ -95,12 +99,16 @@ public class ZippedAppFile implements Updatable {
// System.err.println ("["+content+"]"); // System.err.println ("["+content+"]");
FunctionFile ff = new FunctionFile (content, name, proto); FunctionFile ff = new FunctionFile (content, name, proto);
proto.functions.put (name, ff); proto.functions.put (name, ff);
// mark prototype as updated
proto.markUpdated ();
} }
else if ("type.properties".equalsIgnoreCase (fname)) { else if ("type.properties".equalsIgnoreCase (fname)) {
String name = fname.substring (0, fname.lastIndexOf (".")); String name = fname.substring (0, fname.lastIndexOf ("."));
SystemProperties props = new SystemProperties (zip.getInputStream (entry)); SystemProperties props = new SystemProperties (zip.getInputStream (entry));
// DbMapping does its own registering, just construct it. // DbMapping does its own registering, just construct it.
new DbMapping (app, proto.getName (), props); new DbMapping (app, proto.getName (), props);
// mark prototype as updated
proto.markUpdated ();
} }
} }
} }

View file

@ -1,4 +1,4 @@
// ScriptingEnvironment.java // ScriptingEngine.java
// Copyright (c) Hannes Wallnöfer 1998-2001 // Copyright (c) Hannes Wallnöfer 1998-2001
package helma.scripting; package helma.scripting;
@ -13,35 +13,37 @@ import java.io.File;
* This is the interface that must be implemented to make a scripting environment * This is the interface that must be implemented to make a scripting environment
* usable by the Helma application server. * usable by the Helma application server.
*/ */
public interface ScriptingEnvironment { public interface ScriptingEngine {
/** /**
* Initialize the environment using the given properties * 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 executino context.
*/ */
public void init (Application app, Properties props) throws ScriptingException; public void enterContext (HashMap globals) throws ScriptingException;
/** /**
* A prototype has been updated and must be re-evaluated. * This method is called to let the scripting engine know that the current
* execution context has terminated.
*/ */
public void updatePrototype (Prototype prototype); public void exitContext ();
/** /**
* Invoke a function on some object, using the given arguments and global vars. * Invoke a function on some object, using the given arguments and global vars.
*/ */
public Object invoke (Object thisObject, String functionName, Object[] args, public Object invoke (Object thisObject, String functionName, Object[] args)
HashMap globals, RequestEvaluator reval)
throws ScriptingException; throws ScriptingException;
/** /**
* Get a property on an object * Get a property on an object
*/ */
public Object get (Object thisObject, String key, RequestEvaluator reval); public Object get (Object thisObject, String key);
/** /**
* Return true if a function by that name is defined for that object. * Return true if a function by that name is defined for that object.
*/ */
public boolean hasFunction (Object thisObject, String functionName, RequestEvaluator reval) public boolean hasFunction (Object thisObject, String functionName);
throws ScriptingException;
} }

View file

@ -0,0 +1,18 @@
// FesiEngineFactory.java
// Copyright (c) Hannes Wallnöfer 2002
package helma.scripting.fesi;
import helma.scripting.*;
import helma.framework.core.*;
/**
* Factory class for FESI evalator engines.
*/
public final class FesiEngineFactory {
public static ScriptingEngine getEngine (Application app, RequestEvaluator reval) {
return new FesiEvaluator (app, reval);
}
}

View file

@ -22,16 +22,16 @@ import Acme.LruHashtable;
* This is the implementation of ScriptingEnvironment for the FESI EcmaScript interpreter. * This is the implementation of ScriptingEnvironment for the FESI EcmaScript interpreter.
*/ */
public final class FesiEvaluator { public final class FesiEvaluator implements ScriptingEngine {
// the application we're running in // the application we're running in
public Application app; public final Application app;
// The FESI evaluator // The FESI evaluator
Evaluator evaluator; final Evaluator evaluator;
// the global object // the global object
GlobalObject global; final GlobalObject global;
// caching table for JavaScript object wrappers // caching table for JavaScript object wrappers
LruHashtable wrappercache; LruHashtable wrappercache;
@ -40,7 +40,7 @@ public final class FesiEvaluator {
Hashtable prototypes; Hashtable prototypes;
// the request evaluator instance owning this fesi evaluator // the request evaluator instance owning this fesi evaluator
RequestEvaluator reval; final RequestEvaluator reval;
// extensions loaded by this evaluator // extensions loaded by this evaluator
static String[] extensions = new String[] { static String[] extensions = new String[] {
@ -57,6 +57,7 @@ public final class FesiEvaluator {
// do lazy cleanup // do lazy cleanup
Map lastGlobals = null; Map lastGlobals = null;
public FesiEvaluator (Application app, RequestEvaluator reval) { public FesiEvaluator (Application app, RequestEvaluator reval) {
this.app = app; this.app = app;
this.reval = reval; this.reval = reval;
@ -67,7 +68,7 @@ public final class FesiEvaluator {
evaluator.reval = this; evaluator.reval = this;
global = evaluator.getGlobalObject(); global = evaluator.getGlobalObject();
for (int i=0; i<extensions.length; i++) for (int i=0; i<extensions.length; i++)
evaluator.addExtension (extensions[i]); evaluator.addExtension (extensions[i]);
HopExtension hopx = new HopExtension (app); HopExtension hopx = new HopExtension (app);
hopx.initializeExtension (this); hopx.initializeExtension (this);
MailExtension mailx = (MailExtension) evaluator.addExtension ("helma.scripting.fesi.extensions.MailExtension"); MailExtension mailx = (MailExtension) evaluator.addExtension ("helma.scripting.fesi.extensions.MailExtension");
@ -93,12 +94,66 @@ public final class FesiEvaluator {
Collection prototypes = app.getPrototypes(); Collection prototypes = app.getPrototypes();
for (Iterator i=prototypes.iterator(); i.hasNext(); ) { for (Iterator i=prototypes.iterator(); i.hasNext(); ) {
Prototype proto = (Prototype) i.next (); Prototype proto = (Prototype) i.next ();
evaluatePrototype (proto); initPrototype (proto);
} }
// always fully initialize global prototype, because
// we always need it and there's no chance to trigger
// creation on demand.
getPrototype ("global");
}
void initPrototype (Prototype prototype) {
// System.err.println ("FESI INIT PROTO "+prototype);
ObjectPrototype op = null;
// get the prototype's prototype if possible and necessary
ObjectPrototype opp = null;
Prototype parent = prototype.getParentPrototype ();
if (parent != null) {
// see if parent prototype is already registered. if not, register it
opp = getRawPrototype (parent.getName ());
if (opp == null) {
initPrototype (parent);
opp = getRawPrototype (parent.getName ());
}
}
String name = prototype.getName ();
if (!"global".equalsIgnoreCase (name) && !"hopobject".equalsIgnoreCase (name) && opp == null) {
if (app.isJavaPrototype (name))
opp = getRawPrototype ("__javaobject__");
else
opp = getRawPrototype ("hopobject");
}
// if prototype doesn't exist (i.e. is a standard prototype built by HopExtension), create it.
op = getRawPrototype (name);
if (op == null) {
op = new ObjectPrototype (opp, evaluator);
try {
op.putProperty ("prototypename", new ESString (name), "prototypename".hashCode ());
} catch (EcmaScriptException ignore) {}
putPrototype (name, op);
} else {
// set parent prototype just in case it has been changed
op.setPrototype (opp);
}
// Register a constructor for all types except global.
// This will first create a new prototyped hopobject and then calls
// the actual (scripted) constructor on it.
if (!"global".equalsIgnoreCase (name)) {
if (!"root".equalsIgnoreCase (name) && !"user".equalsIgnoreCase (name))
try {
FunctionPrototype fp = (FunctionPrototype) evaluator.getFunctionPrototype();
global.putHiddenProperty (name, new NodeConstructor (name, fp, this));
} catch (EcmaScriptException ignore) {}
}
// app.typemgr.updatePrototype (prototype.getName());
// evaluatePrototype (prototype);
} }
void evaluatePrototype (Prototype prototype) { void evaluatePrototype (Prototype prototype) {
// System.err.println ("FESI EVALUATE PROTO "+prototype+" FOR "+this);
ObjectPrototype op = null; ObjectPrototype op = null;
// get the prototype's prototype if possible and necessary // get the prototype's prototype if possible and necessary
@ -119,7 +174,6 @@ public final class FesiEvaluator {
else else
opp = getPrototype ("hopobject"); opp = getPrototype ("hopobject");
} }
// if prototype doesn't exist (i.e. is a standard prototype built by HopExtension), create it. // if prototype doesn't exist (i.e. is a standard prototype built by HopExtension), create it.
op = getPrototype (name); op = getPrototype (name);
if (op == null) { if (op == null) {
@ -129,18 +183,19 @@ public final class FesiEvaluator {
} catch (EcmaScriptException ignore) {} } catch (EcmaScriptException ignore) {}
putPrototype (name, op); putPrototype (name, op);
} else { } else {
// reset prototype to original state
resetPrototype (op);
// set parent prototype just in case it has been changed // set parent prototype just in case it has been changed
op.setPrototype (opp); op.setPrototype (opp);
} }
resetPrototype (op);
// Register a constructor for all types except global. // Register a constructor for all types except global.
// This will first create a node and then call the actual (scripted) constructor on it. // This will first create a new prototyped hopobject and then calls
if (!"global".equalsIgnoreCase (name)) { // the actual (scripted) constructor on it.
if (!"global".equalsIgnoreCase (name) && !"root".equalsIgnoreCase (name) && !"user".equalsIgnoreCase (name)) {
try { try {
FunctionPrototype fp = (FunctionPrototype) evaluator.getFunctionPrototype(); FunctionPrototype fp = (FunctionPrototype) evaluator.getFunctionPrototype();
evaluator.getGlobalObject().putHiddenProperty (name, new NodeConstructor (name, fp, this)); global.putHiddenProperty (name, new NodeConstructor (name, fp, this));
} catch (EcmaScriptException ignore) {} } catch (EcmaScriptException ignore) {}
} }
for (Iterator it = prototype.functions.values().iterator(); it.hasNext(); ) { for (Iterator it = prototype.functions.values().iterator(); it.hasNext(); ) {
@ -175,7 +230,6 @@ public final class FesiEvaluator {
String prop = en.nextElement ().toString (); String prop = en.nextElement ().toString ();
try { try {
ESValue esv = op.getProperty (prop, prop.hashCode ()); ESValue esv = op.getProperty (prop, prop.hashCode ());
// System.err.println (protoname+"."+obj+" -> "+esv.getClass());
if (esv instanceof ConstructedFunctionObject || esv instanceof FesiActionAdapter.ThrowException) if (esv instanceof ConstructedFunctionObject || esv instanceof FesiActionAdapter.ThrowException)
op.deleteProperty (prop, prop.hashCode()); op.deleteProperty (prop, prop.hashCode());
} catch (Exception x) {} } catch (Exception x) {}
@ -183,51 +237,40 @@ public final class FesiEvaluator {
} }
/** /**
* Invoke a function on some object, using the given arguments and global vars. * 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 executino context.
*/ */
public Object invoke (Object thisObject, String functionName, Object[] args, HashMap globals) throws ScriptingException { public void enterContext (HashMap globals) throws ScriptingException {
ESObject eso = null; // first loop through existing prototypes and update them if necessary
if (thisObject == null) for (Enumeration e=prototypes.elements(); e.hasMoreElements(); ) {
eso = global; TypeInfo info = (TypeInfo) e.nextElement ();
else // only update prototype if it has already been initialized.
eso = getElementWrapper (thisObject); // otherwise, this will be done on demand
if (info.lastUpdate > 0) {
GlobalObject global = evaluator.getGlobalObject (); Prototype p = app.typemgr.getPrototype (info.protoName);
if (p != null) {
// if we are provided with global variables to set for this invocation, app.typemgr.updatePrototype(info.protoName);
// remember the global variables before invocation to be able to reset them afterwards. if (p.getLastUpdate () > info.lastUpdate) {
Set globalVariables = null; evaluatePrototype(p);
try { info.lastUpdate = p.getLastUpdate ();
ESValue[] esv = args == null ? new ESValue[0] : new ESValue[args.length]; }
for (int i=0; i<esv.length; i++) {
// for java.util.Map objects, we use the special "tight" wrapper
// that makes the Map look like a native object
if (args[i] instanceof Map)
esv[i] = new ESMapWrapper (this, (Map) args[i]);
else
esv[i] = ESLoader.normalizeValue (args[i], evaluator);
}
if (globals != null && globals != lastGlobals) {
// remember all global variables before invocation
Set tmpGlobal = new HashSet ();
for (Enumeration en = global.getAllProperties(); en.hasMoreElements(); ) {
tmpGlobal.add (en.nextElement ().toString ());
} }
globalVariables = tmpGlobal; }
}
// loop through global vars and set them // set globals on the global object
for (Iterator i=globals.keySet().iterator(); i.hasNext(); ) { if (globals != null && globals != lastGlobals) {
String k = (String) i.next(); // loop through global vars and set them
Object v = globals.get (k); for (Iterator i=globals.keySet().iterator(); i.hasNext(); ) {
ESValue sv = null; String k = (String) i.next();
Object v = globals.get (k);
ESValue sv = null;
try {
// we do a lot of extra work to make access to global variables // we do a lot of extra work to make access to global variables
// comfortable to EcmaScript coders, i.e. we use a lot of custom wrappers // comfortable to EcmaScript coders, i.e. we use a lot of custom wrappers
// that expose properties and functions in a special way instead of just going // that expose properties and functions in a special way instead of just going
// with the standard java object wrappers. // with the standard java object wrappers.
if (v instanceof Map) { if (v instanceof Map) {
sv = new ESMapWrapper (this, (Map) v); sv = new ESMapWrapper (this, (Map) v);
} else if ("path".equals (k)) { } else if ("path".equals (k)) {
@ -255,9 +298,53 @@ public final class FesiEvaluator {
sv = ESLoader.normalizeValue (v, evaluator); sv = ESLoader.normalizeValue (v, evaluator);
} }
global.putHiddenProperty (k, sv); global.putHiddenProperty (k, sv);
} catch (Exception x) {
app.logEvent ("Error setting global variable "+k+": "+x);
} }
// remember the globals set on this evaluator }
// lastGlobals = globals; }
// 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 () {
// 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.deleteProperty (g, g.hashCode());
} 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) throws ScriptingException {
ESObject eso = null;
if (thisObject == null)
eso = global;
else
eso = getElementWrapper (thisObject);
try {
ESValue[] esv = args == null ? new ESValue[0] : new ESValue[args.length];
for (int i=0; i<esv.length; i++) {
// for java.util.Map objects, we use the special "tight" wrapper
// that makes the Map look like a native object
if (args[i] instanceof Map)
esv[i] = new ESMapWrapper (this, (Map) args[i]);
else
esv[i] = ESLoader.normalizeValue (args[i], evaluator);
} }
evaluator.thread = Thread.currentThread (); evaluator.thread = Thread.currentThread ();
ESValue retval = eso.doIndirectCall (evaluator, eso, functionName, esv); ESValue retval = eso.doIndirectCall (evaluator, eso, functionName, esv);
@ -276,24 +363,6 @@ public final class FesiEvaluator {
x.printStackTrace (); x.printStackTrace ();
} }
throw new ScriptingException (msg); throw new ScriptingException (msg);
} finally {
// remove global variables that have been added during invocation.
// this are typically undeclared variables, and we don't want them to
// endure from one request to the next since this leads to buggy code that
// relies on requests being served by the same evaluator, which is typically the
// case under development conditions but not in deployment.
if (globalVariables != null) {
for (Enumeration en = global.getAllProperties(); en.hasMoreElements(); ) {
String g = en.nextElement ().toString ();
try {
if (!globalVariables.contains (g) &&
!(global.getProperty (g, g.hashCode()) instanceof BuiltinFunctionObject))
global.deleteProperty (g, g.hashCode());
} catch (Exception x) {
System.err.println ("Error resetting global property: "+g);
}
}
}
} }
} }
@ -303,13 +372,11 @@ public final class FesiEvaluator {
* is a java object) with that name. * is a java object) with that name.
*/ */
public boolean hasFunction (Object obj, String fname) { public boolean hasFunction (Object obj, String fname) {
ESObject eso = null; // System.err.println ("HAS_FUNC: "+fname);
if (obj == null) String protoname = app.getPrototypeName (obj);
eso = evaluator.getGlobalObject ();
else
eso = getElementWrapper (obj);
try { try {
ESValue func = eso.getProperty (fname, fname.hashCode()); ObjectPrototype op = getPrototype (protoname);
ESValue func = op.getProperty (fname, fname.hashCode());
if (func != null && func instanceof FunctionPrototype) if (func != null && func instanceof FunctionPrototype)
return true; return true;
} catch (EcmaScriptException esx) { } catch (EcmaScriptException esx) {
@ -324,7 +391,7 @@ public final class FesiEvaluator {
* Check if an object has a defined property (public field if it * Check if an object has a defined property (public field if it
* is a java object) with that name. * is a java object) with that name.
*/ */
public Object getProperty (Object obj, String propname) { public Object get (Object obj, String propname) {
if (obj == null || propname == null) if (obj == null || propname == null)
return null; return null;
@ -333,7 +400,7 @@ public final class FesiEvaluator {
"password".equalsIgnoreCase (propname)) "password".equalsIgnoreCase (propname))
return "[macro access to password property not allowed]"; return "[macro access to password property not allowed]";
// if this is a HopObject, check if the property is defined // if this is a HopObject, check if the property is defined
// in the type.properties db-mapping. // in the type.properties db-mapping.
if (obj instanceof INode) { if (obj instanceof INode) {
DbMapping dbm = app.getDbMapping (prototypeName); DbMapping dbm = app.getDbMapping (prototypeName);
@ -372,13 +439,37 @@ public final class FesiEvaluator {
return app; return app;
} }
/** /**
* Get the object prototype for a prototype name * Get a raw prototype, i.e. in potentially unfinished state
* without checking if it needs to be updated.
*/
private ObjectPrototype getRawPrototype (String protoName) {
if (protoName == null)
return null;
TypeInfo info = (TypeInfo) prototypes.get (protoName);
return info == null? null : info.objectPrototype;
}
/**
* Get the object prototype for a prototype name and initialize/update it
* if necessary.
*/ */
public ObjectPrototype getPrototype (String protoName) { public ObjectPrototype getPrototype (String protoName) {
if (protoName == null) if (protoName == null)
return null; return null;
return (ObjectPrototype) prototypes.get (protoName); TypeInfo info = (TypeInfo) prototypes.get (protoName);
if (info != null && info.lastUpdate == 0) {
Prototype p = app.typemgr.getPrototype (protoName);
if (p != null) {
app.typemgr.updatePrototype(protoName);
if (p.getLastUpdate () > info.lastUpdate) {
info.lastUpdate = p.getLastUpdate ();
evaluatePrototype(p);
}
}
}
return info == null? null : info.objectPrototype;
} }
/** /**
@ -386,7 +477,7 @@ public final class FesiEvaluator {
*/ */
public void putPrototype (String protoName, ObjectPrototype op) { public void putPrototype (String protoName, ObjectPrototype op) {
if (protoName != null && op != null) if (protoName != null && op != null)
prototypes.put (protoName, op); prototypes.put (protoName, new TypeInfo (op, protoName));
} }
@ -430,7 +521,7 @@ public final class FesiEvaluator {
/** /**
* Get a script wrapper for an implemntation of helma.objectmodel.INode * Get a script wrapper for an instance of helma.objectmodel.INode
*/ */
public ESNode getNodeWrapper (INode n) { public ESNode getNodeWrapper (INode n) {
// FIXME: should this return ESNull.theNull? // FIXME: should this return ESNull.theNull?
@ -440,6 +531,7 @@ public final class FesiEvaluator {
ESNode esn = (ESNode) wrappercache.get (n); ESNode esn = (ESNode) wrappercache.get (n);
if (esn == null || esn.getNode() != n) { if (esn == null || esn.getNode() != n) {
String protoname = n.getPrototype (); String protoname = n.getPrototype ();
ObjectPrototype op = null; ObjectPrototype op = null;
// set the DbMapping of the node according to its prototype. // set the DbMapping of the node according to its prototype.
@ -511,15 +603,11 @@ public final class FesiEvaluator {
updateEvaluator (prototype, reader, es); updateEvaluator (prototype, reader, es);
} }
public synchronized void updateEvaluator (Prototype prototype, Reader reader, EvaluationSource source) { private synchronized void updateEvaluator (Prototype prototype, Reader reader, EvaluationSource source) {
try { try {
ObjectPrototype op = getPrototype (prototype.getName()); ObjectPrototype op = getPrototype (prototype.getName());
// do the update, evaluating the file // do the update, evaluating the file
evaluator.evaluate(reader, op, source, false); evaluator.evaluate(reader, op, source, false);
} catch (Throwable e) { } catch (Throwable e) {
app.logEvent ("Error parsing function file "+source+": "+e); app.logEvent ("Error parsing function file "+source+": "+e);
} finally { } finally {
@ -532,4 +620,16 @@ public final class FesiEvaluator {
} }
class TypeInfo {
ObjectPrototype objectPrototype;
long lastUpdate = 0;
String protoName;
public TypeInfo (ObjectPrototype op, String name) {
objectPrototype = op;
protoName = name;
}
}
} }

View file

@ -1,85 +0,0 @@
// FesiScriptingEnvironment.java
// Copyright (c) Hannes Wallnöfer 2002
package helma.scripting.fesi;
import helma.scripting.*;
import helma.framework.core.*;
import java.util.*;
import java.io.File;
import FESI.Data.*;
import FESI.Interpreter.*;
import FESI.Exceptions.*;
/**
* This is the implementation of ScriptingEnvironment for the FESI EcmaScript interpreter.
*/
public final class FesiScriptingEnvironment implements ScriptingEnvironment {
Application app;
Properties props;
HashMap evaluators;
/**
* Initialize the environment using the given properties
*/
public void init (Application app, Properties props) throws ScriptingException {
this.app = app;
this.props = props;
evaluators = new HashMap ();
}
/**
* A prototype has been updated and must be re-evaluated.
*/
public void updatePrototype (Prototype prototype) {
for (Iterator i = evaluators.values().iterator(); i.hasNext(); ) {
FesiEvaluator fesi = (FesiEvaluator) i.next();
fesi.evaluatePrototype (prototype);
}
}
/**
* Invoke a function on some object, using the given arguments and global vars.
*/
public Object invoke (Object thisObject, String functionName, Object[] args,
HashMap globals, RequestEvaluator reval)
throws ScriptingException {
// check if there is already a FesiEvaluator for this RequestEvaluator.
// if not, create one.
FesiEvaluator fesi = getEvaluator (reval);
return fesi.invoke (thisObject, functionName, args, globals);
}
/**
* Get a property on an object
*/
public Object get (Object thisObject, String key, RequestEvaluator reval) {
FesiEvaluator fesi = getEvaluator (reval);
return fesi.getProperty (thisObject, key);
}
/**
* Return true if a function by that name is defined for that object.
*/
public boolean hasFunction (Object thisObject, String functionName, RequestEvaluator reval)
throws ScriptingException {
FesiEvaluator fesi = getEvaluator (reval);
return fesi.hasFunction (thisObject, functionName);
}
Collection getEvaluators () {
return evaluators.values();
}
private FesiEvaluator getEvaluator (RequestEvaluator reval) {
FesiEvaluator fesi = (FesiEvaluator) evaluators.get (reval);
if (fesi == null) {
fesi = new FesiEvaluator (app, reval);
evaluators.put (reval, fesi);
}
return fesi;
}
}