diff --git a/src/helma/framework/core/Action.java b/src/helma/framework/core/Action.java new file mode 100644 index 00000000..64bc324c --- /dev/null +++ b/src/helma/framework/core/Action.java @@ -0,0 +1,112 @@ +// Action.java +// Copyright (c) Hannes Wallnöfer 1998-2000 + +package helma.framework.core; + +import java.util.*; +import java.io.*; +import helma.framework.*; +import helma.objectmodel.IServer; +import FESI.Data.*; + + +/** + * An Action is a JavaScript function that is exposed as a URI. It is + * usually represented by a file with extension .hac (hop action file) + * that contains the pure JavaScript body of the function. + */ + + +public class Action { + + String name; + String functionName; + Prototype prototype; + Application app; + long lastmod; + + public Action (File file, String name, Prototype proto) { + this.prototype = proto; + this.app = proto.app; + this.name = name; + update (file); + } + + + public void update (File f) { + + long fmod = f.lastModified (); + if (lastmod == fmod) + return; + + try { + FileReader reader = new FileReader (f); + char cbuf[] = new char[(int)f.length ()]; + reader.read (cbuf); + reader.close (); + String content = new String (cbuf); + update (content); + } catch (Exception filex) { + IServer.getLogger().log ("*** Error reading template file "+f+": "+filex); + } + lastmod = fmod; + } + + + + public void update (String content) throws Exception { + // IServer.getLogger().log ("Reading text template " + name); + + functionName = name+"_hop_action"; + + try { + app.typemgr.readFunction (functionName, "arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10", content, prototype.getName ()); + } catch (Exception x) { + String message = x.getMessage (); + app.typemgr.generateErrorFeedback (functionName, message, prototype.getName ()); + } + + } + + + public String getName () { + return name; + } + + public String getFunctionName () { + return functionName; + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/framework/core/Application.java b/src/helma/framework/core/Application.java new file mode 100644 index 00000000..9af6ed21 --- /dev/null +++ b/src/helma/framework/core/Application.java @@ -0,0 +1,552 @@ +// Application.java +// Copyright (c) Hannes Wallnöfer 1998-2000 + +package helma.framework.core; + +import java.util.*; +import java.io.*; +import java.lang.reflect.*; +import java.rmi.*; +import java.rmi.server.*; +import helma.framework.*; +import helma.objectmodel.*; +import helma.objectmodel.db.NodeManager; +import helma.objectmodel.db.WrappedNodeManager; +import helma.xmlrpc.*; +import FESI.Data.*; +import FESI.Interpreter.*; +import com.sleepycat.db.DbException; + + +/** + * The central class of a HOP application. This class keeps a pool of so-called + * request evaluators (threads with JavaScript interpreters), waits for + * requests from the Web server or XML-RPC port and dispatches them to + * the evaluators. + */ +public class Application extends UnicastRemoteObject implements IRemoteApp, Runnable { + + SystemProperties props; + File appDir, dbDir; + private String name; + protected NodeManager nmgr; + protected static WebServer xmlrpc; + protected XmlRpcAccess xmlrpcAccess; + + public boolean debug; + + TypeManager typemgr; + + RequestEvaluator eval; + private Stack freeThreads; + protected Vector allThreads; + + Hashtable sessions; + Hashtable activeUsers; + Hashtable dbMappings; + + Thread worker; + long requestTimeout = 60000; // 60 seconds for request timeout. + + protected String templateExtension, scriptExtension, actionExtension; + + // A transient node that is shared among all evaluators + protected INode appnode; + protected volatile long requestCount = 0; + protected volatile long xmlrpcCount = 0; + protected volatile long errorCount = 0; + + private DbMapping rootMapping, userRootMapping, userMapping; + + + public Application () throws RemoteException { + super (); + } + + public Application (String name, File dbHome, File appHome) throws RemoteException, DbException { + + this.name = name; + appDir = new File (appHome, name); + if (!appDir.exists()) + appDir.mkdirs (); + dbDir = new File (dbHome, name); + if (!dbDir.exists()) + dbDir.mkdirs (); + + File propfile = new File (appDir, "app.properties"); + props = new SystemProperties (propfile.getAbsolutePath (), IServer.sysProps); + + nmgr = new NodeManager (this, dbDir.getAbsolutePath (), props); + + debug = "true".equalsIgnoreCase (props.getProperty ("debug")); + try { + requestTimeout = Long.parseLong (props.getProperty ("requestTimeout", "60"))*1000l; + } catch (Exception ignore) { } + + templateExtension = props.getProperty ("templateExtension", ".hsp"); + scriptExtension = props.getProperty ("scriptExtension", ".js"); + actionExtension = props.getProperty ("actionExtension", ".hac"); + + sessions = new Hashtable (); + activeUsers = new Hashtable (); + dbMappings = new Hashtable (); + + appnode = new Node ("app"); + xmlrpc = IServer.getXmlRpcServer (); + xmlrpcAccess = new XmlRpcAccess (this); + } + + public void start () { + + eval = new RequestEvaluator (this); + IServer.getLogger().log ("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" + x.getMessage () + ""); + } finally { + releaseEvaluator (ev); + } + + res.close (); // this needs to be done before sending it back + return res; + } + + + // get raw content from the database, circumventing the scripting framework. + // currently not used/supported. + public ResponseTrans get (String path, String sessionID) { + ResponseTrans res = null; + return res; + } + + public void ping () { + // do nothing + } + + public INode getDataRoot () { + INode root = nmgr.safe.getNode ("0", null); + root.setDbMapping (rootMapping); + return root; + } + + public INode getUserRoot () { + INode users = nmgr.safe.getNode ("1", null); + users.setDbMapping (userRootMapping); + return users; + } + + public WrappedNodeManager getWrappedNodeManager () { + return nmgr.safe; + } + + public INode getUserNode (String uid) { + if ("prototype".equalsIgnoreCase (uid)) + return null; + try { + INode users = getUserRoot (); + return users.getNode (uid, false); + } catch (Exception x) { + return null; + } + } + + public Prototype getPrototype (String str) { + if (debug) + IServer.getLogger().log ("retrieving prototype for name "+str); + return typemgr.getPrototype (str); + } + + public Prototype getPrototype (INode n) { + IProperty proto = n.get ("prototype", false); + if (proto == null) + return null; + return getPrototype (proto.toString ()); + } + + + public User getUser (String sessionID) { + if (sessionID == null) + return null; + + User u = (User) sessions.get (sessionID); + if (u != null) { + u.touch (); + } else { + u = new User (sessionID, this); + sessions.put (sessionID, u); + } + return u; + } + + + public INode registerUser (String uname, String password) { + // Register a user who already has a user object + // (i.e. who has been surfing around) + if (uname == null) + return null; + uname = uname.toLowerCase ().trim (); + if ("".equals (uname)) + return null; + + INode unode = null; + try { + INode users = getUserRoot (); + unode = users.getNode (uname, false); + if (unode != null) + return null; + + unode = new Node (uname); + unode.setString ("name", uname); + unode.setString ("password", password); + unode.setString ("prototype", "user"); + unode.setDbMapping (userMapping); + users.setNode (uname, unode); + return users.getNode (uname, false); + } catch (Exception x) { + IServer.getLogger().log ("Error registering User: "+x); + return null; + } + } + + public boolean loginUser (String uname, String password, ESUser u) { + // Check the name/password of a user who already has a user object + // (i.e. who has been surfing around) + if (uname == null) + return false; + uname = uname.toLowerCase ().trim (); + if ("".equals (uname)) + return false; + + try { + INode users = getUserRoot (); + INode unode = users.getNode (uname, false); + String pw = unode.getString ("password", false); + if (pw.equals (password)) { + // give the user her piece of persistence + u.setNode (unode); + u.user.setNode (unode); + activeUsers.put (unode.getNameOrID (), u.user); + return true; + } + + } catch (Exception x) { + return false; + } + return false; + } + + public boolean logoutUser (ESUser u) { + if (u.user != null) { + String uid = u.user.uid; + if (uid != null) + activeUsers.remove (uid); + u.user.setNode (null); + u.setNode (u.user.getNode ()); + } + return true; + } + + public String getNodePath (INode n, String tmpname) { + INode root = getDataRoot (); + INode users = getUserRoot (); + String href = n.getUrl (root, users, tmpname); + return href; + } + + public String getNodeHref (INode n, String tmpname) { + boolean linkByQuery = "query".equalsIgnoreCase (props.getProperty ("linkmethod", "")); + INode root = getDataRoot (); + INode users = getUserRoot (); + String connector = linkByQuery ? "?path=" : "/"; + String req = props.getProperty ("baseURI", "") + connector; + String href = n.getHref (root, users, tmpname, req); + // add cache teaser + // href = href + "&tease="+((int) (Math.random ()*999)); + return href; + } + + + public void run () { + long cleanupSleep = 60000; // thread sleep interval (fixed) + long scheduleSleep = 60000; // interval for scheduler invocation + long lastScheduler = 0; + IServer.getLogger().log ("Starting scheduler for "+name); + // as first thing, invoke function onStart in the root object + try { + eval.invokeFunction ((INode) null, "onStart", new ESValue[0]); + } catch (Exception ignore) {} + + while (Thread.currentThread () == worker) { + // get session timeout + int sessionTimeout = 30; + try { + sessionTimeout = Math.max (0, Integer.parseInt (props.getProperty ("sessionTimeout", "30"))); + } catch (Exception ignore) {} + + try { + worker.sleep (cleanupSleep); + } catch (InterruptedException x) { + IServer.getLogger().log ("Scheduler for "+name+" interrupted"); + Thread.currentThread().interrupt(); + } + try { + IServer.getLogger().log ("Cleaning up "+name+": " + sessions.size () + " sessions active"); + long now = System.currentTimeMillis (); + Hashtable cloned = (Hashtable) sessions.clone (); + for (Enumeration e = cloned.elements (); e.hasMoreElements (); ) { + User u = (User) e.nextElement (); + if (now - u.touched () > sessionTimeout * 60000) { + if (u.uid != null) { + try { + eval.invokeFunction (u, "onLogout", new ESValue[0]); + } catch (Exception ignore) { + ignore.printStackTrace (); + } + activeUsers.remove (u.uid); + } + sessions.remove (u.getSessionID ()); + u.setNode (null); + } + } + + IServer.getLogger().log ("Cleaned up "+name+": " + sessions.size () + " sessions remaining"); + } catch (Exception cx) { + IServer.getLogger().log ("Error cleaning up sessions: "+cx); + cx.printStackTrace (); + } + + long now = System.currentTimeMillis (); + if (now - lastScheduler > scheduleSleep) { + lastScheduler = now; + ESValue val = null; + try { + val = eval.invokeFunction ((INode) null, "scheduler", new ESValue[0]); + } catch (Exception ignore) {} + try { + int ret = val.toInt32 (); + if (ret < 1000) + scheduleSleep = 60000l; + else + scheduleSleep = ret; + } catch (Exception ignore) {} + IServer.getLogger().log ("Called scheduler for "+name+", will sleep for "+scheduleSleep+" millis"); + } + } + IServer.getLogger().log ("Scheduler for "+name+" exiting"); + } + + public void rewireDbMappings () { + for (Enumeration e=dbMappings.elements(); e.hasMoreElements(); ) { + try { + DbMapping m = (DbMapping) e.nextElement (); + m.rewire (); + } catch (Exception x) { + IServer.getLogger().log ("Error rewiring DbMappings: "+x); + } + } + } + + public String getName () { + return name; + } + + public DbMapping getDbMapping (String typename) { + return (DbMapping) dbMappings.get (typename); + } + + public void putDbMapping (String typename, DbMapping dbmap) { + dbMappings.put (typename, dbmap); + } + + + /** + * Check if a method may be invoked via XML-RPC on a prototype + */ + protected void checkXmlRpcAccess (String proto, String method) throws Exception { + xmlrpcAccess.checkAccess (proto, method); + } + +} + +////////////////////////////////////////////////////////////// +//// XML-RPC handler class + + +class XmlRpcInvoker implements XmlRpcHandler { + + Application app; + + public XmlRpcInvoker (Application app) { + this.app = app; + } + + public Object execute (String method, Vector argvec) throws Exception { + + app.xmlrpcCount += 1; + + Object retval = null; + RequestEvaluator ev = null; + try { + ev = app.getEvaluator (); + retval = ev.invokeXmlRpc (method, argvec); + } finally { + app.releaseEvaluator (ev); + } + return retval; + } +} + + +////////////////////////////////////////////////////////////// +//// XML-RPC access permission checker + + +class XmlRpcAccess { + + Application app; + Hashtable prototypes; + long lastmod; + + public XmlRpcAccess (Application app) { + this.app = app; + init (); + } + + public void checkAccess (String proto, String method) throws Exception { + if (app.props.lastModified () != lastmod) + init (); + Hashtable protoAccess = (Hashtable) prototypes.get (proto.toLowerCase ()); + if (protoAccess == null) + throw new Exception ("Method "+method+" is not callable via XML-RPC"); + if (protoAccess.get (method.toLowerCase ()) == null) + throw new Exception ("Method "+method+" is not callable via XML-RPC"); + } + + /* + * create internal representation of XML-RPC-Permissions. They're encoded in the app property + * file like this: + * xmlrpcAccess = root.sayHello, story.countMessages, user.login + * i.e. a prototype.method entry for each function callable via XML-RPC. + */ + private void init () { + String newAccessprop = app.props.getProperty ("xmlrpcaccess"); + Hashtable newPrototypes = new Hashtable (); + if (newAccessprop != null) { + StringTokenizer st = new StringTokenizer (newAccessprop, ",; "); + while (st.hasMoreTokens ()) { + String token = st.nextToken ().trim (); + int dot = token.indexOf ("."); + if (dot > -1) { + String proto = token.substring (0, dot).toLowerCase (); + String method = token.substring (dot+1).toLowerCase (); + Hashtable protoAccess = (Hashtable) newPrototypes.get (proto); + if (protoAccess == null) { + protoAccess = new Hashtable (); + newPrototypes.put (proto, protoAccess); + } + protoAccess.put (method, method); + } + } + } + this.prototypes = newPrototypes; + this.lastmod = app.props.lastModified (); + } + +} + + + + + diff --git a/src/helma/framework/core/ESAppNode.java b/src/helma/framework/core/ESAppNode.java new file mode 100644 index 00000000..b8aeca84 --- /dev/null +++ b/src/helma/framework/core/ESAppNode.java @@ -0,0 +1,95 @@ +// ESAppNode.java +// Copyright (c) Hannes Wallnöfer 1998-2000 + +package helma.framework.core; + +import helma.objectmodel.*; +import FESI.Exceptions.*; +import FESI.Data.*; + +/** + * ESApp represents the app node of an application, providing an app-wide transient shared + * space as well as access to some app related runtime information. + */ + +public class ESAppNode extends ESNode { + + private Application app; + private DatePrototype createtime; + + public ESAppNode (INode node, RequestEvaluator eval) { + super (eval.esNodePrototype, eval.evaluator, node, eval); + app = eval.app; + createtime = new DatePrototype (eval.evaluator, node.created()); + } + + /** + * Overrides getProperty to return some app-specific properties + */ + public ESValue getProperty (String propname, int hash) throws EcmaScriptException { + if ("requestCount".equals (propname)) { + return new ESNumber (app.requestCount); + } + if ("xmlrpcCount".equals (propname)) { + return new ESNumber (app.xmlrpcCount); + } + if ("errorCount".equals (propname)) { + return new ESNumber (app.errorCount); + } + if ("upSince".equals (propname)) { + return createtime; + } + return super.getProperty (propname, hash); + } + + + public String toString () { + return ("AppNode "+node.getNameOrID ()); + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/framework/core/ESNode.java b/src/helma/framework/core/ESNode.java new file mode 100644 index 00000000..30e05276 --- /dev/null +++ b/src/helma/framework/core/ESNode.java @@ -0,0 +1,560 @@ +// ESNode.java +// Copyright (c) Hannes Wallnöfer 1998-2000 + + +package helma.framework.core; + +import helma.objectmodel.*; +import helma.util.*; +import FESI.Interpreter.*; +import FESI.Exceptions.*; +import FESI.Data.*; +import java.io.*; +import java.util.*; + + +/** + * An EcmaScript wrapper around a 'Node' object. This is the basic + * HOP object type that can be stored in the internal or external databases. + * All HOP types inherit from the Node object. + */ + +public class ESNode extends ObjectPrototype { + + INode node; + INode cache; + + // The ID of the wrapped Node. Makes ESNodes comparable without accessing the wrapped node. + String nodeID; + String protoName; + ESObject cacheWrapper; + Throwable lastError = null; + RequestEvaluator eval; + + // used to create cache nodes + protected ESNode (INode node, RequestEvaluator eval) { + super (eval.esNodePrototype, eval.evaluator); + this.eval = eval; + this.node = node; + cache = null; + + cacheWrapper = null; + nodeID = node.getID (); + } + + public ESNode (ESObject prototype, Evaluator evaluator, Object obj, RequestEvaluator eval) { + super (prototype, evaluator); + // IServer.getLogger().log ("in ESNode constructor: "+o.getClass ()); + this.eval = eval; + if (obj == null) + node = new Node (); + else if (obj instanceof ESWrapper) + node = (INode) ((ESWrapper) obj).getJavaObject (); + else if (obj instanceof INode) + node = (INode) obj; + else + node = new Node (obj.toString ()); + // set nodeID to id of wrapped node + nodeID = node.getID (); + + // get transient cache Node + cache = node.getCacheNode (); + cacheWrapper = new ESNode (cache, eval); + } + + /** + * Check if the node has been invalidated. If so, it has to be re-fetched + * from the db via the app's node manager. + */ + private void checkNode () { + if (node.getState () == INode.INVALID) try { + setNode (eval.app.nmgr.getNode (node.getID (), node.getDbMapping ())); + } catch (Exception nx) {} + } + + public INode getNode () { + checkNode (); + return node; + } + + public void setNode (INode node) { + if (node != null) { + this.node = node; + eval.objectcache.put (node, this); + // get transient cache Node + cache = node.getCacheNode (); + cacheWrapper = new ESNode (cache, eval); + } + } + + public void setPrototype (String protoName) { + checkNode (); + this.protoName = protoName; + node.setString ("prototype", protoName); + } + + public String getESClassName () { + return protoName == null ? "Node" : protoName; + } + + public String toString () { + if (node == null) + return ""; + return node.toString (); + } + + public String toDetailString () { + return "ES:[Object: builtin " + this.getClass().getName() + ":" + + ((node == null) ? "null" : node.toString()) + "]"; + } + + protected void setError (Throwable e) { + lastError = e; + } + + + public boolean setContent (ESValue what[]) { + checkNode (); + if (what.length > 0) { + if (what[0] instanceof ESString) { + node.setContent (what[0].toString ()); + return true; + } + if (what[0] instanceof ESWrapper) { + Object o = ((ESWrapper) what[0]).toJavaObject (); + if (o instanceof INode) { + try { + INode p = (INode) o; + node.setContent (p.getContent (), p.getContentType ()); + return true; + } catch (Exception x) { + IServer.getLogger().log ("error in ESNode.setContent: "+x); + } + } + } + if (what[0] instanceof ESNode) { + INode i = ((ESNode) what[0]).getNode (); + try { + node.setContent (i.getContent (), i.getContentType ()); + return true; + } catch (Exception x) { + IServer.getLogger().log ("error in ESNode.setContent: "+x); + } + } + } + return false; + } + + public Object getContent () { + checkNode (); + if (node.getContentLength () == 0) + return null; + String contentType = node.getContentType (); + if (contentType != null && contentType.startsWith ("text/")) { + return node.getText (); + } else { + return node.getContent (); + } + } + + public boolean add (ESValue what[]) { + checkNode (); + for (int i=0; i "+esn); + if (esn != null) { + esn.reWrap (newnode.getSubnodeAt (i)); + } + } + for (Enumeration e=oldnode.properties (); e.hasMoreElements (); ) { + IProperty p = (IProperty) e.nextElement (); + if (p.getType () == IProperty.NODE) { + INode next = p.getNodeValue (); + ESNode esn = eval.getNodeWrapperFromCache (next); + if (esn != null) { + esn.reWrap (newnode.getNode (p.getName (), false)); + } + } + } + } + + /** + * Remove one or more subnodes. + */ + public boolean remove (ESValue args[]) { + checkNode (); + for (int i=0; i 2) + return false; + if (!(pval[0] instanceof ESNode)) + return false; + ESNode esn = (ESNode) pval[0]; + INode n = esn.getNode (); + if (!(n instanceof helma.objectmodel.db.Node)) + return false; + // check if there is an additional string element - if so, it's the property name by which the node is + // accessed, otherwise it will be accessed as anonymous subnode via its id + if (pval.length == 2) + ((helma.objectmodel.db.Node) node).setParent ((helma.objectmodel.db.Node) n, pval[1].toString ()); + else + ((helma.objectmodel.db.Node) node).setParent ((helma.objectmodel.db.Node) n, null); + return true; + } + + + public void putProperty(String propertyName, ESValue propertyValue, int hash) throws EcmaScriptException { + checkNode (); + // IServer.getLogger().log ("put property called: "+propertyName+", "+propertyValue.getClass()); + if ("lastmodified".equalsIgnoreCase (propertyName) || "created".equalsIgnoreCase (propertyName) || + "contentlength".equalsIgnoreCase (propertyName) || "cache".equalsIgnoreCase (propertyName) || + "prototype".equalsIgnoreCase (propertyName)) + throw new EcmaScriptException ("Can't modify read-only property \""+propertyName+"\"."); + + if ("subnodeRelation".equalsIgnoreCase (propertyName)) { + node.setSubnodeRelation (propertyValue instanceof ESNull ? null : propertyValue.toString ()); + return; + } + + if ("contenttype".equalsIgnoreCase (propertyName)) + node.setContentType (propertyValue.toString ()); + else if (propertyValue instanceof ESNull) + node.unset (propertyName); + else if (propertyValue instanceof ESString) + node.setString (propertyName, propertyValue.toString ()); + else if (propertyValue instanceof ESBoolean) + node.setBoolean (propertyName, propertyValue.booleanValue ()); + else if (propertyValue instanceof ESNumber) + node.setFloat (propertyName, propertyValue.doubleValue ()); + else if (propertyValue instanceof DatePrototype) + node.setDate (propertyName, (Date) propertyValue.toJavaObject ()); + else if (propertyValue instanceof ESNode) { + // long now = System.currentTimeMillis (); + ESNode esn = (ESNode) propertyValue; + node.setNode (propertyName, esn.getNode ()); + if (esn.getNode () instanceof helma.objectmodel.Node && + !(node instanceof helma.objectmodel.Node)) { + INode newnode = node.getNode (propertyName, false); + esn.reWrap (newnode); + } + // IServer.getLogger().log ("*** spent "+(System.currentTimeMillis () - now)+" ms to set property "+propertyName); + } else { + // IServer.getLogger().log ("got "+propertyValue.getClass ()); + // A persistent node can't store anything other than the types above, so throw an exception + // throw new EcmaScriptException ("Can't set a JavaScript Object or Array as property of "+node); + node.setJavaObject (propertyName, propertyValue.toJavaObject ()); + } + } + + public boolean deleteProperty(String propertyName, int hash) throws EcmaScriptException { + checkNode (); + // IServer.getLogger().log ("delete property called: "+propertyName); + if (node.get (propertyName, false) != null) { + node.unset (propertyName); + return true; + } + return super.deleteProperty (propertyName, hash); + } + + public ESValue getProperty (int i) throws EcmaScriptException { + checkNode (); + INode n = node.getSubnodeAt (i); + if (n == null) return ESNull.theNull; + return eval.getNodeWrapper (n); + } + + public void putProperty(int index, ESValue propertyValue) throws EcmaScriptException { + checkNode (); + if (propertyValue instanceof ESNode) { + ESNode n = (ESNode) propertyValue; + node.addNode (n.getNode (), index); + } else + throw new EcmaScriptException ("Can only add Nodes to Node arrays"); + } + + + public ESValue getProperty(String propertyName, int hash) throws EcmaScriptException { + checkNode (); + // IServer.getLogger().log ("get property called: "+propertyName); + ESValue retval = super.getProperty (propertyName, hash); + if (! (retval instanceof ESUndefined)) + return retval; + + if ("cache".equalsIgnoreCase (propertyName) && cache != null) + return cacheWrapper; + if ("created".equalsIgnoreCase (propertyName)) + return new DatePrototype (evaluator, node.created ()); + if ("lastmodified".equalsIgnoreCase (propertyName)) + return new DatePrototype (evaluator, node.lastModified ()); + if ("contenttype".equalsIgnoreCase (propertyName)) + return new ESString (node.getContentType ()); + if ("contentlength".equalsIgnoreCase (propertyName)) + return new ESNumber (node.getContentLength ()); + + if ("subnodeRelation".equalsIgnoreCase (propertyName)) { + String rel = node.getSubnodeRelation (); + return rel == null ? (ESValue) ESNull.theNull : new ESString (rel); + } + + // this is not very nice, but as a hack we expose the id of a node as node.__id__ + if ("__id__".equals (propertyName)) + return new ESString (node.getID ()); + + // this _may_ do a relational query if properties are mapped to a relational type. + IProperty p = (IProperty) node.get (propertyName, false); + if (p != null) { + if (p.getType () == IProperty.STRING) { + String str = p.getStringValue (); + if (str == null) + return ESNull.theNull; + else + return new ESString (str); + } + if (p.getType () == IProperty.BOOLEAN) + return ESBoolean.makeBoolean (p.getBooleanValue ()); + if (p.getType () == IProperty.DATE) + return new DatePrototype (evaluator, p.getDateValue ()); + if (p.getType () == IProperty.INTEGER) + return new ESNumber ((double) p.getIntegerValue ()); + if (p.getType () == IProperty.FLOAT) + return new ESNumber (p.getFloatValue ()); + if (p.getType () == IProperty.NODE) { + INode nd = p.getNodeValue (); + if (nd == null) + return ESNull.theNull; + else + return eval.getNodeWrapper (nd); + } + if (p.getType () == IProperty.JAVAOBJECT) + return ESLoader.normalizeObject (p.getJavaObjectValue (), evaluator); + } + + // some more internal properties + if ("__parent__".equals (propertyName)) { + INode n = node.getParent (); + if (n == null) + return ESNull.theNull; + else + return eval.getNodeWrapper (n); + } + if ("__name__".equals (propertyName)) + return new ESString (node.getName ()); + if ("__fullname__".equals (propertyName)) + return new ESString (node.getFullName ()); + if ("__hash__".equals (propertyName)) + return new ESString (""+node.hashCode ()); + if ("__node__".equals (propertyName)) + return ESLoader.normalizeObject (node, evaluator); + + // as last resort, try to get property as anonymous subnode + INode anon = node.getSubnode (propertyName); + if (anon != null) + return eval.getNodeWrapper (anon); + + return ESNull.theNull; + } + + public Enumeration getAllProperties () { + return getProperties (); + } + + public Enumeration getProperties () { + checkNode (); + class PropEnum implements Enumeration { + Enumeration props = node.properties(); + public boolean hasMoreElements () { + return props.hasMoreElements (); + } + public Object nextElement () { + IProperty p = (IProperty) props.nextElement (); + return p.getName(); + } + } + return new PropEnum (); + } + + + public String error() { + if (lastError == null) { + return ""; + } else { + String exceptionName = lastError.getClass().getName(); + int l = exceptionName.lastIndexOf("."); + if (l>0) exceptionName = exceptionName.substring(l+1); + return exceptionName +": " + lastError.getMessage(); + } + } + + public void clearError() { + lastError = null; + } + + /** + * An ESNode equals another object if it is an ESNode that wraps the same INode + * or the wrapped INode itself. FIXME: doesen't check dbmapping/type! + */ + public boolean equals (Object what) { + if (what == null) return false; + if (what == this) return true; + if (what instanceof ESNode) { + return (((ESNode) what).nodeID.equals (this.nodeID)); + } + return false; + } + +} // class ESNode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/framework/core/ESRequestData.java b/src/helma/framework/core/ESRequestData.java new file mode 100644 index 00000000..8013fb5e --- /dev/null +++ b/src/helma/framework/core/ESRequestData.java @@ -0,0 +1,126 @@ +// ESRequestData.java +// Copyright (c) Hannes Wallnöfer 1998-2000 + + +package helma.framework.core; + +import FESI.Data.*; +import FESI.Exceptions.*; +import FESI.Interpreter.Evaluator; +import java.util.*; +import helma.objectmodel.INode; + + +/** + * An EcmaScript object to access the form data sent with a HTTP request + */ + +public class ESRequestData extends ESWrapper { + + private Hashtable data; + private RequestEvaluator reval; + + public ESRequestData (ESObject prototype, Evaluator evaluator, RequestEvaluator reval) { + super (prototype, evaluator); + this.reval = reval; + } + + public void setData (Hashtable data) { + this.data = data; + } + + /** + * Overridden to make the object read-only + */ + public void putProperty(String propertyName, ESValue propertyValue, int hash) throws EcmaScriptException { + throw new EcmaScriptException ("Can't set property, req.data is read-only"); + } + + public boolean deleteProperty(String propertyName, int hash) throws EcmaScriptException { + throw new EcmaScriptException ("Can't delete property, req.data is read-only"); + } + + public ESValue getProperty(String propertyName, int hash) throws EcmaScriptException { + if (data == null) + return ESNull.theNull; + + Object val = data.get (propertyName); + + if (val == null) + return ESNull.theNull; + + if (val instanceof String) + return new ESString ((String) val); + else if (val instanceof INode) + return reval.getNodeWrapper ((INode) val); + + return ESLoader.normalizeValue(val, evaluator); + } + + + public Enumeration getAllProperties () { + return getProperties (); + } + + public Enumeration getProperties () { + if (data == null) + return new Hashtable().keys(); + return data.keys(); + } + + + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/framework/core/ESUser.java b/src/helma/framework/core/ESUser.java new file mode 100644 index 00000000..b1990b35 --- /dev/null +++ b/src/helma/framework/core/ESUser.java @@ -0,0 +1,114 @@ +// ESUser.java +// Copyright (c) Hannes Wallnöfer 1998-2000 + +package helma.framework.core; + +import helma.objectmodel.*; +import FESI.Interpreter.*; +import FESI.Exceptions.*; +import FESI.Data.*; + +/** + * The ESUser is a special kind of Node object that represents a user of + * a HOP application. The actual user session data are kept in class User. + * If the user is logged in as a registered member, the wrapped node represents + * the user object in the database, while for anonymous surfers the node object + * is just a transient node.

+ * This means that the wrapped node will be swapped when the user logs in or out. + * To save session state across logins and logouts, the + * cache property of the user object stays the same for the whole time the user + * spends on this site. + */ + +public class ESUser extends ESNode { + + // if the user is online, this is his/her online session object + public User user; + + public ESUser (INode node, RequestEvaluator eval) { + super (eval.esUserPrototype, eval.evaluator, node, eval); + user = (User) eval.app.activeUsers.get (node.getNameOrID ()); + if (user == null) + user = (User) eval.app.sessions.get (node.getNameOrID ()); + if (user != null) { + cache = user.cache; + cacheWrapper = new ESNode (cache, eval); + } + } + + /** + * Overrides getProperty to return the uid (which is not a regular property) + */ + public ESValue getProperty (String propname, int hash) throws EcmaScriptException { + if ("uid".equals (propname)) { + if (user == null || user.uid == null) + return ESNull.theNull; + else + return new ESString (user.uid); + } + if ("sessionID".equals (propname)) { + return new ESString (user.getSessionID ()); + } + return super.getProperty (propname, hash); + } + + + public void setUser (User user) { + if (this.user != user) { + this.user = user; + cache = user.cache; + } + cacheWrapper = new ESNode (cache, eval); + } + + + public String toString () { + return ("UserNode "+node.getNameOrID ()); + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/framework/core/FunctionFile.java b/src/helma/framework/core/FunctionFile.java new file mode 100644 index 00000000..fec6aa80 --- /dev/null +++ b/src/helma/framework/core/FunctionFile.java @@ -0,0 +1,81 @@ +// FunctionFile.java +// Copyright (c) Hannes Wallnöfer 1998-2000 + +package helma.framework.core; + +import java.util.*; +import java.io.*; +import helma.framework.*; + + +/** + * This represents a File containing JavaScript functions for a given Object. + */ + + +public class FunctionFile { + + String name; + Prototype prototype; + Application app; + long lastmod; + + public FunctionFile (File file, String name, Prototype proto) { + this.prototype = proto; + this.app = proto.app; + this.name = name; + update (file); + } + + + public void update (File f) { + + long fmod = f.lastModified (); + if (lastmod == fmod) + return; + + lastmod = fmod; + app.typemgr.readFunctionFile (f, prototype.getName ()); + } + + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/framework/core/HopExtension.java b/src/helma/framework/core/HopExtension.java new file mode 100644 index 00000000..1dc20134 --- /dev/null +++ b/src/helma/framework/core/HopExtension.java @@ -0,0 +1,1044 @@ +// HopExtension.java +// Copyright (c) Hannes Wallnöfer 1998-2000 + +package helma.framework.core; + +import helma.objectmodel.*; +import helma.util.*; +import FESI.Interpreter.*; +import FESI.Exceptions.*; +import FESI.Extensions.*; +import FESI.Data.*; +import java.io.*; +import java.util.*; +import java.text.*; +import org.xml.sax.InputSource; + +/** + * This is the basic Extension for FESI interpreters used in HOP. It sets up + * varios constructors, global functions and properties etc. + */ + +public class HopExtension { + + protected Application app; + + public HopExtension () { + super(); + } + + + /** + * Called by the evaluator after the extension is loaded. + */ + public void initializeExtension (RequestEvaluator reval) throws EcmaScriptException { + + this.app = reval.app; + Evaluator evaluator = reval.evaluator; + GlobalObject go = evaluator.getGlobalObject(); + FunctionPrototype fp = (FunctionPrototype) evaluator.getFunctionPrototype(); + + ESObject op = evaluator.getObjectPrototype(); + + ////////// The editor functions for String, Boolean and Number are deprecated! + ESObject sp = evaluator.getStringPrototype (); + sp.putHiddenProperty ("editor", new StringEditor ("editor", evaluator, fp)); + ESObject np = evaluator.getNumberPrototype (); + np.putHiddenProperty ("editor", new NumberEditor ("editor", evaluator, fp)); + np.putHiddenProperty ("format", new NumberPrototypeFormat ("format", evaluator, fp)); + ESObject bp = evaluator.getBooleanPrototype (); + bp.putHiddenProperty ("editor", new BooleanEditor ("editor", evaluator, fp)); + ESObject dp = evaluator.getDatePrototype (); + dp.putHiddenProperty ("format", new DatePrototypeFormat ("format", evaluator, fp)); + + reval.esNodePrototype = new ObjectPrototype(op, evaluator); // the Node prototype + + reval.esUserPrototype = new ObjectPrototype (reval.esNodePrototype, evaluator); // the User prototype + + ESObject node = new NodeConstructor ("Node", fp, reval); // the Node constructor + + // register the default methods of Node objects in the Node prototype + reval.esNodePrototype.putHiddenProperty ("add", new NodeAdd ("add", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("addAt", new NodeAddAt ("addAt", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("remove", new NodeRemove ("remove", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("link", new NodeLink ("link", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("list", new NodeList ("list", evaluator, fp, reval)); + reval.esNodePrototype.putHiddenProperty ("set", new NodeSet ("set", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("get", new NodeGet ("get", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("count", new NodeCount ("count", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("contains", new NodeContains ("contains", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("size", new NodeCount ("size", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("editor", new NodeEditor ("editor", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("chooser", new NodeChooser ("chooser", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("multiChooser", new MultiNodeChooser ("multiChooser", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("getContent", new NodeGetContent ("getContent", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("setContent", new NodeSetContent ("setContent", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("path", new NodePath ("path", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("href", new NodeHref ("href", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("setParent", new NodeSetParent ("setParent", evaluator, fp)); + reval.esNodePrototype.putHiddenProperty ("invalidate", new NodeInvalidate ("invalidate", evaluator, fp)); + + // methods that give access to properties and global user lists + go.putHiddenProperty("Node", node); // register the constructor for a plain Node object. + go.putHiddenProperty("getProperty", new GlobalGetProperty ("getProperty", evaluator, fp)); + go.putHiddenProperty("token", new GlobalGetProperty ("token", evaluator, fp)); + go.putHiddenProperty("getUser", new GlobalGetUser ("getUser", evaluator, fp, reval)); + go.putHiddenProperty("getUserBySession", new GlobalGetUserBySession ("getUserBySession", evaluator, fp, reval)); + go.putHiddenProperty("getAllUsers", new GlobalGetAllUsers ("getAllUsers", evaluator, fp, reval)); + go.putHiddenProperty("getActiveUsers", new GlobalGetActiveUsers ("getActiveUsers", evaluator, fp, reval)); + go.putHiddenProperty("isActive", new GlobalIsActive ("isActive", evaluator, fp)); + go.putHiddenProperty("getAge", new GlobalGetAge ("getAge", evaluator, fp)); + go.putHiddenProperty("getURL", new GlobalGetURL ("getURL", evaluator, fp)); + go.putHiddenProperty("encode", new GlobalEncode ("encode", evaluator, fp)); + go.putHiddenProperty("encodeXml", new GlobalEncodeXml ("encodeXml", evaluator, fp)); + go.putHiddenProperty("format", new GlobalFormat ("format", evaluator, fp)); + go.putHiddenProperty("getXmlDocument", new GlobalGetXmlDocument ("getXmlDocument", evaluator, fp)); + go.putHiddenProperty("getHtmlDocument", new GlobalGetHtmlDocument ("getHtmlDocument", evaluator, fp)); + go.putHiddenProperty("jdomize", new GlobalJDOM ("jdomize", evaluator, fp)); + go.deleteProperty("exit", "exit".hashCode()); + + // and some methods for session management from JS... + reval.esUserPrototype.putHiddenProperty("logon", new UserLogin ("logon", evaluator, fp)); + reval.esUserPrototype.putHiddenProperty("login", new UserLogin ("login", evaluator, fp)); + reval.esUserPrototype.putHiddenProperty("register", new UserRegister ("register", evaluator, fp, reval)); + reval.esUserPrototype.putHiddenProperty("logout", new UserLogout ("logout", evaluator, fp)); + reval.esUserPrototype.putHiddenProperty("onSince", new UserOnSince ("onSince", evaluator, fp)); + reval.esUserPrototype.putHiddenProperty("lastActive", new UserLastActive ("lastActive", evaluator, fp)); + reval.esUserPrototype.putHiddenProperty("touch", new UserTouch ("touch", evaluator, fp)); + + } + + + class NodeSetContent extends BuiltinFunctionObject { + NodeSetContent (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode node = (ESNode) thisObject; + return ESBoolean.makeBoolean (node.setContent (arguments)); + } + } + + class NodeGetContent extends BuiltinFunctionObject { + NodeGetContent (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode node = (ESNode) thisObject; + Object content = node.getContent (); + if (content == null) + return ESNull.theNull; + else + return ESLoader.normalizeValue (content, this.evaluator); + } + } + + class NodeAdd extends BuiltinFunctionObject { + NodeAdd (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode node = (ESNode) thisObject; + return ESBoolean.makeBoolean (node.add (arguments)); + } + } + + class NodeAddAt extends BuiltinFunctionObject { + NodeAddAt (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode node = (ESNode) thisObject; + return ESBoolean.makeBoolean (node.addAt (arguments)); + } + } + + class NodeRemove extends BuiltinFunctionObject { + NodeRemove (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode node = (ESNode) thisObject; + return ESBoolean.makeBoolean (node.remove (arguments)); + } + } + + + class NodeLink extends BuiltinFunctionObject { + NodeLink (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode node = (ESNode) thisObject; + return ESBoolean.makeBoolean (node.link (arguments)); + } + } + + class NodeList extends BuiltinFunctionObject { + RequestEvaluator reval; + NodeList (String name, Evaluator evaluator, FunctionPrototype fp, RequestEvaluator reval) { + super(fp, evaluator, name, 0); + this.reval = reval; + } + public ESValue callFunction(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode node = (ESNode) thisObject; + return node.list (); + } + } + + class NodeGet extends BuiltinFunctionObject { + NodeGet (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESValue esv = null; + if (arguments[0].isNumberValue ()) { + int i = arguments[0].toInt32 (); + esv = thisObject.getProperty (i); + } else { + String name = arguments[0].toString (); + esv = thisObject.getProperty (name, name.hashCode ()); + } + return (esv); + } + } + + class NodeSet extends BuiltinFunctionObject { + NodeSet (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode esn = (ESNode) thisObject; + if (arguments[0].isNumberValue ()) { + return ESBoolean.makeBoolean (esn.addAt (arguments)); + } else { + String propname = arguments[0].toString (); + esn.putProperty (propname, arguments[1], propname.hashCode ()); + } + return ESBoolean.makeBoolean (true); + } + } + + class NodeCount extends BuiltinFunctionObject { + NodeCount (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + INode node = ((ESNode) thisObject).getNode (); + return new ESNumber ((double) node.numberOfNodes ()); + } + } + + class NodeContains extends BuiltinFunctionObject { + NodeContains (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode node = (ESNode) thisObject; + return new ESNumber (node.contains (arguments)); + } + } + + + class NodeInvalidate extends BuiltinFunctionObject { + NodeInvalidate (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 0); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode esn = (ESNode) thisObject; + INode node = esn.getNode (); + if (node instanceof helma.objectmodel.db.Node) { + ((helma.objectmodel.db.Node) node).invalidate (); + try { + node = app.nmgr.getNode (node.getID (), node.getDbMapping ()); + esn.setNode (node); + } catch (Exception x) {} + } + return ESBoolean.makeBoolean (true); + } + } + + + class NodeEditor extends BuiltinFunctionObject { + + NodeEditor (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + if (arguments.length < 1) + throw new EcmaScriptException ("Missing argument for Node.editor(): Name of property to be edited."); + String propName = arguments[0].toString (); + ESValue propValue = thisObject.getProperty (propName, propName.hashCode ()); + if (propValue.isBooleanValue ()) + return (booleanEditor (thisObject, propName, propValue, arguments)); + if (propValue.isNumberValue ()) + return (numberEditor (thisObject, propName, propValue, arguments)); + if (propValue instanceof DatePrototype) + return (dateEditor (thisObject, propName, propValue, arguments)); + return (stringEditor (thisObject, propName, propValue, arguments)); + } + + public ESValue stringEditor (ESObject thisObject, String propName, ESValue propValue, ESValue[] arguments) + throws EcmaScriptException { + String value = null; + if (propValue == null || ESNull.theNull == propValue || ESUndefined.theUndefined == propValue) + value = ""; + else + value = HtmlEncoder.encodeFormValue (propValue.toString ()); + if (arguments.length == 2 && arguments[1].isNumberValue ()) + return new ESString (""); + else if (arguments.length == 3 && arguments[1].isNumberValue () && arguments[2].isNumberValue ()) + return new ESString (""); + return new ESString (""); + } + + public ESValue dateEditor (ESObject thisObject, String propName, ESValue propValue, ESValue[] arguments) + throws EcmaScriptException { + Date date = (Date) propValue.toJavaObject (); + DateFormat fmt = arguments.length > 1 ? new SimpleDateFormat (arguments[1].toString ()) : new SimpleDateFormat (); + return new ESString (""); + } + + public ESValue numberEditor (ESObject thisObject, String propName, ESValue propValue, ESValue[] arguments) + throws EcmaScriptException { + ESNumber esn = (ESNumber) propValue.toESNumber (); + double value = esn.doubleValue (); + if (arguments.length == 3 && arguments[1].isNumberValue () && arguments[2].isNumberValue ()) + return new ESString (getNumberChoice (propName, arguments[1].toInt32 (), arguments[2].toInt32 (), 1, value)); + else if (arguments.length == 4 && arguments[1].isNumberValue () && arguments[2].isNumberValue () && arguments[3].isNumberValue ()) + return new ESString (getNumberChoice (propName, arguments[1].doubleValue (), arguments[2].doubleValue (), arguments[3].doubleValue (), value)); + DecimalFormat fmt = new DecimalFormat (); + if (arguments.length == 2 && arguments[1].isNumberValue ()) + return new ESString (""); + else + return new ESString (""); + } + + public ESValue booleanEditor (ESObject thisObject, String propName, ESValue propValue, ESValue[] arguments) + throws EcmaScriptException { + ESBoolean esb = (ESBoolean) propValue.toESBoolean (); + String value = esb.toString (); + if (arguments.length < 2) { + String retval = ""); + return new ESString (retval.toString ()); + } + } + + + class StringEditor extends BuiltinFunctionObject { + StringEditor (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + throw (new EcmaScriptException ("String.editor() wird nicht mehr unterstuetzt. Statt node.strvar.editor(...) kann node.editor(\"strvar\", ...) verwendet werden.")); + } + } + + class NumberEditor extends BuiltinFunctionObject { + NumberEditor (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + throw (new EcmaScriptException ("Number.editor() wird nicht mehr unterstuetzt. Statt node.nvar.editor(...) kann node.editor(\"nvar\", ...) verwendet werden.")); + } + } + + class BooleanEditor extends BuiltinFunctionObject { + BooleanEditor (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + throw (new EcmaScriptException ("Boolean.editor() wird nicht mehr unterstuetzt. Statt node.boolvar.editor(...) kann node.editor(\"boolvar\", ...) verwendet werden.")); + } + } + + // previously in FESI.Data.DateObject + class DatePrototypeFormat extends BuiltinFunctionObject { + DatePrototypeFormat(String name, Evaluator evaluator, FunctionPrototype fp) { + super(fp, evaluator, name, 0); + } + public ESValue callFunction(ESObject thisObject, + ESValue[] arguments) + throws EcmaScriptException { + DatePrototype aDate = (DatePrototype) thisObject; + + DateFormat df = arguments.length > 0 ? + new SimpleDateFormat (arguments[0].toString ()) : + new SimpleDateFormat (); + df.setTimeZone(TimeZone.getDefault()); + return (aDate.toJavaObject() == null) ? + new ESString(""): + new ESString(df.format((Date) aDate.toJavaObject())); + } + } + + // previously in FESI.Data.NumberObject + private Hashtable formatTable = new Hashtable (); + class NumberPrototypeFormat extends BuiltinFunctionObject { + NumberPrototypeFormat(String name, Evaluator evaluator, FunctionPrototype fp) { + super(fp, evaluator, name, 0); + } + public ESValue callFunction(ESObject thisObject, + ESValue[] arguments) + throws EcmaScriptException { + ESObject aNumber = (ESObject) thisObject; + String fstr = "#,##0.00"; + if (arguments.length >= 1) + fstr = arguments[0].toString (); + DecimalFormat df = (DecimalFormat) formatTable.get (fstr); + if (df == null) { + df = new DecimalFormat (fstr); + formatTable.put (fstr, df); + } + return new ESString(df.format(aNumber.doubleValue ())); + } + } + + class NodeChooser extends BuiltinFunctionObject { + NodeChooser (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode esn = (ESNode) thisObject; + if (arguments.length < 1) { + return ESBoolean.makeBoolean(false); + } + String nodename = arguments[0].toString (); + INode target = esn.getNode ().getNode (nodename, false); + ESNode collection = arguments.length > 1 ? (ESNode) arguments[1] : esn; + if (arguments.length > 2) + return new ESString (getNodeChooserDD (nodename, collection.getNode (), target, arguments[2].toString ())); + else + return new ESString (getNodeChooserRB (nodename, collection.getNode (), target)); + } + } + + class MultiNodeChooser extends BuiltinFunctionObject { + MultiNodeChooser (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode esn = (ESNode) thisObject; + if (arguments.length < 1) { + return ESBoolean.makeBoolean(false); + } + String nodename = arguments[0].toString (); + INode thisNode = esn.getNode (); + INode target = thisNode.getNode (nodename, false); + if (target == null) { + target = thisNode.createNode (nodename); + } + ESNode collection = arguments.length > 1 ? (ESNode) arguments[1] : esn; + return new ESString (getNodeChooserCB (nodename, collection.getNode (), target)); + } + } + + class UserLogin extends BuiltinFunctionObject { + UserLogin (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + if (arguments.length < 2) + return ESBoolean.makeBoolean(false); + ESUser u = (ESUser) thisObject; + if (u.user == null) + throw new EcmaScriptException ("login() can only be called for user objects that are online at the moment!"); + boolean success = app.loginUser (arguments[0].toString (), arguments[1].toString (), u); + try { + u.doIndirectCall (this.evaluator, u, "onLogin", new ESValue[0]); + } catch (Exception nosuch) {} + return ESBoolean.makeBoolean (success); + } + } + + class UserRegister extends BuiltinFunctionObject { + RequestEvaluator reval; + UserRegister (String name, Evaluator evaluator, FunctionPrototype fp, RequestEvaluator reval) { + super (fp, evaluator, name, 2); + this.reval = reval; + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + if (arguments.length < 2) + return ESBoolean.makeBoolean(false); + INode unode = app.registerUser (arguments[0].toString (), arguments[1].toString ()); + if (unode == null) + return ESNull.theNull; + else + return reval.getNodeWrapper (unode); + } + } + + class UserLogout extends BuiltinFunctionObject { + UserLogout (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESUser u = (ESUser) thisObject; + if (u.user == null) + return ESBoolean.makeBoolean (true); + try { + u.doIndirectCall (this.evaluator, u, "onLogout", new ESValue[0]); + } catch (Exception nosuch) {} + return ESBoolean.makeBoolean (app.logoutUser (u)); + } + } + + class UserOnSince extends BuiltinFunctionObject { + UserOnSince (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESUser u = (ESUser) thisObject; + if (u.user == null) + throw new EcmaScriptException ("onSince() can only be called for users that are online at the moment!"); + DatePrototype date = new DatePrototype(this.evaluator, new Date (u.user.onSince)); + return date; + } + } + + class UserLastActive extends BuiltinFunctionObject { + UserLastActive (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESUser u = (ESUser) thisObject; + if (u.user == null) + throw new EcmaScriptException ("lastActive() can only be called for users that are online at the moment!"); + DatePrototype date = new DatePrototype(this.evaluator, new Date (u.user.lastTouched)); + return date; + } + } + + class UserTouch extends BuiltinFunctionObject { + UserTouch (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESUser u = (ESUser) thisObject; + if (u.user != null) + u.user.touch (); + return ESNull.theNull; + } + } + + class GlobalGetProperty extends BuiltinFunctionObject { + GlobalGetProperty (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + if (arguments.length == 0) + return new ESString (""); + String defval = (arguments.length > 1) ? arguments[1].toString () : ""; + return new ESString (app.props.getProperty (arguments[0].toString (), defval)); + + } + } + + class GlobalGetUser extends BuiltinFunctionObject { + RequestEvaluator reval; + GlobalGetUser (String name, Evaluator evaluator, FunctionPrototype fp, RequestEvaluator reval) { + super (fp, evaluator, name, 1); + this.reval = reval; + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + INode user = null; + if (arguments.length > 0) { + String uname = arguments[0].toString ().trim (); + user = app.getUserNode (uname); + } + if (user == null) + return ESNull.theNull; + return reval.getNodeWrapper (user); + } + } + + class GlobalGetUserBySession extends BuiltinFunctionObject { + RequestEvaluator reval; + GlobalGetUserBySession (String name, Evaluator evaluator, FunctionPrototype fp, RequestEvaluator reval) { + super (fp, evaluator, name, 1); + this.reval = reval; + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + User user = null; + if (arguments.length > 0) { + String sid = arguments[0].toString ().trim (); + user = app.getUser (sid); + } + if (user == null || user.uid == null) + return ESNull.theNull; + user.touch (); + return reval.getNodeWrapper (user.getNode ()); + } + } + + + class GlobalGetAllUsers extends BuiltinFunctionObject { + RequestEvaluator reval; + GlobalGetAllUsers (String name, Evaluator evaluator, FunctionPrototype fp, RequestEvaluator reval) { + super (fp, evaluator, name, 1); + this.reval = reval; + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + INode users = app.getUserRoot (); + ESObject ap = this.evaluator.getArrayPrototype(); + ArrayPrototype theArray = new ArrayPrototype(ap, this.evaluator); + int i=0; + for (Enumeration e=users.properties (); e.hasMoreElements (); ) { + IProperty prop = (IProperty) e.nextElement (); + if (!"prototype".equals (prop.getName ()) && prop.getType () == IProperty.NODE) + theArray.putProperty (i++, reval.getNodeWrapper (prop.getNodeValue ())); + } + return theArray; + } + } + + class GlobalGetActiveUsers extends BuiltinFunctionObject { + RequestEvaluator reval; + GlobalGetActiveUsers (String name, Evaluator evaluator, FunctionPrototype fp, RequestEvaluator reval) { + super (fp, evaluator, name, 1); + this.reval = reval; + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + Hashtable sessions = (Hashtable) app.sessions.clone (); + int l = sessions.size (); + ESObject ap = this.evaluator.getArrayPrototype(); + ArrayPrototype theArray = new ArrayPrototype (ap, this.evaluator); + theArray.setSize(l); + int i=0; + Hashtable visited = new Hashtable (); + for (Enumeration e=sessions.elements(); e.hasMoreElements(); ) { + User u = (User) e.nextElement (); + if (u.uid == null || !visited.containsKey (u.uid)) { + theArray.setElementAt (reval.getNodeWrapper (u.getNode ()), i++); + if (u.uid != null) visited.put (u.uid, u); + } + } + return theArray; + } + } + + class GlobalIsActive extends BuiltinFunctionObject { + GlobalIsActive (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + if (arguments.length < 1) + return ESBoolean.makeBoolean (false); + String username = null; + boolean active = false; + if (arguments[0] instanceof ESUser) { + ESUser esu = (ESUser) arguments[0]; + active = (esu.user != null); + } else { + active = app.activeUsers.contains (arguments[0].toString ()); + } + return ESBoolean.makeBoolean (active); + } + } + + class GlobalGetAge extends BuiltinFunctionObject { + GlobalGetAge (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + if (arguments.length != 1 || !(arguments[0] instanceof DatePrototype)) + throw new EcmaScriptException ("Invalid arguments for function getAge(Date)"); + + Date d = (Date) arguments[0].toJavaObject (); + try { + long then = d.getTime () / 60000l; + long now = System.currentTimeMillis () / 60000l; + StringBuffer age = new StringBuffer (); + String divider = "vor "; + long diff = now - then; + long days = diff / 1440; + if (days > 0) { + age.append (days > 1 ? divider+days+ " Tagen" : divider+"1 Tag"); + divider = ", "; + } + long hours = (diff % 1440) / 60; + if (hours > 0) { + age.append (hours > 1 ? divider+hours+ " Stunden" : divider+"1 Stunde"); + divider = ", "; + } + long minutes = diff % 60; + if (minutes > 0) + age.append (minutes > 1 ? divider+minutes+ " Minuten" : divider+"1 Minute"); + return new ESString (age.toString ()); + + } catch (Exception e) { + IServer.getLogger().log ("Error formatting date: "+e); + e.printStackTrace (); + return new ESString (""); + } + } + } + + class GlobalGetURL extends BuiltinFunctionObject { + GlobalGetURL (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + if (arguments.length < 1) + return ESNull.theNull; + try { + StringBuffer urltext = new StringBuffer (); + java.net.URL url = new java.net.URL (arguments[0].toString ()); + BufferedReader in = new BufferedReader (new InputStreamReader (url.openConnection ().getInputStream ())); + char[] c = new char[1024]; + int read = -1; + while ((read = in.read (c)) > -1) + urltext.append (c, 0, read); + in.close (); + return new ESString (urltext.toString ()); + } catch (Exception ignore) {} + return ESNull.theNull; + } + } + + class GlobalEncode extends BuiltinFunctionObject { + GlobalEncode (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + if (arguments.length < 1) + return ESNull.theNull; + return new ESString (HtmlEncoder.encodeAll (arguments[0].toString ())); + } + } + + class GlobalEncodeXml extends BuiltinFunctionObject { + GlobalEncodeXml (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + if (arguments.length < 1) + return ESNull.theNull; + return new ESString (HtmlEncoder.encodeXml (arguments[0].toString ())); + } + } + + + class GlobalFormat extends BuiltinFunctionObject { + GlobalFormat (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + if (arguments.length < 1) + return ESNull.theNull; + return new ESString (HtmlEncoder.encode (arguments[0].toString ())); + } + } + + class GlobalGetXmlDocument extends BuiltinFunctionObject { + GlobalGetXmlDocument (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + try { + // Class.forName ("org.apache.xerces.parsers.DOMParser"); + // org.apache.xerces.parsers.DOMParser parser = new org.apache.xerces.parsers.DOMParser(); + Class.forName ("org.openxml.parser.XMLParser"); + org.openxml.parser.XMLParser parser = new org.openxml.parser.XMLParser(); + Object p = arguments[0].toJavaObject (); + if (p instanceof String) try { + // first try to interpret string as URL + java.net.URL u = new java.net.URL (p.toString ()); + parser.parse (p.toString()); + } catch (java.net.MalformedURLException nourl) { + // if not a URL, maybe it is the XML itself + parser.parse (new InputSource (new StringReader (p.toString()))); + } + else if (p instanceof InputStream) + parser.parse (new InputSource ((InputStream) p)); + else if (p instanceof Reader) + parser.parse (new InputSource ((Reader) p)); + return ESLoader.normalizeObject (parser.getDocument(), evaluator); + } catch (Exception noluck) { + IServer.getLogger().log ("Error creating XML document: "+noluck); + } + return ESNull.theNull; + } + } + + class GlobalGetHtmlDocument extends BuiltinFunctionObject { + GlobalGetHtmlDocument (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + try { + Class.forName ("org.openxml.parser.HTMLParser"); + org.openxml.parser.HTMLParser parser = new org.openxml.parser.HTMLParser(); + Object p = arguments[0].toJavaObject (); + if (p instanceof String) try { + // first try to interpret string as URL + java.net.URL u = new java.net.URL (p.toString ()); + parser.parse (p.toString()); + } catch (java.net.MalformedURLException nourl) { + // if not a URL, maybe it is the HTML itself + parser.parse (new InputSource (new StringReader (p.toString()))); + } + else if (p instanceof InputStream) + parser.parse (new InputSource ((InputStream) p)); + else if (p instanceof Reader) + parser.parse (new InputSource ((Reader) p)); + return ESLoader.normalizeObject (parser.getDocument(), evaluator); + } catch (Exception noluck) { + IServer.getLogger().log ("Error creating HTML document: "+noluck); + } + return ESNull.theNull; + } + } + + class GlobalJDOM extends BuiltinFunctionObject { + GlobalJDOM (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + try { + Class.forName ("org.w3c.dom.Document"); + org.w3c.dom.Document doc = (org.w3c.dom.Document) arguments[0].toJavaObject (); + Class.forName ("org.jdom.input.DOMBuilder"); + org.jdom.input.DOMBuilder builder = new org.jdom.input.DOMBuilder (); + return ESLoader.normalizeObject (builder.build (doc), evaluator); + } catch (Exception noluck) { + IServer.getLogger().log ("Error wrapping JDOM document: "+noluck); + } + return ESNull.theNull; + } + } + + + class NodePath extends BuiltinFunctionObject { + NodePath (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + INode n = ((ESNode) thisObject).getNode (); + String tmpname = arguments[0].toString (); + return new ESString (app.getNodePath (n, tmpname)); + } + } + + class NodeSetParent extends BuiltinFunctionObject { + NodeSetParent (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 2); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode node = (ESNode) thisObject; + return ESBoolean.makeBoolean (node.setParent (arguments)); + } + } + + + class NodeHref extends BuiltinFunctionObject { + NodeHref (String name, Evaluator evaluator, FunctionPrototype fp) { + super (fp, evaluator, name, 1); + } + public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + INode n = ((ESNode) thisObject).getNode (); + String tmpname = arguments.length == 0 ? "" : arguments[0].toString (); + return new ESString (app.getNodeHref (n, tmpname)); + + } + } + + + private String getNumberChoice (String name, double from, double to, double step, double value) { + double l = 0.000001; + StringBuffer b = new StringBuffer (""); + return b.toString (); + } + + private String getNodeChooserDD (String name, INode collection, INode target, String teaser) { + StringBuffer buffer = new StringBuffer (""); + return buffer.toString (); + } + + private String getNodeChooserRB (String name, INode collection, INode target) { + StringBuffer buffer = new StringBuffer (); + if (collection != null) { + int l = collection.numberOfNodes (); + for (int i=0; i"); + String cname = next.getString ("name", false); + if (cname == null) cname = next.getName (); + buffer.append (HtmlEncoder.encodeAll (cname)); + buffer.append ("
"); + } + } + return buffer.toString (); + } + + private String getNodeChooserCB (String name, INode collection, INode target) { + StringBuffer buffer = new StringBuffer (); + if (collection != null) { + int l = collection.numberOfNodes (); + for (int i=0; i"); + buffer.append (HtmlEncoder.encodeAll (next.getName ())); + buffer.append ("
"); + } + } + return buffer.toString (); + } + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/framework/core/NodeConstructor.java b/src/helma/framework/core/NodeConstructor.java new file mode 100644 index 00000000..112d3a71 --- /dev/null +++ b/src/helma/framework/core/NodeConstructor.java @@ -0,0 +1,81 @@ +// NodeConstructor.java +// Copyright (c) Hannes Wallnöfer 2000 + +package helma.framework.core; + +import FESI.Data.*; +import FESI.Exceptions.*; +import FESI.Interpreter.*; + +/** + * A constructor for user defined data types. This first constructs a node, sets its prototype + * and invokes the scripted constructor function on it. + */ + +public class NodeConstructor extends BuiltinFunctionObject { + + RequestEvaluator reval; + String typename; + + public NodeConstructor (String name, FunctionPrototype fp, RequestEvaluator reval) { + super(fp, reval.evaluator, name, 1); + typename = name; + this.reval = reval; + } + + public ESValue callFunction(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + return doConstruct(thisObject, arguments); + } + + public ESObject doConstruct(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException { + ESNode node = null; + if ("Node".equals (typename)) { + if (arguments.length == 0) { + node = new ESNode (reval.esNodePrototype, this.evaluator, null, reval); + reval.objectcache.put (node.getNode (), node); + } else { + node = new ESNode (reval.esNodePrototype, this.evaluator, arguments[0], reval); + reval.objectcache.put (node.getNode (), node); + } + } else { + // Typed nodes are instantiated as helma.objectmodel.db.Node from the beginning + // even if we don't know yet if they are going to be stored in a database. The reason + // is that we want to be able to use the specail features like subnode relations even for + // transient nodes. + ObjectPrototype op = reval.getPrototype (typename); + node = new ESNode (op, reval.evaluator, typename, reval); + node.setPrototype (typename); + node.getNode ().setDbMapping (reval.app.getDbMapping (typename)); + try { + // first try calling "constructor", if that doesn't work, try calling a function + // with the name of the type. + // HACK: There is an incompatibility problem here, because the property + // constructor is defined as the constructor of the object by EcmaScript. + if (op.getProperty("constructor", "constructor".hashCode()) instanceof ConstructedFunctionObject) + node.doIndirectCall (reval.evaluator, node, "constructor", arguments); + else + node.doIndirectCall (reval.evaluator, node, typename, arguments); + } catch (Exception ignore) {} + } + return node; + } + + public ESValue getPropertyInScope(String propertyName, ScopeChain previousScope, int hash) throws EcmaScriptException { + return super.getPropertyInScope (propertyName, previousScope, hash); + } + + public ESValue getProperty(String propertyName, int hash) throws EcmaScriptException { + if ("prototype".equals (propertyName)) + return reval.getPrototype (typename); + return super.getProperty(propertyName, hash); + } + + public String[] getSpecialPropertyNames() { + String ns[] = {}; + return ns; + } + + } // class NodeConstructor + + + diff --git a/src/helma/framework/core/Prototype.java b/src/helma/framework/core/Prototype.java new file mode 100644 index 00000000..b7419d06 --- /dev/null +++ b/src/helma/framework/core/Prototype.java @@ -0,0 +1,172 @@ +// Prototype.java +// Copyright (c) Hannes Wallnöfer 1998-2000 + +package helma.framework.core; + +import java.util.*; +import java.io.*; +import helma.framework.*; +import helma.objectmodel.*; + + +/** + * The Prototype class represents JavaScript prototypes defined in HOP + * applications. This manages a prototypes templates, functions and actions + * as well as optional information about the mapping of this type to a + * relational database table. + */ + + +public class Prototype { + + String id; + String name; + Application app; + Hashtable templates, functions, actions; + File codeDir; + long lastUpdate; + + DbMapping dbmap; + + Prototype prototype; + + + public Prototype (File codeDir, Application app) { + + IServer.getLogger().log ("Constructing Prototype "+app.getName()+"/"+codeDir.getName ()); + + this.codeDir = codeDir; + this.app = app; + this.name = codeDir.getName (); + + File propfile = new File (codeDir, "type.properties"); + SystemProperties props = new SystemProperties (propfile.getAbsolutePath ()); + dbmap = new DbMapping (app, name, props); + + lastUpdate = System.currentTimeMillis (); + + } + + + public Action getActionOrTemplate (String aname) { + + Action retval = null; + if (aname == null || "".equals (aname)) + aname = "main"; + retval = (Action) actions.get (aname); + // check if it's allowed to access templates via URL + // this should be cached in the future + if (retval == null && "true".equalsIgnoreCase (app.props.getProperty ("exposetemplates"))) + retval = (Action) templates.get (aname); + // if still not found, check if the action is defined for the generic node prototype + if (retval == null && this != app.typemgr.nodeProto && app.typemgr.nodeProto != null) + retval = app.typemgr.nodeProto.getActionOrTemplate (aname); + return retval; + } + + public void setPrototype (Prototype prototype) { + this.prototype = prototype; + } + + public Prototype getPrototype () { + return prototype; + } + + public Template getTemplate (String tmpname) { + return (Template) templates.get (tmpname); + } + + public FunctionFile getFunctionFile (String ffname) { + return (FunctionFile) functions.get (ffname); + } + + public Action getAction (String afname) { + return (Action) actions.get (afname); + } + + public File getCodeDir () { + return codeDir; + } + + public synchronized boolean checkCodeDir () { + + boolean retval = false; + String[] list = codeDir.list (); + + for (int i=0; i lastUpdate) { + lastUpdate = System.currentTimeMillis (); + try { + app.typemgr.updatePrototype (this.name, codeDir, this); + // TypeManager.broadcaster.broadcast ("Finished update for prototype "+name+" @ "+new Date ()+"


"); + } catch (Exception x) { + IServer.getLogger().log ("Error building function protos in prototype: "+x); + // TypeManager.broadcaster.broadcast ("Error updating prototype "+name+" in application "+app.getName()+":
"+x.getMessage ()+"

"); + } + retval = true; + } + } + } + return retval; + } + + + public String getName () { + return name; + } + + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/framework/core/RequestEvaluator.java b/src/helma/framework/core/RequestEvaluator.java new file mode 100644 index 00000000..273826e4 --- /dev/null +++ b/src/helma/framework/core/RequestEvaluator.java @@ -0,0 +1,850 @@ +// RequestEvaluator.java +// Copyright (c) Hannes Wallnöfer 1998-2000 + +package helma.framework.core; + +import java.util.*; +import java.io.*; +import helma.objectmodel.*; +import helma.objectmodel.db.Transactor; +import helma.framework.*; +import helma.framework.extensions.*; +import helma.xmlrpc.fesi.*; +import helma.util.*; +import Acme.LruHashtable; +import FESI.Data.*; +import FESI.Interpreter.*; +import FESI.Exceptions.*; + +/** + * This class does the work for incoming requests. It holds a transactor thread + * and an EcmaScript evaluator to get the work done. Incoming threads are + * blocked until the request has been serviced by the evaluator, or the timeout + * specified by the application has passed. In the latter case, the evaluator thread + * is killed and an error message is returned. + */ + +public class RequestEvaluator implements Runnable { + + + Application app; + + RequestTrans req; + ResponseTrans res; + + volatile Transactor rtx; + + String method; + ESObject current; + User user; + Vector args; + ESValue[] esargs; + ESValue esresult; + Object result; + Exception exception; + private ArrayPrototype reqPath; + private ESRequestData reqData; + + // vars for FESI EcmaScript support + protected Evaluator evaluator; + protected ObjectPrototype esNodePrototype; + protected ObjectPrototype esUserPrototype; + protected LruHashtable objectcache; + protected Hashtable prototypes; + + GlobalObject global; + HopExtension hopx; + MailExtension mailx; + FesiRpcServer xmlrpc; + ESAppNode appnode; + static String[] extensions = new String[] { + "FESI.Extensions.BasicIO", + "FESI.Extensions.FileIO", + "helma.xmlrpc.fesi.FesiRpcExtension", + "helma.framework.extensions.ImageExtension", + "helma.framework.extensions.FtpExtension", + "helma.framework.extensions.Database", + "FESI.Extensions.JavaAccess", + "FESI.Extensions.OptionalRegExp"}; + + // the type of request to be serviced + int reqtype; + static final int NONE = 0; // no request + static final int HTTP = 1; // via HTTP gateway + static final int XMLRPC = 2; // via XML-RPC + static final int INTERNAL = 3; // generic function call, e.g. by scheduler + + INode root, userroot, currentNode; + + /** + * Build a RenderContext from a RequestTrans. Checks if the path is the user home node ("user") + * or a subnode of it. Otherwise, the data root of the site is used. Two arrays are built, one + * that contains the data nodes, and anotherone with the corresponding Prototypes or Prototype.Parts. + */ + public RequestEvaluator (Application app) { + this.app = app; + this.objectcache = new LruHashtable (100, .80f); + this.prototypes = new Hashtable (); + initEvaluator (); + startThread (); + } + + + // init Script Evaluator + private void initEvaluator () { + try { + evaluator = new Evaluator(); + global = evaluator.getGlobalObject(); + for (int i=0; i 1) { + next = st.nextToken (); + if ("user".equalsIgnoreCase (next)) { + currentNode = user.getNode (); + ntokens -=1; + if (currentNode != null) + reqPath.putProperty (1, getNodeWrapper (currentNode)); + } else if ("users".equalsIgnoreCase (next)) { + currentNode = app.getUserRoot (); + ntokens -=1; + isProperty = true; + } else { + currentNode = currentNode.getSubnode (next); + ntokens -=1; + if (currentNode != null) + reqPath.putProperty (1, getNodeWrapper (currentNode)); + } + } + + for (int i=1; i 20) throw new RuntimeException ("Path too deep"); + } + + if (currentNode == null) + throw new FrameworkException ("Object not found."); + + Prototype p = app.getPrototype (currentNode); + next = st.nextToken (); + if (p != null) + action = p.getActionOrTemplate (next); + + if (p == null || action == null) { + currentNode = currentNode.getSubnode (next); + if (currentNode == null) + throw new FrameworkException ("Object or Action '"+next+"' not found."); + p = app.getPrototype (currentNode); + // add to reqPath array + if (currentNode != null && currentNode.getState() != INode.VIRTUAL) + reqPath.putProperty (reqPath.size(), getNodeWrapper (currentNode)); + if (p != null) + action = p.getActionOrTemplate ("main"); + } + + current = getNodeWrapper (currentNode); + + } + + if (action == null) + throw new FrameworkException ("Action not found"); + + } catch (FrameworkException notfound) { + // The path could not be resolved. Check if there is a "not found" action + // specified in the property file. + String notFoundAction = app.props.getProperty ("notFound", "notfound"); + Prototype p = app.getPrototype (root); + action = p.getActionOrTemplate (notFoundAction); + if (action == null) + throw new FrameworkException (notfound.getMessage ()); + current = getNodeWrapper (root); + } + + rtx.timer.endEvent (requestPath+" init"); + + try { + rtx.timer.beginEvent (requestPath+" execute"); + current.doIndirectCall (evaluator, current, action.getFunctionName (), new ESValue[0]); + rtx.timer.endEvent (requestPath+" execute"); + done = true; + } catch (RedirectException redirect) { + res.redirect = redirect.getMessage (); + done = true; + } + + rtx.commit (); + done = true; + // if (app.debug) + // ((Transactor) Thread.currentThread ()).timer.dump (System.err); + + } catch (ConcurrencyException x) { + try { rtx.abort (); } catch (Exception ignore) {} + res.reset (); + if (++tries < 8) { + try { + // wait a bit longer with each try + int base = 500 * tries; + Thread.currentThread ().sleep ((long) (base + Math.random ()*base*2)); + } catch (Exception ignore) {} + continue; + } + app.errorCount += 1; + res.write ("Error in application '"+app.getName()+"':

Couldn't complete transaction due to heavy object traffic (tried "+tries+" times).
"); + done = true; + + } catch (FrameworkException x) { + try { rtx.abort (); } catch (Exception ignore) {} + app.errorCount += 1; + res.reset (); + res.write ("Error in application '"+app.getName()+"':

" + x.getMessage () + "
"); + if (app.debug) + x.printStackTrace (); + + done = true; + } catch (Exception x) { + try { rtx.abort (); } catch (Exception ignore) {} + app.errorCount += 1; + System.err.println ("### Exception in "+app.getName()+"/"+req.path+": current = "+currentNode); + System.err.println (x); + // Dump the profiling data to System.err + ((Transactor) Thread.currentThread ()).timer.dump (System.err); + if (app.debug) + x.printStackTrace (); + + // If the transactor thread has been killed by the invoker thread we don't have to + // bother for the error message, just quit. + if (rtx != Thread.currentThread()) + return; + + res.reset (); + res.write ("Error in application '"+app.getName()+"':

" + x.getMessage () + "
"); + done = true; + } + } + break; + case XMLRPC: + try { + rtx.begin (app.getName()+":xmlrpc/"+method); + + root = app.getDataRoot (); + + global.putHiddenProperty ("root", getNodeWrapper (root)); + global.deleteProperty("user", "user".hashCode()); + global.deleteProperty ("req", "req".hashCode()); + // global.deleteProperty ("res", "res".hashCode()); + global.putHiddenProperty ("res", ESLoader.normalizeValue(new ResponseTrans (), evaluator)); + global.deleteProperty ("path", "path".hashCode()); + global.putHiddenProperty ("app", appnode); + + // convert arguments + int l = args.size (); + current = getNodeWrapper (root); + if (method.indexOf (".") > -1) { + StringTokenizer st = new StringTokenizer (method, "."); + int cnt = st.countTokens (); + for (int i=1; i 1000) { + rtx.cleanup (); // close sql connections held by this thread + startThread (); + } + } + + // IServer.getLogger().log (this+ " exiting."); + } + + public synchronized ResponseTrans invoke (RequestTrans req, User user) throws Exception { + checkThread (); + this.reqtype = HTTP; + this.req = req; + this.user = user; + this.res = new ResponseTrans (); + + notifyAll (); + wait (app.requestTimeout); + if (reqtype > 0) { + IServer.getLogger().log ("Stopping Thread for Request "+app.getName()+"/"+req.path); + evaluator.thread = null; + rtx.kill (); + res.reset (); + res.write ("Error in application '"+app.getName()+"':

Request timed out.
"); + rtx = new Transactor (this, app.nmgr); + evaluator.thread = rtx; + rtx.start (); + Thread.yield (); + } + + return res; + } + + + public synchronized Object invokeXmlRpc (String method, Vector args) throws Exception { + checkThread (); + this.reqtype = XMLRPC; + this.user = null; + this.method = method; + this.args = args; + result = null; + exception = null; + + notifyAll (); + wait (app.requestTimeout); + if (reqtype > 0) { + IServer.getLogger().log ("Stopping Thread"); + evaluator.thread = null; + rtx.kill (); + rtx = new Transactor (this, app.nmgr); + evaluator.thread = rtx; + rtx.start (); + Thread.yield (); + } + + if (exception != null) + throw (exception); + return result; + } + + public synchronized ESValue invokeFunction (INode node, String functionName, ESValue[] args) + throws Exception { + ESObject obj = null; + if (node == null) + obj = global; + else + obj = getNodeWrapper (node); + return invokeFunction (obj, functionName, args); + } + + public synchronized ESValue invokeFunction (ESObject obj, String functionName, ESValue[] args) + throws Exception { + checkThread (); + this.reqtype = INTERNAL; + this.user = null; + this.current = obj; + this.method = functionName; + this.esargs = args; + esresult = ESNull.theNull; + exception = null; + + notifyAll (); + wait (60000l*15); // give internal call more time (15 minutes) to complete + + if (reqtype > 0) { + IServer.getLogger().log ("Stopping Thread"); + evaluator.thread = null; + rtx.kill (); + rtx = new Transactor (this, app.nmgr); + evaluator.thread = rtx; + rtx.start (); + Thread.yield (); + } + + if (exception != null) + throw (exception); + return esresult; + } + + public synchronized ESValue invokeFunction (User user, String functionName, ESValue[] args) + throws Exception { + checkThread (); + this.reqtype = INTERNAL; + this.user = user; + this.current = null; + this.method = functionName; + this.esargs = args; + esresult = ESNull.theNull; + exception = null; + + notifyAll (); + wait (app.requestTimeout); + + if (reqtype > 0) { + IServer.getLogger().log ("Stopping Thread"); + evaluator.thread = null; + rtx.kill (); + rtx = new Transactor (this, app.nmgr); + evaluator.thread = rtx; + rtx.start (); + Thread.yield (); + } + + if (exception != null) + throw (exception); + return esresult; + } + + + /** + * Stop this request evaluator. If currently active kill the request, otherwise just + * notify. + */ + public synchronized void stop () { + evaluator.thread = null; + if (reqtype != NONE) { + rtx.kill (); + } else { + notifyAll (); + } + } + + private void checkThread () { + if (rtx == null || !rtx.isAlive()) { + rtx = new Transactor (this, app.nmgr); + evaluator.thread = rtx; + rtx.start (); + Thread.yield (); + } + } + + + /** + * Returns a node wrapper only if it already exists in the cache table. This is used + * in those places when wrappers have to be updated if they already exist. + */ + public ESNode getNodeWrapperFromCache (INode n) { + return n == null ? null : (ESNode) objectcache.get (n); + } + + public ESNode getNodeWrapper (INode n) { + + if (n == null) + return null; + + ESNode esn = (ESNode) objectcache.get (n); + + if (esn == null || esn.getNode() != n) { + ObjectPrototype op = null; + String protoname = n.getString ("prototype", false); + + // set the DbMapping of the node according to its prototype. + // this *should* be done on the objectmodel level, but isn't currently + // for embedded nodes since there's not enough type info at the objectmodel level + // for those nodes. + if (protoname != null && protoname.length() > 0 && n.getDbMapping () == null) { + n.setDbMapping (app.getDbMapping (protoname)); + } + + try { + op = (ObjectPrototype) prototypes.get (protoname); + } catch (Exception ignore) {} + + if (op == null) + op = esNodePrototype; // no prototype found for this node. + + if ("user".equalsIgnoreCase (protoname)) + esn = new ESUser (n, this); + else + esn = new ESNode (op, evaluator, n, this); + + objectcache.put (n, esn); + // IServer.getLogger().log ("Wrapper for "+n+" created"); + } + + return esn; + } + + public ObjectPrototype getPrototype (String protoName) { + return (ObjectPrototype) prototypes.get (protoName); + } + + public void putPrototype (String protoName, ObjectPrototype op) { + prototypes.put (protoName, op); + } + + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/framework/core/Template.java b/src/helma/framework/core/Template.java new file mode 100644 index 00000000..90cb4748 --- /dev/null +++ b/src/helma/framework/core/Template.java @@ -0,0 +1,214 @@ +// Template.java +// Copyright (c) Hannes Wallnöfer 1998-2000 + +package helma.framework.core; + +import java.util.*; +import java.io.*; +import helma.framework.*; +import FESI.Data.*; + + +/** + * This represents a HOP template, i.e. a file with the extension .hsp + * (HOP server page) that contains both parts that are to be evaluated + * as EcmaScript and parts that are to be delivered to the client as-is. + * Internally, templates are regular functions. + * HOP templates are callable via URL, but this is just a leftover from the + * days when there were no .hac (action) files. The recommended way + * now is to have a .hac file with all the logic which in turn calls one or more + * template files to do the formatting. + */ + +public class Template extends Action { + + public Template (File file, String name, Prototype proto) { + super (file, name, proto); + } + + public void update (String content) throws Exception { + // IServer.getLogger().log ("Reading text template " + name); + + Vector partBuffer = new Vector (); + int l = content.length (); + char cnt[] = new char[l]; + content.getChars (0, l, cnt, 0); + + // if last charackter is whitespace, swallow it. this is necessary for some inner templates to look ok. + if (Character.isWhitespace (cnt[l-1])) + l -= 1; + + int lastIdx = 0; + for (int i = 0; i < l-1; i++) { + if (cnt[i] == '<' && cnt[i+1] == '%') { + int j = i+2; + while (j < l-1 && (cnt[j] != '%' || cnt[j+1] != '>')) { + j++; + } + if (j > i+2) { + if (i - lastIdx > 0) + partBuffer.addElement (new Part (this, new String (cnt, lastIdx, i - lastIdx), true)); + String script = new String (cnt, i+2, (j-i)-2); + partBuffer.addElement (new Part (this, script, false)); + lastIdx = j+2; + } + i = j+1; + } + } + if (lastIdx < l) + partBuffer.addElement (new Part (this, new String (cnt, lastIdx, l - lastIdx), true)); + + StringBuffer templateBody = new StringBuffer (); + int nparts = partBuffer.size(); + + for (int k = 0; k < nparts; k++) { + Part nextPart = (Part) partBuffer.elementAt (k); + + if (nextPart.isStatic || nextPart.content.trim ().startsWith ("=")) { + // check for <%= ... %> statements + if (!nextPart.isStatic) { + nextPart.content = nextPart.content.trim ().substring (1).trim (); + // cut trailing ";" + while (nextPart.content.endsWith (";")) + nextPart.content = nextPart.content.substring (0, nextPart.content.length()-1); + } + + StringTokenizer st = new StringTokenizer (nextPart.content, "\r\n", true); + String nextLine = st.hasMoreTokens () ? st.nextToken () : null; + + // count newLines we "swallow", see explanation below + int newLineCount = 0; + + templateBody.append ("res.write ("); + if (nextPart.isStatic) { + templateBody.append ("\""); + } + + while (nextLine != null) { + + if ("\n".equals (nextLine)) { + // append a CRLF + newLineCount++; + templateBody.append ("\\r\\n"); + } else if (!"\r".equals (nextLine)){ + StringReader lineReader = new StringReader (nextLine); + int c = lineReader.read (); + while (c > -1) { + if (nextPart.isStatic && ((char)c == '"' || (char)c == '\\')) { + templateBody.append ('\\'); + } + templateBody.append ((char) c); + c = lineReader.read (); + } + } + + nextLine = st.hasMoreTokens () ? st.nextToken () : null; + + } + + if (nextPart.isStatic) { + templateBody.append ("\""); + } + + templateBody.append ("); "); + + // append the number of lines we have "swallowed" into + // one write statement, so error messages will *approximately* + // give correct line numbers. + for (int i=0; i"+x+"

"); + } + + } else if (list[i].endsWith (app.scriptExtension) && tmpfile.length () > 0) { + try { + FunctionFile ff = new FunctionFile (tmpfile, tmpname, proto); + nfunc.put (tmpname, ff); + } catch (Throwable x) { + IServer.getLogger().log ("Error creating prototype: "+x); + // broadcaster.broadcast ("Error creating prototype "+list[i]+":
"+x+"

"); + } + } else if (list[i].endsWith (app.actionExtension) && tmpfile.length () > 0) { + try { + Action af = new Action (tmpfile, tmpname, proto); + nact.put (tmpname, af); + } catch (Throwable x) { + IServer.getLogger().log ("Error creating prototype: "+x); + // broadcaster.broadcast ("Error creating prototype "+list[i]+":
"+x+"

"); + } + } + } + proto.templates = ntemp; + proto.functions = nfunc; + proto.actions = nact; + } + + + public void updatePrototype (String name, File dir, Prototype proto) { + // IServer.getLogger().log ("updating prototype "+name); + + String list[] = dir.list(); + Hashtable ntemp = new Hashtable (); + Hashtable nfunc = new Hashtable (); + Hashtable nact = new Hashtable (); + + for (int i=0; i"+x+"

"); + } + ntemp.put (tmpname, t); + + } else if (list[i].endsWith (app.scriptExtension) && tmpfile.length () > 0) { + FunctionFile ff = proto.getFunctionFile (tmpname); + try { + if (ff == null) { + ff = new FunctionFile (tmpfile, tmpname, proto); + idleSeconds = 0; + } else if (ff.lastmod != tmpfile.lastModified ()) { + ff.update (tmpfile); + idleSeconds = 0; + } + } catch (Throwable x) { + IServer.getLogger().log ("Error updating prototype: "+x); + // broadcaster.broadcast ("Error updating prototype "+list[i]+":
"+x+"

"); + } + nfunc.put (tmpname, ff); + + } else if (list[i].endsWith (app.actionExtension) && tmpfile.length () > 0) { + Action af = proto.getAction (tmpname); + try { + if (af == null) { + af = new Action (tmpfile, tmpname, proto); + idleSeconds = 0; + } else if (af.lastmod != tmpfile.lastModified ()) { + af.update (tmpfile); + idleSeconds = 0; + } + } catch (Throwable x) { + IServer.getLogger().log ("Error updating prototype: "+x); + // broadcaster.broadcast ("Error updating prototype "+list[i]+":
"+x+"

"); + } + nact.put (tmpname, af); + + } else if ("type.properties".equalsIgnoreCase (list[i])) { + try { + if (proto.dbmap.read ()) { + idleSeconds = 0; + rewire = true; + } + } catch (Exception ignore) { + IServer.getLogger().log ("Error updating db mapping for type "+name+": "+ignore); + } + } + } + proto.templates = ntemp; + proto.functions = nfunc; + proto.actions = nact; + } + + protected void readFunctionFile (File f, String protoname) { + + EvaluationSource es = new FileEvaluationSource(f.getPath(), null); + FileReader fr = null; + + int size = app.allThreads.size (); + for (int i=0; i