diff --git a/src/helma/framework/core/Application.java b/src/helma/framework/core/Application.java index 79cc699e..a6ceeeec 100644 --- a/src/helma/framework/core/Application.java +++ b/src/helma/framework/core/Application.java @@ -300,8 +300,6 @@ public final class Application implements Runnable { dbSources = new Hashtable(); modules = new SystemMap(); - - cachenode = new TransientNode("app"); } /** @@ -455,6 +453,9 @@ public final class Application implements Runnable { nmgr = new NodeManager(Application.this); nmgr.init(dbDir.getAbsoluteFile(), props); + // create the app cache node exposed as app.data + cachenode = new Node("app", null, getWrappedNodeManager()); + // create and init session manager String sessionMgrImpl = props.getProperty("sessionManagerImpl", "helma.framework.core.SessionManager"); diff --git a/src/helma/framework/core/Session.java b/src/helma/framework/core/Session.java index 8bb84de4..4f720457 100644 --- a/src/helma/framework/core/Session.java +++ b/src/helma/framework/core/Session.java @@ -67,7 +67,7 @@ public class Session implements Serializable { this.app = app; this.uid = null; this.userHandle = null; - cacheNode = new TransientNode("session"); + cacheNode = new Node("session", null, app.getWrappedNodeManager()); onSince = System.currentTimeMillis(); lastTouched = lastModified = onSince; } diff --git a/src/helma/objectmodel/Property.java b/src/helma/objectmodel/Property.java deleted file mode 100644 index 916b71a3..00000000 --- a/src/helma/objectmodel/Property.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Helma License Notice - * - * The contents of this file are subject to the Helma License - * Version 2.0 (the "License"). You may not use this file except in - * compliance with the License. A copy of the License is available at - * http://adele.helma.org/download/helma/license.txt - * - * Copyright 1998-2003 Helma Software. All Rights Reserved. - * - * $RCSfile$ - * $Author$ - * $Revision$ - * $Date$ - */ - -package helma.objectmodel; - -import java.io.*; -import java.text.*; -import java.util.Date; - -/** - * A property implementation for Nodes stored inside a database. - */ -public final class Property implements IProperty, Serializable { - protected String propname; - protected TransientNode node; - public String svalue; - public boolean bvalue; - public long lvalue; - public double dvalue; - public INode nvalue; - public Object jvalue; - public int type; - - /** - * Creates a new Property object. - * - * @param node ... - */ - public Property(TransientNode node) { - this.node = node; - } - - /** - * Creates a new Property object. - * - * @param propname ... - * @param node ... - */ - public Property(String propname, TransientNode node) { - this.propname = propname; - this.node = node; - } - - /** - * - * - * @return ... - */ - public String getName() { - return propname; - } - - /** - * - * - * @return ... - */ - public Object getValue() { - switch (type) { - case STRING: - return svalue; - - case BOOLEAN: - return new Boolean(bvalue); - - case INTEGER: - return new Long(lvalue); - - case FLOAT: - return new Double(dvalue); - - case DATE: - return new Date(lvalue); - - case NODE: - return nvalue; - - case JAVAOBJECT: - return jvalue; - } - - return null; - } - - /** - * - * - * @param value ... - */ - public void setStringValue(String value) { - if (type == NODE) { - this.nvalue = null; - } - - if (type == JAVAOBJECT) { - this.jvalue = null; - } - - type = STRING; - this.svalue = value; - } - - /** - * - * - * @param value ... - */ - public void setIntegerValue(long value) { - if (type == NODE) { - this.nvalue = null; - } - - if (type == JAVAOBJECT) { - this.jvalue = null; - } - - type = INTEGER; - this.lvalue = value; - } - - /** - * - * - * @param value ... - */ - public void setFloatValue(double value) { - if (type == NODE) { - this.nvalue = null; - } - - if (type == JAVAOBJECT) { - this.jvalue = null; - } - - type = FLOAT; - this.dvalue = value; - } - - /** - * - * - * @param value ... - */ - public void setDateValue(Date value) { - if (type == NODE) { - this.nvalue = null; - } - - if (type == JAVAOBJECT) { - this.jvalue = null; - } - - type = DATE; - this.lvalue = value.getTime(); - } - - /** - * - * - * @param value ... - */ - public void setBooleanValue(boolean value) { - if (type == NODE) { - this.nvalue = null; - } - - if (type == JAVAOBJECT) { - this.jvalue = null; - } - - type = BOOLEAN; - this.bvalue = value; - } - - /** - * - * - * @param value ... - */ - public void setNodeValue(INode value) { - if (type == JAVAOBJECT) { - this.jvalue = null; - } - - type = NODE; - this.nvalue = value; - } - - /** - * - * - * @param value ... - */ - public void setJavaObjectValue(Object value) { - if (type == NODE) { - this.nvalue = null; - } - - type = JAVAOBJECT; - this.jvalue = value; - } - - /** - * - * - * @return ... - */ - public String getStringValue() { - switch (type) { - case STRING: - return svalue; - - case BOOLEAN: - return "" + bvalue; - - case DATE: - - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - return format.format(new Date(lvalue)); - - case INTEGER: - return Long.toString(lvalue); - - case FLOAT: - return Double.toString(dvalue); - - case NODE: - return nvalue.getName(); - - case JAVAOBJECT: - return (jvalue == null) ? null : jvalue.toString(); - } - - return ""; - } - - /** - * - * - * @return ... - */ - public String toString() { - return getStringValue(); - } - - /** - * - * - * @return ... - */ - public long getIntegerValue() { - if (type == INTEGER) { - return lvalue; - } - - return 0; - } - - /** - * - * - * @return ... - */ - public double getFloatValue() { - if (type == FLOAT) { - return dvalue; - } - - return 0.0; - } - - /** - * - * - * @return ... - */ - public Date getDateValue() { - if (type == DATE) { - return new Date(lvalue); - } - - return null; - } - - /** - * - * - * @return ... - */ - public boolean getBooleanValue() { - if (type == BOOLEAN) { - return bvalue; - } - - return false; - } - - /** - * - * - * @return ... - */ - public INode getNodeValue() { - if (type == NODE) { - return nvalue; - } - - return null; - } - - /** - * - * - * @return ... - */ - public Object getJavaObjectValue() { - if (type == JAVAOBJECT) { - return jvalue; - } - - return null; - } - - /** - * - * - * @return ... - */ - public int getType() { - return type; - } -} diff --git a/src/helma/objectmodel/TransientNode.java b/src/helma/objectmodel/TransientNode.java deleted file mode 100644 index ab5d199e..00000000 --- a/src/helma/objectmodel/TransientNode.java +++ /dev/null @@ -1,950 +0,0 @@ -/* - * Helma License Notice - * - * The contents of this file are subject to the Helma License - * Version 2.0 (the "License"). You may not use this file except in - * compliance with the License. A copy of the License is available at - * http://adele.helma.org/download/helma/license.txt - * - * Copyright 1998-2003 Helma Software. All Rights Reserved. - * - * $RCSfile$ - * $Author$ - * $Revision$ - * $Date$ - */ - -package helma.objectmodel; - -import helma.framework.IPathElement; -import helma.objectmodel.db.DbMapping; -import helma.objectmodel.db.Relation; -import helma.objectmodel.db.Node; -import helma.util.*; -import java.io.*; -import java.util.Date; -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.StringTokenizer; -import java.util.Vector; - -/** - * A transient implementation of INode. An instance of this class can't be - * made persistent by reachability from a persistent node. To make a persistent-capable - * object, class helma.objectmodel.db.Node has to be used. - */ -public class TransientNode implements INode, Serializable { - private static long idgen = 0; - protected Hashtable propMap; - protected Hashtable nodeMap; - protected Vector nodes; - protected TransientNode parent; - protected Vector links; // links to this node - protected Vector proplinks; // nodes using this node as property - transient String prototype; - protected long created; - protected long lastmodified; - protected String id; - protected String name; - - // is the main identity a named property or an anonymous node in a collection? - protected boolean anonymous = false; - transient DbMapping dbmap; - INode cacheNode; - - /** - * Creates a new TransientNode object. - */ - public TransientNode() { - id = generateID(); - name = id; - created = lastmodified = System.currentTimeMillis(); - } - - /** - * Make a new TransientNode object with a given name - */ - public TransientNode(String n) { - id = generateID(); - name = ((n == null) || "".equals(n)) ? id : n; - created = lastmodified = System.currentTimeMillis(); - } - - /** - * - * - * @return ... - */ - public static String generateID() { - // make transient ids differ from persistent ones - // and are unique within on runtime session - return "t" + idgen++; - } - - /** - * - * - * @param dbmap ... - */ - public void setDbMapping(DbMapping dbmap) { - this.dbmap = dbmap; - } - - /** - * - * - * @return ... - */ - public DbMapping getDbMapping() { - return dbmap; - } - - /** - * navigation-related - */ - public String getID() { - return id; - } - - /** - * - * - * @return ... - */ - public boolean isAnonymous() { - return anonymous; - } - - /** - * - * - * @return ... - */ - public String getName() { - return name; - } - - /** - * - * - * @return ... - */ - public String getElementName() { - return anonymous ? id : name; - } - - /** - * - * - * @return ... - */ - public int getState() { - return TRANSIENT; - } - - /** - * - * - * @param s ... - */ - public void setState(int s) { - // state always is TRANSIENT on this kind of node - } - - /** - * - * - * @return ... - */ - public String getFullName() { - return getFullName(null); - } - - /** - * - * - * @param root ... - * - * @return ... - */ - public String getFullName(INode root) { - String divider = null; - StringBuffer b = new StringBuffer(); - TransientNode p = this; - - while ((p != null) && (p.parent != null) && (p != root)) { - if (divider != null) { - b.insert(0, divider); - } else { - divider = "/"; - } - - b.insert(0, p.getElementName()); - p = p.parent; - } - - return b.toString(); - } - - /** - * - * - * @param name ... - */ - public void setName(String name) { - // if (name.indexOf('/') > -1) - // throw new RuntimeException ("The name of the node must not contain \"/\"."); - if ((name == null) || (name.trim().length() == 0)) { - this.name = id; - } else { - this.name = name; - } - } - - /** - * - * - * @return ... - */ - public String getPrototype() { - // if prototype is null, it's a vanilla HopObject. - if (prototype == null) { - return "hopobject"; - } - - return prototype; - } - - /** - * - * - * @param proto ... - */ - public void setPrototype(String proto) { - this.prototype = proto; - } - - /** - * - * - * @return ... - */ - public INode getParent() { - return parent; - } - - /** - * INode-related - */ - public void setSubnodeRelation(String rel) { - throw new RuntimeException("Can't set subnode relation for non-persistent Node."); - } - - /** - * - * - * @return ... - */ - public String getSubnodeRelation() { - return null; - } - - /** - * - * - * @return ... - */ - public int numberOfNodes() { - return (nodes == null) ? 0 : nodes.size(); - } - - /** - * - * - * @param elem ... - * - * @return ... - */ - public INode addNode(INode elem) { - return addNode(elem, numberOfNodes()); - } - - /** - * - * - * @param elem ... - * @param where ... - * - * @return ... - */ - public INode addNode(INode elem, int where) { - if ((where < 0) || (where > numberOfNodes())) { - where = numberOfNodes(); - } - - String n = elem.getName(); - - if (n.indexOf('/') > -1) { - throw new RuntimeException("The name of a node must not contain \"/\" (slash)."); - } - - // IServer.getLogger().log ("adding: "+node+" -- "+node.getContentLength ()); - if ((nodeMap != null) && (nodeMap.get(elem.getID()) != null)) { - nodes.removeElement(elem); - where = Math.min(where, numberOfNodes()); - nodes.insertElementAt(elem, where); - - return elem; - } - - if (nodeMap == null) { - nodeMap = new Hashtable(); - } - - if (nodes == null) { - nodes = new Vector(); - } - - nodeMap.put(elem.getID(), elem); - nodes.insertElementAt(elem, where); - - if (elem instanceof TransientNode) { - TransientNode node = (TransientNode) elem; - - if (node.parent == null) { - node.parent = this; - node.anonymous = true; - } - } - - lastmodified = System.currentTimeMillis(); - - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.SUBNODE_ADDED, node)); - return elem; - } - - /** - * - * - * @return ... - */ - public INode createNode() { - return createNode(null, 0); // where is ignored since this is an anonymous node - } - - /** - * - * - * @param where ... - * - * @return ... - */ - public INode createNode(int where) { - return createNode(null, where); - } - - /** - * - * - * @param nm ... - * - * @return ... - */ - public INode createNode(String nm) { - return createNode(nm, numberOfNodes()); // where is usually ignored (if nm != null) - } - - /** - * - * - * @param nm ... - * @param where ... - * - * @return ... - */ - public INode createNode(String nm, int where) { - boolean anon = false; - - if ((nm == null) || "".equals(nm.trim())) { - anon = true; - } - - INode n = new TransientNode(nm); - - if (anon) { - addNode(n, where); - } else { - setNode(nm, n); - } - - return n; - } - - /** - * register a node that links to this node. - */ - - /* protected void registerLink (TransientNode from) { - if (links == null) - links = new Vector (); - if (!links.contains (from)) - links.addElement (from); - } */ - public IPathElement getParentElement() { - return getParent(); - } - - /** - * - * - * @param name ... - * - * @return ... - */ - public IPathElement getChildElement(String name) { - return getNode(name); - } - - /** - * - * - * @param name ... - * - * @return ... - */ - public INode getSubnode(String name) { - StringTokenizer st = new StringTokenizer(name, "/"); - TransientNode retval = this; - TransientNode runner; - - while (st.hasMoreTokens() && (retval != null)) { - runner = retval; - - String next = st.nextToken().trim().toLowerCase(); - - if ("".equals(next)) { - retval = this; - } else { - retval = (runner.nodeMap == null) ? null - : (TransientNode) runner.nodeMap.get(next); - } - - if (retval == null) { - retval = (TransientNode) runner.getNode(next); - } - } - - return retval; - } - - /** - * - * - * @param index ... - * - * @return ... - */ - public INode getSubnodeAt(int index) { - return (nodes == null) ? null : (INode) nodes.elementAt(index); - } - - /** - * - * - * @param n ... - * - * @return ... - */ - public int contains(INode n) { - if ((n == null) || (nodes == null)) { - return -1; - } - - return nodes.indexOf(n); - } - - /** - * - * - * @return ... - */ - public boolean remove() { - if (anonymous) { - parent.unset(name); - } else { - parent.removeNode(this); - } - - return true; - } - - /** - * - * - * @param node ... - */ - public void removeNode(INode node) { - // IServer.getLogger().log ("removing: "+ node); - releaseNode(node); - - TransientNode n = (TransientNode) node; - - if ((n.getParent() == this) && n.anonymous) { - int l = (n.links == null) ? 0 : n.links.size(); // notify nodes that link to n that n is going down. - - for (int i = 0; i < l; i++) { - TransientNode link = (TransientNode) n.links.elementAt(i); - - link.releaseNode(n); - } - - if (n.proplinks != null) { - // clean up all nodes that use n as a property - for (Enumeration e1 = n.proplinks.elements(); e1.hasMoreElements();) - try { - Property p = (Property) e1.nextElement(); - - p.node.propMap.remove(p.propname.toLowerCase()); - } catch (Exception ignore) { - } - } - - // remove all subnodes, giving them a chance to destroy themselves. - Vector v = new Vector(); // removeElement modifies the Vector we are enumerating, so we are extra careful. - - for (Enumeration e3 = n.getSubnodes(); e3.hasMoreElements();) { - v.addElement(e3.nextElement()); - } - - int m = v.size(); - - for (int i = 0; i < m; i++) { - n.removeNode((TransientNode) v.elementAt(i)); - } - } else { - // - n.links.removeElement(this); - } - } - - /** - * "Physically" remove a subnode from the subnodes table. - * the logical stuff necessary for keeping data consistent is done elsewhere (in removeNode). - */ - protected void releaseNode(INode node) { - if ((nodes == null) || (nodeMap == null)) { - - return; - } - - int runner = nodes.indexOf(node); - - // this is due to difference between .equals() and == - while ((runner > -1) && (nodes.elementAt(runner) != node)) - runner = nodes.indexOf(node, Math.min(nodes.size() - 1, runner + 1)); - - if (runner > -1) { - nodes.removeElementAt(runner); - } - - // nodes.remove (node); - nodeMap.remove(node.getName().toLowerCase()); - - // Server.throwNodeEvent (new NodeEvent (node, NodeEvent.NODE_REMOVED)); - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.SUBNODE_REMOVED, node)); - lastmodified = System.currentTimeMillis(); - - // IServer.getLogger().log ("released node "+node +" from "+this+" oldobj = "+what); - } - - /** - * - * - * @return ... - */ - public Enumeration getSubnodes() { - return (nodes == null) ? new Vector().elements() : nodes.elements(); - } - - /** - * property-related - */ - public Enumeration properties() { - return (propMap == null) ? new EmptyEnumeration() : propMap.keys(); - } - - private Property getProperty(String propname) { - Property prop = (propMap == null) ? null : (Property) propMap.get(propname); - - // check if we have to create a virtual node - if ((prop == null) && (dbmap != null)) { - Relation rel = dbmap.getPropertyRelation(propname); - - if ((rel != null) && rel.isVirtual()) { - prop = makeVirtualNode(propname, rel); - } - } - - return prop; - } - - private Property makeVirtualNode(String propname, Relation rel) { - INode node = new Node(rel.getPropName(), rel.getPrototype(), - dbmap.getWrappedNodeManager()); - - // node.setState (TRANSIENT); - // make a db mapping good enough that the virtual node finds its subnodes - // DbMapping dbm = new DbMapping (); - // dbm.setSubnodeRelation (rel); - // dbm.setPropertyRelation (rel); - node.setDbMapping(rel.getVirtualMapping()); - setNode(propname, node); - - return (Property) propMap.get(propname); - } - - /** - * - * - * @param propname ... - * - * @return ... - */ - public IProperty get(String propname) { - return getProperty(propname); - } - - /** - * - * - * @param propname ... - * @param defaultValue ... - * - * @return ... - */ - public String getString(String propname, String defaultValue) { - String propValue = getString(propname); - - return (propValue == null) ? defaultValue : propValue; - } - - /** - * - * - * @param propname ... - * - * @return ... - */ - public String getString(String propname) { - Property prop = getProperty(propname); - - try { - return prop.getStringValue(); - } catch (Exception ignore) { - } - - return null; - } - - /** - * - * - * @param propname ... - * - * @return ... - */ - public long getInteger(String propname) { - Property prop = getProperty(propname); - - try { - return prop.getIntegerValue(); - } catch (Exception ignore) { - } - - return 0; - } - - /** - * - * - * @param propname ... - * - * @return ... - */ - public double getFloat(String propname) { - Property prop = getProperty(propname); - - try { - return prop.getFloatValue(); - } catch (Exception ignore) { - } - - return 0.0; - } - - /** - * - * - * @param propname ... - * - * @return ... - */ - public Date getDate(String propname) { - Property prop = getProperty(propname); - - try { - return prop.getDateValue(); - } catch (Exception ignore) { - } - - return null; - } - - /** - * - * - * @param propname ... - * - * @return ... - */ - public boolean getBoolean(String propname) { - Property prop = getProperty(propname); - - try { - return prop.getBooleanValue(); - } catch (Exception ignore) { - } - - return false; - } - - /** - * - * - * @param propname ... - * - * @return ... - */ - public INode getNode(String propname) { - Property prop = getProperty(propname); - - try { - return prop.getNodeValue(); - } catch (Exception ignore) { - } - - return null; - } - - /** - * - * - * @param propname ... - * - * @return ... - */ - public Object getJavaObject(String propname) { - Property prop = getProperty(propname); - - try { - return prop.getJavaObjectValue(); - } catch (Exception ignore) { - } - - return null; - } - - // create a property if it doesn't exist for this name - private Property initProperty(String propname) { - if (propMap == null) { - propMap = new Hashtable(); - } - - propname = propname.trim(); - Property prop = (Property) propMap.get(propname); - - if (prop == null) { - prop = new Property(propname, this); - propMap.put(propname, prop); - } - - return prop; - } - - /** - * - * - * @param propname ... - * @param value ... - */ - public void setString(String propname, String value) { - // IServer.getLogger().log ("setting String prop"); - Property prop = initProperty(propname); - - prop.setStringValue(value); - - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); - lastmodified = System.currentTimeMillis(); - } - - /** - * - * - * @param propname ... - * @param value ... - */ - public void setInteger(String propname, long value) { - // IServer.getLogger().log ("setting bool prop"); - Property prop = initProperty(propname); - - prop.setIntegerValue(value); - - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); - lastmodified = System.currentTimeMillis(); - } - - /** - * - * - * @param propname ... - * @param value ... - */ - public void setFloat(String propname, double value) { - // IServer.getLogger().log ("setting bool prop"); - Property prop = initProperty(propname); - - prop.setFloatValue(value); - - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); - lastmodified = System.currentTimeMillis(); - } - - /** - * - * - * @param propname ... - * @param value ... - */ - public void setBoolean(String propname, boolean value) { - // IServer.getLogger().log ("setting bool prop"); - Property prop = initProperty(propname); - - prop.setBooleanValue(value); - - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); - lastmodified = System.currentTimeMillis(); - } - - /** - * - * - * @param propname ... - * @param value ... - */ - public void setDate(String propname, Date value) { - // IServer.getLogger().log ("setting date prop"); - Property prop = initProperty(propname); - - prop.setDateValue(value); - - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); - lastmodified = System.currentTimeMillis(); - } - - /** - * - * - * @param propname ... - * @param value ... - */ - public void setJavaObject(String propname, Object value) { - // IServer.getLogger().log ("setting date prop"); - Property prop = initProperty(propname); - - prop.setJavaObjectValue(value); - - // Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED)); - lastmodified = System.currentTimeMillis(); - } - - /** - * - * - * @param propname ... - * @param value ... - */ - public void setNode(String propname, INode value) { - // IServer.getLogger().log ("setting date prop"); - Property prop = initProperty(propname); - - prop.setNodeValue(value); - - // check if the main identity of this node is as a named property - // or as an anonymous node in a collection - if (value instanceof TransientNode) { - TransientNode n = (TransientNode) value; - - if (n.parent == null) { - n.name = propname; - n.parent = this; - n.anonymous = false; - } - } - - lastmodified = System.currentTimeMillis(); - } - - /** - * - * - * @param propname ... - */ - public void unset(String propname) { - if (propMap != null && propname != null) { - propMap.remove(propname); - lastmodified = System.currentTimeMillis(); - } - } - - public long lastModified() { - return lastmodified; - } - - /** - * - * - * @return ... - */ - public long created() { - return created; - } - - /** - * - * - * @return ... - */ - public String toString() { - return "TransientNode " + name; - } - - /** - * Get the cache node for this node. This can - * be used to store transient cache data per node - * from Javascript. - */ - public synchronized INode getCacheNode() { - if (cacheNode == null) { - cacheNode = new TransientNode(); - } - - return cacheNode; - } - - /** - * Reset the cache node for this node. - */ - public synchronized void clearCacheNode() { - cacheNode = null; - } -} diff --git a/src/helma/objectmodel/db/Key.java b/src/helma/objectmodel/db/Key.java index 185eadf4..7fa5271a 100644 --- a/src/helma/objectmodel/db/Key.java +++ b/src/helma/objectmodel/db/Key.java @@ -38,7 +38,7 @@ public interface Key { public String getID(); /** - * Get the key's storage id + * Get the key's storage type name * * @return ... */ diff --git a/src/helma/objectmodel/db/Node.java b/src/helma/objectmodel/db/Node.java index 60b5c246..812ef048 100644 --- a/src/helma/objectmodel/db/Node.java +++ b/src/helma/objectmodel/db/Node.java @@ -22,7 +22,6 @@ import helma.framework.core.Application; import helma.objectmodel.ConcurrencyException; import helma.objectmodel.INode; import helma.objectmodel.IProperty; -import helma.objectmodel.TransientNode; import helma.util.EmptyEnumeration; import java.io.IOException; @@ -65,14 +64,11 @@ public final class Node implements INode, Serializable { transient DbMapping dbmap; transient Key primaryKey = null; transient String subnodeRelation = null; - transient long lastSubnodeFetch = 0; - transient long lastSubnodeChange = 0; transient long lastNameCheck = 0; transient long lastParentSet = 0; - transient long lastSubnodeCount = 0; // these two are only used - transient int subnodeCount = -1; // for aggressive loading relational subnodes transient private volatile Transactor lock; transient private volatile int state; + private static long idgen = 0; /** * Creates an empty, uninitialized Node. The init() method must be called on the @@ -230,7 +226,7 @@ public final class Node implements INode, Serializable { DbMapping smap = (dbmap == null) ? null : dbmap.getSubnodeMapping(); - if ((smap != null) && smap.isRelational()) { + if (smap != null && smap.isRelational()) { out.writeObject(null); } else { out.writeObject(subnodes); @@ -368,7 +364,9 @@ public final class Node implements INode, Serializable { * child index as changed */ public void markSubnodesChanged() { - lastSubnodeChange += 1; + if (subnodes != null) { + subnodes.lastSubnodeChange += 1; + } } /** @@ -429,8 +427,8 @@ public final class Node implements INode, Serializable { public String getID() { // if we are transient, we generate an id on demand. It's possible that we'll never need // it, but if we do it's important to keep the one we have. - if ((state == TRANSIENT) && (id == null)) { - id = TransientNode.generateID(); + if (state == TRANSIENT && id == null) { + id = generateTransientID(); } return id; } @@ -636,8 +634,8 @@ public final class Node implements INode, Serializable { * @param rel ... */ public synchronized void setSubnodeRelation(String rel) { - if (((rel == null) && (this.subnodeRelation == null)) || - ((rel != null) && rel.equalsIgnoreCase(this.subnodeRelation))) { + if ((rel == null && this.subnodeRelation == null) || + (rel != null && rel.equalsIgnoreCase(this.subnodeRelation))) { return; } @@ -646,9 +644,8 @@ public final class Node implements INode, Serializable { DbMapping smap = (dbmap == null) ? null : dbmap.getSubnodeMapping(); - if ((smap != null) && smap.isRelational()) { + if (subnodes != null && smap != null && smap.isRelational()) { subnodes = null; - subnodeCount = -1; } } @@ -1166,20 +1163,7 @@ public final class Node implements INode, Serializable { return null; } - Node retval = null; - - if (subnodes.size() > index) { - // check if there is a group-by relation - retval = ((NodeHandle) subnodes.get(index)).getNode(nmgr); - - if ((retval != null) && (retval.parentHandle == null) && - !nmgr.isRootNode(retval)) { - retval.setParent(this); - retval.anonymous = true; - } - } - - return retval; + return subnodes.getNode(index); } protected Node getGroupbySubnode(Node node, boolean create) { @@ -1232,7 +1216,7 @@ public final class Node implements INode, Serializable { loadNodes(); if (subnodes == null) { - subnodes = new SubnodeList(nmgr, dbmap.getSubnodeRelation()); + subnodes = new SubnodeList(this); } if (create || subnodes.contains(new NodeHandle(new SyntheticKey(getKey(), sid)))) { @@ -1520,38 +1504,7 @@ public final class Node implements INode, Serializable { * may actually load their IDs in order to do this. */ public int numberOfNodes() { - // If the subnodes are loaded aggressively, we really just - // do a count statement, otherwise we just return the size of the id index. - // (after loading it, if it's coming from a relational data source). - DbMapping subMap = (dbmap == null) ? null : dbmap.getSubnodeMapping(); - - if ((subMap != null) && subMap.isRelational()) { - // check if subnodes need to be rechecked - Relation subRel = dbmap.getSubnodeRelation(); - - // do not fetch subnodes for nodes that haven't been persisted yet or are in - // the process of being persistified - except if "manual" subnoderelation is set. - if (subRel.aggressiveLoading && subRel.getGroup() == null && - (((state != TRANSIENT) && (state != NEW)) || - (subnodeRelation != null))) { - // we don't want to load *all* nodes if we just want to count them - long lastChange = getLastSubnodeChange(subRel); - - if ((lastChange == lastSubnodeFetch) && (subnodes != null)) { - // we can use the nodes vector to determine number of subnodes - subnodeCount = subnodes.size(); - lastSubnodeCount = lastChange; - } else if ((lastChange != lastSubnodeCount) || (subnodeCount < 0)) { - // count nodes in db without fetching anything - subnodeCount = nmgr.countNodes(this, subRel); - lastSubnodeCount = lastChange; - } - return subnodeCount; - } - } - loadNodes(); - return (subnodes == null) ? 0 : subnodes.size(); } @@ -1562,32 +1515,19 @@ public final class Node implements INode, Serializable { */ public void loadNodes() { // Don't do this for transient nodes which don't have an explicit subnode relation set - if (((state == TRANSIENT) || (state == NEW)) && (subnodeRelation == null)) { + if ((state == TRANSIENT || state == NEW) && subnodeRelation == null) { return; } DbMapping subMap = (dbmap == null) ? null : dbmap.getSubnodeMapping(); - if ((subMap != null) && subMap.isRelational()) { + if (subMap != null && subMap.isRelational()) { // check if subnodes need to be reloaded - Relation subRel = dbmap.getSubnodeRelation(); - synchronized (this) { - // also reload if the type mapping has changed. - long lastChange = getLastSubnodeChange(subRel); - - if ((lastChange != lastSubnodeFetch && !subRel.autoSorted) || (subnodes == null)) { - if (subRel.updateCriteria!=null) { - // updateSubnodeList is setting the subnodes directly returning an integer - nmgr.updateSubnodeList(this, subRel); - } else if (subRel.aggressiveLoading) { - subnodes = nmgr.getNodes(this, subRel); - } else { - subnodes = nmgr.getNodeIDs(this, subRel); - } - - lastSubnodeFetch = lastChange; + if (subnodes == null) { + createSubnodeList(); } + subnodes.update(); } } } @@ -1598,27 +1538,17 @@ public final class Node implements INode, Serializable { * @return List an empty List of the type used by this Node */ public SubnodeList createSubnodeList() { - Relation rel = this.dbmap == null ? null : this.dbmap.getSubnodeRelation(); - if (rel != null && rel.updateCriteria != null) { - subnodes = new UpdateableSubnodeList(nmgr, rel); - } else if (rel != null && rel.autoSorted) { - subnodes = new OrderedSubnodeList(nmgr, rel); - } else { - subnodes = new SubnodeList(nmgr, rel); - } + subnodes = new SegmentedSubnodeList(this); return subnodes; } /** * Compute a serial number indicating the last change in subnode collection - * @param subRel the subnode relation * @return a serial number that increases with each subnode change */ - long getLastSubnodeChange(Relation subRel) { - // include dbmap.getLastTypeChange to also reload if the type mapping has changed. - long checkSum = lastSubnodeChange + dbmap.getLastTypeChange(); - return subRel.aggressiveCaching ? - checkSum : checkSum + subRel.otherType.getLastDataChange(); + long getLastSubnodeChange() { + // TODO check if we should compute this on demand + return subnodes == null ? 0 : subnodes.getLastSubnodeChange(); } /** @@ -1629,65 +1559,46 @@ public final class Node implements INode, Serializable { * * @throws Exception ... */ - public void prefetchChildren(int startIndex, int length) - throws Exception { - if (length < 1) { - return; - } - + public void prefetchChildren(int startIndex, int length) { if (startIndex < 0) { return; } loadNodes(); - if (subnodes == null) { + if (subnodes == null || startIndex >= subnodes.size()) { return; } - if (startIndex >= subnodes.size()) { - return; - } - - int l = Math.min(subnodes.size() - startIndex, length); - - if (l < 1) { - return; - } - - Key[] keys = new Key[l]; - - for (int i = 0; i < l; i++) { - keys[i] = ((NodeHandle) subnodes.get(i + startIndex)).getKey(); - } - - prefetchChildren (keys); - } - - public void prefetchChildren (Key[] keys) throws Exception { - nmgr.nmgr.prefetchNodes(this, dbmap.getSubnodeRelation(), keys); + subnodes.prefetch(startIndex, length); } /** - * - * - * @return ... + * Enumerate through the subnodes of this node. + * @return an enumeration of this node's subnodes */ public Enumeration getSubnodes() { loadNodes(); - class Enum implements Enumeration { - int count = 0; + + final SubnodeList list = subnodes; + if (list == null) { + return new EmptyEnumeration(); + } + + return new Enumeration() { + int pos = 0; public boolean hasMoreElements() { - return count < numberOfNodes(); + return pos < list.size(); } public Object nextElement() { - return getSubnodeAt(count++); + // prefetch in batches of 100 + // if (pos % 100 == 0) + // list.prefetch(pos, 100); + return list.getNode(pos++); } - } - - return new Enum(); + }; } /** @@ -2632,7 +2543,7 @@ public final class Node implements INode, Serializable { */ public synchronized INode getCacheNode() { if (cacheNode == null) { - cacheNode = new TransientNode(); + cacheNode = new Node("cache", null, nmgr); } return cacheNode; @@ -2679,6 +2590,12 @@ public final class Node implements INode, Serializable { return nmgr == null; } + String generateTransientID() { + // make transient ids differ from persistent ones + // and are unique within on runtime session + return "t" + idgen++; + } + /** * We overwrite hashCode to make it dependant from the prototype. That way, when the prototype * changes, the node will automatically get a new ESNode wrapper, since they're cached in a hashtable. @@ -2713,7 +2630,7 @@ public final class Node implements INode, Serializable { * (somefiled1 > theHighestKnownValue value and somefield2 < theLowestKnownValue) * @return the number of loaded nodes within this collection update */ - public int updateSubnodes () { + /* public int updateSubnodes () { // TODO: what do we do if dbmap is null if (dbmap == null) { throw new RuntimeException (this + " doesn't have a DbMapping"); @@ -2723,7 +2640,7 @@ public final class Node implements INode, Serializable { lastSubnodeFetch = getLastSubnodeChange(subRel); return nmgr.updateSubnodeList(this, subRel); } - } + } */ /** * Get the application this node belongs to. diff --git a/src/helma/objectmodel/db/NodeHandle.java b/src/helma/objectmodel/db/NodeHandle.java index 4883c59b..89aaa66b 100644 --- a/src/helma/objectmodel/db/NodeHandle.java +++ b/src/helma/objectmodel/db/NodeHandle.java @@ -29,6 +29,15 @@ import java.io.Serializable; * While a direct reference may point to a node that has been evicted from the cache * and reinstanciated since being set, NodeHandle will always return an up-to-date * instance of its node. + * + * Helma tries to ensure the following rules on NodeHandles: + *
    + *
  1. For transient nodes there exists only one NodeHandle.
  2. + *
  3. If a transient node becomes persistent its node handle is notified and + * converted into a persistent NodeHandle.
  4. + *
+ * These two properties guarantee that NodeHandle comparisons are easy and usually correct. + * */ public final class NodeHandle implements INodeState, Serializable { static final long serialVersionUID = 3067763116576910931L; @@ -40,9 +49,12 @@ public final class NodeHandle implements INodeState, Serializable { private Key key; /** - * Builds a handle for a node + * Builds a handle for a node. This constructor is package private in order to make + * sure only one NodeHandle exists per transient node. Use {@link Node#getHandle()} + * to get a Node's handle. + * @param node the node */ - public NodeHandle(Node node) { + NodeHandle(Node node) { int state = node.getState(); if (state == TRANSIENT) { @@ -58,6 +70,7 @@ public final class NodeHandle implements INodeState, Serializable { * Builds a handle given a node's retrieval information. At the time this is called, * the node is ususally not yet created. It will be fetched on demand when accessed by * application code. + * @param key the key */ public NodeHandle(Key key) { this.node = null; @@ -71,19 +84,22 @@ public final class NodeHandle implements INodeState, Serializable { if (node != null) { return node; } - return nodemgr.getNode(key); } + /** + * Check if the node is available without fetching it from the node manager + * @return true if we alreay have a reference to our node + */ + public boolean hasNode() { + return node != null; + } + /** * Get the key for the node described by this handle. - * This may only be called on persistent Nodes. + * This will return null for transient Nodes. */ public Key getKey() { - if (key == null) { - throw new RuntimeException("getKey called on transient Node"); - } - return key; } @@ -95,7 +111,6 @@ public final class NodeHandle implements INodeState, Serializable { if (key == null) { return node.getID(); } - return key.getID(); } @@ -115,11 +130,12 @@ public final class NodeHandle implements INodeState, Serializable { * @return ... */ public boolean equals(Object other) { - try { - return getObject().equals(((NodeHandle) other).getObject()); - } catch (Exception x) { - return false; + if (other instanceof NodeHandle) { + Object obj1 = getObject(); + Object obj2 = ((NodeHandle) other).getObject(); + return obj1 == obj2 || obj1.equals(obj2); } + return false; } /** diff --git a/src/helma/objectmodel/db/NodeManager.java b/src/helma/objectmodel/db/NodeManager.java index a6b2c46f..6c692c6c 100644 --- a/src/helma/objectmodel/db/NodeManager.java +++ b/src/helma/objectmodel/db/NodeManager.java @@ -251,7 +251,7 @@ public final class NodeManager { if ((node != null) && (node.getState() != Node.INVALID)) { // check if node is null node (cached null) if (node.isNullNode()) { - if (node.created != home.getLastSubnodeChange(rel)) { + if (node.created != home.getLastSubnodeChange()) { node = null; // cached null not valid anymore } } else if (!rel.virtual) { @@ -292,7 +292,7 @@ public final class NodeManager { } else { // node fetched from db is null, cache result using nullNode synchronized (cache) { - cache.put(key, new Node(home.getLastSubnodeChange(rel))); + cache.put(key, new Node(home.getLastSubnodeChange())); // we ignore the case that onother thread has created the node in the meantime return null; @@ -876,14 +876,14 @@ public final class NodeManager { * Loades subnodes via subnode relation. Only the ID index is loaded, the nodes are * loaded later on demand. */ - public SubnodeList getNodeIDs(Node home, Relation rel) throws Exception { + public List getNodeIDs(Node home, Relation rel) throws Exception { if ((rel == null) || (rel.otherType == null) || !rel.otherType.isRelational()) { // this should never be called for embedded nodes throw new RuntimeException("NodeMgr.getNodeIDs called for non-relational node " + home); } else { - SubnodeList retval = home.createSubnodeList(); + List retval = new ArrayList(); // if we do a groupby query (creating an intermediate layer of groupby nodes), // retrieve the value of that field instead of the primary key @@ -946,7 +946,7 @@ public final class NodeManager { Key key = (rel.groupby == null) ? (Key) new DbKey(rel.otherType, kstr) : (Key) new SyntheticKey(k, kstr); - retval.addSorted(new NodeHandle(key)); + retval.add(new NodeHandle(key)); // if these are groupby nodes, evict nullNode keys if (rel.groupby != null) { @@ -980,79 +980,77 @@ public final class NodeManager { * actually loades all nodes in one go, which is better for small node collections. * This method is used when xxx.loadmode=aggressive is specified. */ - public SubnodeList getNodes(Node home, Relation rel) throws Exception { + public List getNodes(Node home, Relation rel) throws Exception { // This does not apply for groupby nodes - use getNodeIDs instead - if (rel.groupby != null) { - return getNodeIDs(home, rel); - } + assert rel.groupby == null; if ((rel == null) || (rel.otherType == null) || !rel.otherType.isRelational()) { // this should never be called for embedded nodes throw new RuntimeException("NodeMgr.getNodes called for non-relational node " + home); - } else { - SubnodeList retval = home.createSubnodeList(); - DbMapping dbm = rel.otherType; + } - Connection con = dbm.getConnection(); - // set connection to read-only mode - if (!con.isReadOnly()) con.setReadOnly(true); + List retval = new ArrayList(); + DbMapping dbm = rel.otherType; - Statement stmt = con.createStatement(); - DbColumn[] columns = dbm.getColumns(); - Relation[] joins = dbm.getJoins(); - String query = null; - long logTimeStart = logSql ? System.currentTimeMillis() : 0; + Connection con = dbm.getConnection(); + // set connection to read-only mode + if (!con.isReadOnly()) con.setReadOnly(true); - try { - StringBuffer b = dbm.getSelect(rel); + Statement stmt = con.createStatement(); + DbColumn[] columns = dbm.getColumns(); + Relation[] joins = dbm.getJoins(); + String query = null; + long logTimeStart = logSql ? System.currentTimeMillis() : 0; - if (home.getSubnodeRelation() != null) { - b.append(home.getSubnodeRelation()); - } else { - // let relation object build the query - rel.buildQuery(b, home, null, " WHERE ", true); - } + try { + StringBuffer b = dbm.getSelect(rel); - query = b.toString(); - - if (rel.maxSize > 0) { - stmt.setMaxRows(rel.maxSize); - } - - ResultSet rs = stmt.executeQuery(query); - - while (rs.next()) { - // create new Nodes. - Node node = createNode(rel.otherType, rs, columns, 0); - if (node == null) { - continue; - } - Key primKey = node.getKey(); - - retval.addSorted(new NodeHandle(primKey)); - - registerNewNode(node, null); - - fetchJoinedNodes(rs, joins, columns.length); - } - - } finally { - if (logSql) { - long logTimeStop = System.currentTimeMillis(); - logSqlStatement("SQL SELECT_ALL", dbm.getTableName(), - logTimeStart, logTimeStop, query); - } - if (stmt != null) { - try { - stmt.close(); - } catch (Exception ignore) { - } - } + if (home.getSubnodeRelation() != null) { + b.append(home.getSubnodeRelation()); + } else { + // let relation object build the query + rel.buildQuery(b, home, null, " WHERE ", true); } - return retval; + query = b.toString(); + + if (rel.maxSize > 0) { + stmt.setMaxRows(rel.maxSize); + } + + ResultSet rs = stmt.executeQuery(query); + + while (rs.next()) { + // create new Nodes. + Node node = createNode(rel.otherType, rs, columns, 0); + if (node == null) { + continue; + } + Key primKey = node.getKey(); + + retval.add(new NodeHandle(primKey)); + + registerNewNode(node, null); + + fetchJoinedNodes(rs, joins, columns.length); + } + + } finally { + if (logSql) { + long logTimeStop = System.currentTimeMillis(); + logSqlStatement("SQL SELECT_ALL", dbm.getTableName(), + logTimeStart, logTimeStop, query); + } + if (stmt != null) { + try { + stmt.close(); + } catch (Exception ignore) { + } + } } + + return retval; } /** @@ -1068,7 +1066,7 @@ public final class NodeManager { * @return A map having two properties of type String (newNodes (number of nodes retreived by the select-statment), addedNodes (nodes added to the collection)) * @throws Exception */ - public int updateSubnodeList(Node home, Relation rel) throws Exception { + /* public int updateSubnodeList(Node home, Relation rel) throws Exception { if ((rel == null) || (rel.otherType == null) || !rel.otherType.isRelational()) { // this should never be called for embedded nodes throw new RuntimeException("NodeMgr.updateSubnodeList called for non-relational node " + @@ -1199,20 +1197,35 @@ public final class NodeManager { } } } + } */ + + protected List collectMissingKeys(SubnodeList list, int start, int length) { + List retval = null; + for (int i = start; i < start + length; i++) { + NodeHandle handle = (NodeHandle) list.get(i); + if (handle != null && !cache.containsKey(handle.getKey())) { + if (retval == null) { + retval = new ArrayList(); + } + retval.add(handle.getKey().getID()); + } + } + return retval; } /** * */ - public void prefetchNodes(Node home, Relation rel, Key[] keys) + public void prefetchNodes(Node home, Relation rel, SubnodeList list, int start, int length) throws Exception { DbMapping dbm = rel.otherType; // this does nothing for objects in the embedded database if (dbm != null && dbm.isRelational()) { - int missing = cache.containsKeys(keys); + // int missing = cache.containsKeys(keys); + List missing = collectMissingKeys(list, start, length); - if (missing > 0) { + if (missing != null) { Connection con = dbm.getConnection(); // set connection to read-only mode if (!con.isReadOnly()) con.setReadOnly(true); @@ -1226,13 +1239,7 @@ public final class NodeManager { try { StringBuffer b = dbm.getSelect(null).append(" WHERE "); String idfield = (rel.groupby != null) ? rel.groupby : dbm.getIDField(); - - String[] ids = new String[missing]; - int j = 0; - for (int k = 0; k < keys.length; k++) { - if (keys[k] != null) - ids[j++] = keys[k].getID(); - } + String[] ids = (String[]) missing.toArray(new String[missing.size()]); dbm.appendCondition(b, idfield, ids); dbm.addJoinConstraints(b, " AND "); @@ -1277,7 +1284,7 @@ public final class NodeManager { // group nodes. String groupName = null; - if (groupbyProp != null) { + /* if (groupbyProp != null) { groupName = node.getString(groupbyProp); SubnodeList sn = (SubnodeList) groupbySubnodes.get(groupName); @@ -1287,8 +1294,8 @@ public final class NodeManager { groupbySubnodes.put(groupName, sn); } - sn.addSorted(new NodeHandle(key)); - } + sn.add(new NodeHandle(key)); + } */ // if relation doesn't use primary key as accessName, get secondary key if (accessProp != null) { @@ -1313,7 +1320,7 @@ public final class NodeManager { // If these are grouped nodes, build the intermediary group nodes // with the subnod lists we created - if (groupbyProp != null) { + /* if (groupbyProp != null) { for (Iterator i = groupbySubnodes.keySet().iterator(); i.hasNext();) { String groupname = (String) i.next(); @@ -1328,7 +1335,7 @@ public final class NodeManager { groupnode.lastSubnodeFetch = groupnode.getLastSubnodeChange(groupnode.dbmap.getSubnodeRelation()); } - } + } */ } catch (Exception x) { app.logError("Error in prefetchNodes()", x); } finally { @@ -1417,7 +1424,7 @@ public final class NodeManager { } /** - * Similar to getNodeIDs, but returns a Vector that return's the nodes property names instead of IDs + * Similar to getNodeIDs, but returns a List that contains the nodes property names instead of IDs */ public Vector getPropertyNames(Node home, Relation rel) throws Exception { @@ -1467,7 +1474,7 @@ public final class NodeManager { String n = rs.getString(1); if (n != null) { - retval.addElement(n); + retval.add(n); } } } finally { diff --git a/src/helma/objectmodel/db/OrderedSubnodeList.java b/src/helma/objectmodel/db/OrderedSubnodeList.java deleted file mode 100644 index cb9a5d71..00000000 --- a/src/helma/objectmodel/db/OrderedSubnodeList.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Helma License Notice - * - * The contents of this file are subject to the Helma License - * Version 2.0 (the "License"). You may not use this file except in - * compliance with the License. A copy of the License is available at - * http://adele.helma.org/download/helma/license.txt - * - * Copyright 1998-2003 Helma Software. All Rights Reserved. - * - * $RCSfile$ - * $Author$ - * $Revision$ - * $Date$ - */ -package helma.objectmodel.db; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; - - -/** - * @author manfred andres - * This subnode-collection may be used to add nodes in an ordered way depending on - * the given order. It is also possible to retrieve views of this list in a different - * order. These views will be cached and automatically updated if this List's add- - * or remove-methods are called. - */ -public class OrderedSubnodeList extends SubnodeList { - - // the base subnode list, in case this is an ordered view - private SubnodeList origin; - // an array containing the order-fields - private String orderProperties[]; - // an array containing the direction for ordering - private boolean orderIsDesc[]; - - /** - * Construct a new OrderedSubnodeList. The Relation is needed - * to get the information about the ORDERING - */ - public OrderedSubnodeList (WrappedNodeManager nmgr, Relation rel) { - super(nmgr, rel); - this.origin = null; - // check the order of this collection for automatically sorting - // in the values in the correct order - initOrder(rel.order); - } - - /** - * This constructor is used to create a view for the OrderedSubnodeList origin. - * @param origin the origin-list which holds the elements - * @param expr the new order for this view - * @param rel the relation given for the origin-list - */ - public OrderedSubnodeList (WrappedNodeManager nmgr, Relation rel, SubnodeList origin, String expr) { - super(nmgr, rel); - this.origin = origin; - initOrder(expr); - if (origin != null) { - sortIn(origin, false); - } - } - - private void initOrder(String order) { - if (order == null) { - orderProperties=null; - orderIsDesc=null; - } else { - String orderParts[] = order.split(","); - orderProperties = new String[orderParts.length]; - orderIsDesc = new boolean[orderParts.length]; - for (int i = 0; i < orderParts.length; i++) { - String part[] = orderParts[i].trim().split(" "); - orderProperties[i] = part[0].equals("_id") ? - null : part[0]; - orderIsDesc[i] = part.length == 2 && - "DESC".equalsIgnoreCase(part[1]); - } - } - } - - /** - * Adds the specified object to this list performing - * custom ordering - * - * @param obj element to be inserted. - */ - public boolean add(Object obj) { - return add(obj, true); - } - - /** - * Adds the specified object to the list at the given position - * @param idx the index to insert the element at - * @param obj the object t add - */ - public void add(int idx, Object obj) { - if (this.orderProperties!=null) - throw new RuntimeException ("Indexed add isn't alowed for ordered subnodes"); - super.add(idx, obj); - addToViews(obj); - } - - /** - * Adds the specified object to this list without performing - * custom ordering. - * - * @param obj element to be inserted. - */ - public boolean addSorted(Object obj) { - return add(obj, false); - } - - boolean add(Object obj, boolean sort) { - if (origin != null) { - return origin.add(obj); - } - addToViews(obj); - int maxSize = rel == null ? 0 : rel.maxSize; - while (maxSize > 0 && this.size() >= maxSize) { - remove(size() - 1); - } - // escape sorting for presorted adds and grouped nodes - if (sort && (rel == null || rel.groupby == null)) { - sortIn(obj); - } else { - super.add(obj); - } - return true; - } - - /** - * add a new node honoring the Nodes SQL-Order - * @param obj the object to add - */ - public boolean sortIn(Object obj) { - // no order, just add - if (this.orderProperties==null) - return super.add(obj); - addToViews(obj); - Node node = ((NodeHandle) obj).getNode(nmgr); - int idx = this.determineNodePosition(node, 0); - // System.err.println("Position: " + idx); - if (idx<0) - return super.add(obj); - else - super.add(idx, obj); - return true; - } - - public boolean addAll(Collection col) { - return sortIn(col, true) > 0; - } - - private void addAllToViews (Collection col) { - if (views == null || origin != null || views.isEmpty()) - return; - for (Iterator i = views.values().iterator(); i.hasNext(); ) { - OrderedSubnodeList osl = (OrderedSubnodeList) i.next(); - osl.addAll(col); - } - } - - /** - * Add all nodes contained inside the specified Collection to this - * UpdateableSubnodeList. The order of the added Nodes is assumed to - * be ordered according to the SQL-Order-Clause given for this - * Subnode collection but doesn't prevent adding of unordered Collections. - * Ordered Collections will be sorted in more efficiently than unordered ones. - * - * @param col the collection containing all elements to add in the order returned by the select-statement - * @param colHasDefaultOrder true if the given collection does have the default-order defined by the relation - */ - public int sortIn (Collection col, boolean colHasDefaultOrder) { - addAllToViews(col); - int cntr=0; - // there is no order specified, add on top - if (orderProperties == null) { - for (Iterator i = col.iterator(); i.hasNext(); ) { - super.add(cntr, i.next()); - cntr++; - } - int maxSize = rel == null ? 0 : rel.maxSize; - if (maxSize > 0) { - int diff = this.size() - maxSize; - if (diff > 0) - super.removeRange(this.size()-1-diff, this.size()-1); - } - } else if (!colHasDefaultOrder || origin != null) { - // this collection is a view or the given collection doesn't have the - // default order - for (Iterator i = col.iterator(); i.hasNext(); ) { - Object obj = i.next(); - if (obj==null) - continue; - sortIn (obj); - cntr++; - } - } else { - NodeHandle[] nhArr = (NodeHandle[]) col.toArray (new NodeHandle[0]); - Node node = nhArr[0].getNode(nmgr); - int locIdx = determineNodePosition(node, 0); // determine start-point - if (locIdx == -1) - locIdx = this.size(); - // int interval=Math.max(1, this.size()/2); - for (int addIdx=0; addIdx < nhArr.length; addIdx++) { - node = nhArr[addIdx].getNode(nmgr); - while (locIdx < this.size() && - compareNodes(node, (NodeHandle) this.get(locIdx)) >= 0) - locIdx++; - if (locIdx < this.size()) { - this.add(locIdx, nhArr[addIdx]); - } else { - this.add(nhArr[addIdx]); - } - cntr++; - } - } - return cntr; - } - - /** - * remove all elements contained inside the specified collection - * from this List - * @param c the Collection containing all Objects to remove from this List - * @return true if the List has been modified - */ - public boolean removeAll(Collection c) { - removeAllFromViews(c); - return super.removeAll(c); - } - - private void removeAllFromViews(Collection c) { - if (views == null || origin != null || views.isEmpty()) - return; - for (Iterator i = views.values().iterator(); i.hasNext(); ) { - OrderedSubnodeList osl = (OrderedSubnodeList) i.next(); - osl.removeAll(c); - } - } - - /** - * remove all elements from this List, which are NOT specified - * inside the specified Collecion - * @param c the Collection containing all Objects to keep on the List - * @return true if the List has been modified - */ - public boolean retainAll (Collection c) { - retainAllInViews(c); - return super.retainAll(c); - } - - private void retainAllInViews(Collection c) { - if (views == null || origin != null || views.isEmpty()) - return; - for (Iterator i = views.values().iterator(); i.hasNext(); ) { - OrderedSubnodeList osl = (OrderedSubnodeList) i.next(); - osl.retainAll (c); - } - } - - private int determineNodePosition (Node node, int startIdx) { - int size = this.size(); - int interval = Math.max(1, (size - startIdx) / 2); - boolean dirUp=true; - int cntr = 0; - int maxSize = rel == null ? 0 : rel.maxSize; - for (int i = 0; i < size - && (i < maxSize || maxSize <= 0) - && cntr < (size * 2); cntr++) { // cntr is used to avoid endless-loops which shouldn't happen - NodeHandle curr = (NodeHandle) this.get(i); - int comp = compareNodes(node, curr); - // current NodeHandle is below the given NodeHandle - // interval has to be 1 and - // idx must be zero or the node before the current node must be higher or equal - // all conditions must be met to determine the correct position of a node - if (comp < 0 && interval==1 && (i==0 || compareNodes(node, (NodeHandle) this.get(i-1)) >= 0)) { - return i; - } else if (comp < 0) { - dirUp=false; - } else if (comp > 0) { - dirUp=true; - } else if (comp == 0) { - dirUp=true; - interval=1; - } - if (dirUp) { - i=i+interval; - if (i >= this.size()) { - if (compareNodes(node, (NodeHandle) this.get(size-1)) >= 0) - break; - interval = Math.max(1, (i - size-1)/2); - i = this.size()-1; - } else { - interval = Math.max(1,interval/2); - } - } else { - i = i-interval; - if (i < 0) { // shouldn't happen i think - interval=Math.max(1, (interval+i)/2); - i=0; - } - } - - } - if (cntr >= size * 2 && size > 1) { - System.err.println("determineNodePosition needed more than the allowed iterations"); - } - return -1; - } - - /** - * Compare two nodes depending on the specified ORDER for this collection. - * @param node the first Node - * @param nodeHandle the second Node - * @return an integer lesser than zero if nh1 is less than, zero if nh1 is equal to and a value greater than zero if nh1 is bigger than nh2. - */ - private int compareNodes(Node node, NodeHandle nodeHandle) { - for (int i = 0; i < orderProperties.length; i++) { - if (orderProperties[i]==null) { - // we have the id as order-criteria-> avoid loading node - // and compare numerically instead of lexicographically - String s1 = node.getID(); - String s2 = nodeHandle.getID(); - int j = compareNumericString (s1, s2); - if (j == 0) - continue; - if (orderIsDesc[i]) - j = j * -1; - return j; - } - Property p1 = node.getProperty(orderProperties[i]); - Property p2 = nodeHandle.getNode(nmgr).getProperty(orderProperties[i]); - int j; - if (p1 == null && p2 == null) { - continue; - } else if (p1 == null) { - j = 1; - } else if (p2 == null) { - j = -1; - } else { - j = p1.compareTo(p2); - } - if (j == 0) { - continue; - } - if (orderIsDesc[i]) { - j = j * -1; - } - return j; - } - return -1; - } - - /** - * Compare two strings containing numbers depending on their numeric values - * instead of doing a lexicographical comparison. - * @param a the first String - * @param b the second String - */ - public static int compareNumericString(String a, String b) { - if (a == null && b != null) - return -1; - if (a != null && b == null) - return 1; - if (a == null && b == null) - return 0; - if (a.length() < b.length()) - return -1; - if (a.length() > b.length()) - return 1; - for (int i = 0; i < a.length(); i++) { - if (a.charAt(i) < b.charAt(i)) - return -1; - if (a.charAt(i) > b.charAt(i)) - return 1; - } - return 0; - } - - public SubnodeList getOrderedView (String order) { - if (origin != null) { - return origin.getOrderedView(order); - } else { - return super.getOrderedView(order); - } - } -} diff --git a/src/helma/objectmodel/db/Relation.java b/src/helma/objectmodel/db/Relation.java index ca5aa454..3726f5c9 100644 --- a/src/helma/objectmodel/db/Relation.java +++ b/src/helma/objectmodel/db/Relation.java @@ -106,9 +106,10 @@ public final class Relation { /** * This constructor makes a copy of an existing relation. Not all fields are copied, just those - * which are needed in groupby- and virtual nodes defined by this relation. + * which are needed in groupby- and virtual nodes defined by this relation. use + * {@link Relation#getClone()} to get a full copy of this relation. */ - private Relation(Relation rel) { + protected Relation(Relation rel) { // Note: prototype, groupby, groupbyPrototype and groupbyOrder aren't copied here. // these are set by the individual get*Relation() methods as appropriate. this.ownType = rel.ownType; @@ -790,7 +791,7 @@ public final class Relation { virtualMapping.propRelation = getVirtualPropertyRelation(); } } - + virtualMapping.lastTypeChange = ownType.lastTypeChange; return virtualMapping; } @@ -1119,6 +1120,19 @@ public final class Relation { return readonly; } + /** + * Get a copy of this relation. + * @return a clone of this relation + */ + public Relation getClone() { + Relation rel = new Relation(this); + rel.prototype = prototype; + rel.groupby = groupby; + rel.groupbyPrototype = groupbyPrototype; + rel.groupbyOrder = groupbyOrder; + return rel; + } + /** * Check if the child node fullfills the constraints defined by this relation. * FIXME: This always returns false if the relation has a filter value set, diff --git a/src/helma/objectmodel/db/SegmentedSubnodeList.java b/src/helma/objectmodel/db/SegmentedSubnodeList.java new file mode 100644 index 00000000..6c080ce4 --- /dev/null +++ b/src/helma/objectmodel/db/SegmentedSubnodeList.java @@ -0,0 +1,255 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 2009 Helma Project. All Rights Reserved. + */ + +package helma.objectmodel.db; + +import java.util.*; + +public class SegmentedSubnodeList extends SubnodeList { + + transient Segment[] segments; + static int SEGLENGTH = 10000; + + transient long lastSubnodeCount = 0; + transient int subnodeCount = -1; + + /** + * Creates a new subnode list + * @param node the node we belong to + */ + public SegmentedSubnodeList(Node node) { + super(node); + } + + /** + * Adds the specified object to this list performing + * custom ordering + * + * @param obj element to be inserted. + */ + public synchronized boolean add(Object obj) { + subnodeCount++; + return list.add(obj); + } + /** + * Adds the specified object to the list at the given position + * @param index the index to insert the element at + * @param obj the object t add + */ + public synchronized void add(int index, Object obj) { + if (!hasRelationalNodes() || segments == null) { + super.add(index, obj); + return; + } + list.add(index, obj); + // shift segment indices by one + int s = getSegment(index); + segments[s].length += 1; + for (int i = s + 1; i < segments.length; i++) { + segments[i].startIndex += 1; + } + } + + public Object get(int index) { + if (!hasRelationalNodes() || segments == null) { + return super.get(index); + } + if (index < 0 || index >= subnodeCount) { + return null; + } + loadSegment(getSegment(index), false); + return list.get(index); + } + + public synchronized boolean contains(Object object) { + if (!hasRelationalNodes() || segments == null) { + return super.contains(object); + } + if (list.contains(object)) { + return true; + } + for (int i = 0; i < segments.length; i++) { + if (loadSegment(i, false).contains(object)) { + return true; + } + } + return false; + } + + public synchronized int indexOf(Object object) { + if (!hasRelationalNodes() || segments == null) { + return super.indexOf(object); + } + int index; + if ((index = list.indexOf(object)) > -1) { + return index; + } + for (int i = 0; i < segments.length; i++) { + if ((index = loadSegment(i, false).indexOf(object)) > -1) { + return segments[i].startIndex + index; + } + } + return -1; + } + + /** + * remove the object specified by the given index-position + * @param index the index-position of the NodeHandle to remove + */ + public synchronized Object remove(int index) { + if (!hasRelationalNodes() || segments == null) { + return super.remove(index); + } + Object removed = list.remove(index); + int s = getSegment(index); + segments[s].length -= 1; + for (int i = s + 1; i < segments.length; i++) { + segments[i].startIndex -= 1; + } + return removed; + } + + /** + * remove the given Object from this List + * @param object the NodeHandle to remove + */ + public synchronized boolean remove(Object object) { + if (!hasRelationalNodes() || segments == null) { + return super.remove(object); + } + int index = indexOf(object); + if (index > -1) { + list.remove(object); + int s = getSegment(index); + segments[s].length -= 1; + for (int i = s + 1; i < segments.length; i++) { + segments[i].startIndex -= 1; + } + return true; + } + return false; + } + + public synchronized Object[] toArray() { + if (!hasRelationalNodes() || segments == null) { + return super.toArray(); + } + node.nmgr.logEvent("Warning: toArray() called on large segmented collection: " + node); + for (int i = 0; i < segments.length; i++) { + loadSegment(i, false); + } + return list.toArray(); + } + + private int getSegment(int index) { + for (int i = 1; i < segments.length; i++) { + if (index < segments[i].startIndex) { + return i - 1; + } + } + return segments.length - 1; + } + + private List loadSegment(int seg, boolean deep) { + Segment segment = segments[seg]; + if (segment != null && !segment.loaded) { + Relation rel = getSubnodeRelation().getClone(); + rel.offset = segment.startIndex; + int expectedSize = rel.maxSize = segment.length; + List seglist = deep ? + node.nmgr.getNodes(node, rel) : + node.nmgr.getNodeIDs(node, rel); + int actualSize = seglist.size(); + if (actualSize != expectedSize) { + node.nmgr.logEvent("Inconsistent segment size in " + node + ": " + segment); + } + int listSize = list.size(); + for (int i = 0; i < actualSize; i++) { + if (segment.startIndex + i < listSize) { + list.set(segment.startIndex + i, seglist.get(i)); + } else { + list.add(seglist.get(i)); + } + // FIXME how to handle inconsistencies? + } + segment.loaded = true; + return seglist; + } + return Collections.EMPTY_LIST; + } + + protected synchronized void update() { + if (!hasRelationalNodes()) { + super.update(); + } + // also reload if the type mapping has changed. + long lastChange = getLastSubnodeChange(); + if (lastChange != lastSubnodeFetch) { + float size = size(); + if (size > SEGLENGTH) { + int nsegments = (int) Math.ceil(size / SEGLENGTH); + int remainder = (int) size % SEGLENGTH; + segments = new Segment[nsegments]; + for (int s = 0; s < nsegments; s++) { + int length = (s == nsegments - 1 && remainder > 0) ? + remainder : SEGLENGTH; + segments[s] = new Segment(s * SEGLENGTH, length); + } + list = new ArrayList((int) size + 5); + for (int i = 0; i < size; i++) { + list.add(null); + } + } else { + segments = null; + super.update(); + } + lastSubnodeFetch = lastChange; + } + } + + public int size() { + if (!hasRelationalNodes()) { + return super.size(); + } + + Relation rel = getSubnodeRelation(); + long lastChange = getLastSubnodeChange(); + + if (lastChange != lastSubnodeCount || subnodeCount < 0) { + // count nodes in db without fetching anything + subnodeCount = node.nmgr.countNodes(node, rel); + lastSubnodeCount = lastChange; + } + return subnodeCount; + } + + class Segment { + + int startIndex, length; + boolean loaded; + + Segment(int startIndex, int length) { + this.startIndex = startIndex; + this.length = length; + this.loaded = false; + } + + int endIndex() { + return startIndex + length; + } + + public String toString() { + return "Segment{startIndex: " + startIndex + ", length: " + length + "}"; + } + } + +} + diff --git a/src/helma/objectmodel/db/SubnodeList.java b/src/helma/objectmodel/db/SubnodeList.java index 1de67312..53ffe191 100644 --- a/src/helma/objectmodel/db/SubnodeList.java +++ b/src/helma/objectmodel/db/SubnodeList.java @@ -18,17 +18,19 @@ package helma.objectmodel.db; import java.util.ArrayList; import java.util.List; -import java.util.HashMap; -import java.util.Iterator; +import java.io.Serializable; /** * A subclass of ArrayList that adds an addSorted(Object) method to */ -public class SubnodeList extends ArrayList { +public class SubnodeList implements Serializable { - transient WrappedNodeManager nmgr; - transient HashMap views = null; - transient Relation rel; + Node node; + List list; + + transient long lastSubnodeFetch = 0; + transient long lastSubnodeChange = 0; + /** * Hide/disable zero argument constructor for subclasses @@ -37,21 +39,11 @@ public class SubnodeList extends ArrayList { /** * Creates a new subnode list - * @param nmgr + * @param node the node we belong to */ - public SubnodeList(WrappedNodeManager nmgr, Relation rel) { - this.nmgr = nmgr; - this.rel = rel; - } - - /** - * Inserts the specified element at the specified position in this - * list without performing custom ordering - * - * @param obj element to be inserted. - */ - public boolean addSorted(Object obj) { - return add(obj); + public SubnodeList(Node node) { + this.node = node; + this.list = new ArrayList(); } /** @@ -61,8 +53,7 @@ public class SubnodeList extends ArrayList { * @param obj element to be inserted. */ public boolean add(Object obj) { - addToViews(obj); - return super.add(obj); + return list.add(obj); } /** * Adds the specified object to the list at the given position @@ -70,8 +61,39 @@ public class SubnodeList extends ArrayList { * @param obj the object t add */ public void add(int idx, Object obj) { - addToViews(obj); - super.add(idx, obj); + list.add(idx, obj); + } + + public Object get(int index) { + if (index < 0 || index >= list.size()) { + return null; + } + return list.get(index); + } + + public Node getNode(int index) { + Node retval = null; + NodeHandle handle = (NodeHandle) get(index); + + if (handle != null) { + retval = handle.getNode(node.nmgr); + // Legacy alarm! + if ((retval != null) && (retval.parentHandle == null) && + !node.nmgr.isRootNode(retval)) { + retval.setParent(node); + retval.anonymous = true; + } + } + + return retval; + } + + public boolean contains(Object object) { + return list.contains(object); + } + + public int indexOf(Object object) { + return list.indexOf(object); } /** @@ -79,11 +101,7 @@ public class SubnodeList extends ArrayList { * @param idx the index-position of the NodeHandle to remove */ public Object remove (int idx) { - Object obj = get(idx); - if (obj != null) { - removeFromViews(obj); - } - return super.remove(idx); + return list.remove(idx); } /** @@ -91,40 +109,78 @@ public class SubnodeList extends ArrayList { * @param obj the NodeHandle to remove */ public boolean remove (Object obj) { - removeFromViews(obj); - return super.remove(obj); + return list.remove(obj); } - protected void removeFromViews(Object obj) { - if (views == null || views.isEmpty()) + public Object[] toArray() { + return list.toArray(); + } + + /** + * Return the size of the list. + * @return the list size + */ + public int size() { + return list.size(); + } + + protected void update() { + // also reload if the type mapping has changed. + long lastChange = getLastSubnodeChange(); + if (lastChange != lastSubnodeFetch) { + Relation rel = getSubnodeRelation(); + if (rel.aggressiveLoading && rel.groupby == null) { + list = node.nmgr.getNodes(node, rel); + } else { + list = node.nmgr.getNodeIDs(node, rel); + } + lastSubnodeFetch = lastChange; + } + } + + protected void prefetch(int start, int length) { + if (start < 0 || start >= size()) { return; - for (Iterator i = views.values().iterator(); i.hasNext(); ) { - OrderedSubnodeList osl = (OrderedSubnodeList) i.next(); - osl.remove(obj); } - } - - public SubnodeList getOrderedView (String order) { - String key = order.trim().toLowerCase(); - // long start = System.currentTimeMillis(); - if (views == null) { - views = new HashMap(); - } - OrderedSubnodeList osl = (OrderedSubnodeList) views.get(key); - if (osl == null) { - osl = new OrderedSubnodeList (nmgr, rel, this, order); - views.put(key, osl); - } - return osl; - } - - protected void addToViews (Object obj) { - if (views == null || views.isEmpty()) + length = (length < 0) ? + size() - start : Math.min(length, size() - start); + if (length < 0) { return; - for (Iterator i = views.values().iterator(); i.hasNext(); ) { - OrderedSubnodeList osl = (OrderedSubnodeList) i.next(); - osl.sortIn(obj); } + + DbMapping dbmap = getSubnodeMapping(); + Relation rel = getSubnodeRelation(); + + if (!dbmap.isRelational() || rel.getGroup() != null) { + return; + } + node.nmgr.prefetchNodes(node, rel, this, start, length); } + /** + * Compute a serial number indicating the last change in subnode collection + * @return a serial number that increases with each subnode change + */ + protected long getLastSubnodeChange() { + // include dbmap.getLastTypeChange to also reload if the type mapping has changed. + long checkSum = lastSubnodeChange + node.dbmap.getLastTypeChange(); + Relation rel = getSubnodeRelation(); + return rel.aggressiveCaching ? + checkSum : checkSum + rel.otherType.getLastDataChange(); + } + + protected boolean hasRelationalNodes() { + DbMapping dbmap = getSubnodeMapping(); + return (dbmap != null && dbmap.isRelational() + && ((node.getState() != Node.TRANSIENT && node.getState() != Node.NEW) + || node.getSubnodeRelation() != null)); + } + + protected DbMapping getSubnodeMapping() { + return node.dbmap == null ? null : node.dbmap.getSubnodeMapping(); + } + + protected Relation getSubnodeRelation() { + return node.dbmap == null ? null : node.dbmap.getSubnodeRelation(); + } } diff --git a/src/helma/objectmodel/db/UpdateableSubnodeList.java b/src/helma/objectmodel/db/UpdateableSubnodeList.java deleted file mode 100644 index 2c5f8e03..00000000 --- a/src/helma/objectmodel/db/UpdateableSubnodeList.java +++ /dev/null @@ -1,400 +0,0 @@ -/* - * Helma License Notice - * - * The contents of this file are subject to the Helma License - * Version 2.0 (the "License"). You may not use this file except in - * compliance with the License. A copy of the License is available at - * http://adele.helma.org/download/helma/license.txt - * - * Copyright 1998-2003 Helma Software. All Rights Reserved. - * - * $RCSfile$ - * $Author$ - * $Revision$ - * $Date$ - */ -package helma.objectmodel.db; - -import java.sql.SQLException; -import java.sql.Timestamp; -import java.sql.Types; -import java.util.Collection; -import java.util.Iterator; - -public class UpdateableSubnodeList extends OrderedSubnodeList { - - // the update-criteria-fields - private final String updateCriteria[]; - // the corresponding property to this update-criteria - private final String updateProperty[]; - // records to fetch from the db will have a lower value? - private final boolean updateTypeDesc[]; - - // arrays representing the current borders for each update-criteria - private Object highestValues[]=null; - private Object lowestValues[]=null; - - /** - * Construct a new UpdateableSubnodeList. The Relation is needed - * to get the information about the ORDERING and the UPDATECriteriaS - */ - public UpdateableSubnodeList (WrappedNodeManager nmgr, Relation rel) { - super(nmgr, rel); - // check the update-criterias for updating this collection - if (rel.updateCriteria == null) { - // criteria-field muss vom criteria-operant getrennt werden - // damit das update-criteria-rendering gut funktioniert - updateCriteria = new String[1]; - updateCriteria[0]=rel.otherType.getIDField(); - updateProperty=null; - updateTypeDesc = new boolean[1]; - updateTypeDesc[0] = false; - highestValues = new Object[1]; - lowestValues = new Object[1]; - } else { - String singleCriterias[] = rel.updateCriteria.split(","); - updateCriteria = new String[singleCriterias.length]; - updateProperty = new String[singleCriterias.length]; - updateTypeDesc = new boolean[singleCriterias.length]; - highestValues = new Object[singleCriterias.length]; - lowestValues = new Object[singleCriterias.length]; - for (int i = 0; i < singleCriterias.length; i++) { - parseCriteria (i, singleCriterias[i]); - } - } - } - - /** - * Utility-method for parsing criterias for updating this collection. - */ - private void parseCriteria (int idx, String criteria) { - String criteriaParts[] = criteria.trim().split(" "); - updateCriteria[idx]=criteriaParts[0].trim(); - updateProperty[idx] = rel.otherType.columnNameToProperty(updateCriteria[idx]); - if (updateProperty[idx] == null - && !updateCriteria[idx].equalsIgnoreCase(rel.otherType.getIDField())) { - throw new RuntimeException("updateCriteria has to be mapped as Property of this Prototype (" + updateCriteria[idx] + ")"); - } - if (criteriaParts.length < 2) { - // default to INCREASING or to BIDIRECTIONAL? - updateTypeDesc[idx]=false; - return; - } - String direction = criteriaParts[1].trim().toLowerCase(); - if ("desc".equals(direction)) { - updateTypeDesc[idx]=true; - } else { - updateTypeDesc[idx]=false; - } - } - - /** - * render the criterias for fetching new nodes, which have been added to the - * relational db. - * @return the sql-criteria for updating this subnodelist - * @throws ClassNotFoundException @see helma.objectmodel.db.DbMapping#getColumn(String) - * @throws SQLException @see helma.objectmodel.db.DbMapping#getColumn @see helma.objectmodel.db.DbMapping#needsQuotes(String) - */ - public String getUpdateCriteria() throws SQLException, ClassNotFoundException { - StringBuffer criteria = new StringBuffer (); - for (int i = 0; i < updateCriteria.length; i++) { - // if we don't know the borders ignore this criteria - if (!updateTypeDesc[i] && highestValues[i]==null) - continue; - if (updateTypeDesc[i] && lowestValues[i]==null) - continue; - if (criteria.length() > 0) { - criteria.append (" OR "); - } - renderUpdateCriteria(i, criteria); - } - if (criteria.length() < 1) - return null; - criteria.insert(0, "("); - criteria.append(")"); - return criteria.toString(); - } - - /** - * Render the current updatecriteria specified by idx and add the result to the given - * StringBuffer (sb). - * @param idx index of the current updatecriteria - * @param sb the StringBuffer to append to - * @throws ClassNotFoundException @see helma.objectmodel.db.DbMapping#getColumn(String) - * @throws SQLException @see helma.objectmodel.db.DbMapping#getColumn @see helma.objectmodel.db.DbMapping#needsQuotes(String) - */ - private void renderUpdateCriteria(int idx, StringBuffer sb) throws SQLException, ClassNotFoundException { - if (!updateTypeDesc[idx]) { - sb.append(updateCriteria[idx]); - sb.append(" > "); - renderValue(idx, highestValues, sb); - } else { - sb.append(updateCriteria[idx]); - sb.append(" < "); - renderValue(idx, lowestValues, sb); - } - } - - /** - * Renders the value contained inside the given Array (values) at the given - * index (idx) depending on it's SQL-Type and add the result to the given - * StringBuffer (sb). - * @param idx index-position of the value to render - * @param values the values-array to operate on - * @param sb the StringBuffer to append to - * @throws ClassNotFoundException @see helma.objectmodel.db.DbMapping#getColumn(String) - * @throws SQLException @see helma.objectmodel.db.DbMapping#getColumn @see helma.objectmodel.db.DbMapping#needsQuotes(String) - */ - private void renderValue(int idx, Object[] values, StringBuffer sb) throws SQLException, ClassNotFoundException { - DbColumn dbc = rel.otherType.getColumn(updateCriteria[idx]); - if (rel.otherType.getIDField().equalsIgnoreCase(updateCriteria[idx])) { - if (rel.otherType.needsQuotes(updateCriteria[idx])) { - sb.append("'").append(DbMapping.escapeString(values[idx])).append("'"); - } else { - sb.append(DbMapping.checkNumber(values[idx])); - } - return; - } - Property p = (Property) values[idx]; - String strgVal = p.getStringValue(); - - switch (dbc.getType()) { - case Types.DATE: - case Types.TIME: - case Types.TIMESTAMP: - // use SQL Escape Sequences for JDBC Timestamps - // (http://www.oreilly.com/catalog/jentnut2/chapter/ch02.html) - Timestamp ts = p.getTimestampValue(); - sb.append ("{ts '"); - sb.append (ts.toString()); - sb.append ("'}"); - return; - - case Types.BIT: - case Types.BOOLEAN: - case Types.TINYINT: - case Types.BIGINT: - case Types.SMALLINT: - case Types.INTEGER: - case Types.REAL: - case Types.FLOAT: - case Types.DOUBLE: - case Types.DECIMAL: - case Types.NUMERIC: - case Types.VARBINARY: - case Types.BINARY: - case Types.LONGVARBINARY: - case Types.LONGVARCHAR: - case Types.CHAR: - case Types.VARCHAR: - case Types.OTHER: - case Types.NULL: - case Types.CLOB: - default: - if (rel.otherType.needsQuotes(updateCriteria[idx])) { - sb.append("'").append(DbMapping.escapeString(strgVal)).append("'"); - } else { - sb.append(DbMapping.checkNumber(strgVal)); - } - } - } - - /** - * add a new node honoring the Nodes SQL-Order and check if the borders for - * the updateCriterias have changed by adding this node - * @param obj the object to add - */ - public boolean add(Object obj) { - // we do not have a SQL-Order and add this node on top of the list - NodeHandle nh = (NodeHandle) obj; - updateBorders(nh); - return super.add(nh); - } - - /** - * add a new node at the given index position and check if the - * borders for the updateCriterias have changed - * NOTE: this overrules the ordering (should we disallowe this?) - * @param idx the index-position this node should be added at - * @param obj the NodeHandle of the node which should be added - */ - public void add (int idx, Object obj) { - NodeHandle nh = (NodeHandle) obj; - super.add(idx, nh); - updateBorders(nh); - } - - /** - * Check if the currently added node changes the borers for the updateCriterias and - * update them if neccessary. - * @param nh the added Node - */ - private void updateBorders(NodeHandle nh) { - Node node=null; - for (int i = 0; i < updateCriteria.length; i++) { - node=updateBorder(i, nh, node); - } - } - - /** - * Check if the given NodeHandle's node is outside of the criteria's border - * having the given index-position (idx) and update the border if neccessary. - * @param idx the index-position - * @param nh the NodeHandle possible changing the border - * @param node optional the node handled by this NodeHandler - * @return The Node given or the Node retrieved of this NodeHandle - */ - private Node updateBorder(int idx, NodeHandle nh, Node node) { - String cret = updateCriteria[idx]; - if (rel.otherType.getIDField().equalsIgnoreCase(cret)) { - String nid = nh.getID(); - if (updateTypeDesc[idx] - && OrderedSubnodeList.compareNumericString(nid, (String) lowestValues[idx]) < 0) { - lowestValues[idx]=nid; - } else if (!updateTypeDesc[idx] - && OrderedSubnodeList.compareNumericString(nid, (String) highestValues[idx]) > 0) { - highestValues[idx]=nid; - } - } else { - if (node == null) - node = nh.getNode(rel.otherType.getWrappedNodeManager()); - Property np = node.getProperty( - rel.otherType.columnNameToProperty(cret)); - if (updateTypeDesc[idx]) { - Property lp = (Property) lowestValues[idx]; - if (lp==null || np.compareTo(lp)<0) - lowestValues[idx]=np; - } else { - Property hp = (Property) highestValues[idx]; - if (hp==null || np.compareTo(hp)>0) - highestValues[idx]=np; - } - } - return node; - } - - /** - * First check which borders this node will change and rebuild - * these if neccessary. - * @param nh the NodeHandle of the removed Node - */ - private void rebuildBorders(NodeHandle nh) { - boolean[] check = new boolean[updateCriteria.length]; - Node node = nh.getNode(rel.otherType.getWrappedNodeManager()); - for (int i = 0; i < updateCriteria.length; i++) { - String cret = updateCriteria[i]; - if (cret.equalsIgnoreCase(rel.otherType.getIDField())) { - check[i] = (updateTypeDesc[i] - && nh.getID().equals(lowestValues[i])) - || (!updateTypeDesc[i] - && nh.getID().equals(highestValues[i])); - } else { - Property p = node.getProperty(updateProperty[i]); - check[i] = (updateTypeDesc[i] - && p.equals(lowestValues[i])) - || (!updateTypeDesc[i] - && p.equals(highestValues[i])); - } - } - for (int i = 0; i < updateCriteria.length; i++) { - if (!check[i]) - continue; - rebuildBorder(i); - } - } - - /** - * Rebuild all borders for all the updateCriterias - */ - public void rebuildBorders() { - for (int i = 0; i < updateCriteria.length; i++) { - rebuildBorder(i); - } - } - - /** - * Only rebuild the border for the update-criteria specified by the given - * index-position (idx). - * @param idx the index-position of the updateCriteria to rebuild the border for - */ - private void rebuildBorder(int idx) { - if (updateTypeDesc[idx]) { - lowestValues[idx]=null; - } else { - highestValues[idx]=null; - } - for (int i = 0; i < this.size(); i++) { - updateBorder(idx, (NodeHandle) this.get(i), null); - } - } - - /** - * remove the object specified by the given index-position - * and update the borders if neccesary - * @param idx the index-position of the NodeHandle to remove - */ - public Object remove (int idx) { - Object obj = super.remove(idx); - if (obj == null) - return null; - rebuildBorders((NodeHandle) obj); - return obj; - } - - /** - * remove the given Object from this List and update the borders if neccesary - * @param obj the NodeHandle to remove - */ - public boolean remove (Object obj) { - if (!super.remove(obj)) - return false; - rebuildBorders((NodeHandle) obj); - return true; - } - - /** - * remove all elements conteined inside the specified collection - * from this List and update the borders if neccesary - * @param c the Collection containing all Objects to remove from this List - * @return true if the List has been modified - */ - public boolean removeAll(Collection c) { - if (!super.removeAll(c)) - return false; - for (Iterator i = c.iterator(); i.hasNext(); ) - rebuildBorders((NodeHandle) i.next()); - return true; - } - - /** - * remove all elements from this List, which are NOT specified - * inside the specified Collecion and update the borders if neccesary - * @param c the Collection containing all Objects to keep on the List - * @return true if the List has been modified - */ - public boolean retainAll (Collection c) { - if (!super.retainAll(c)) - return false; - rebuildBorders(); - return true; - } - - /** - * checks if the borders have to be rebuilt because of the removed or - * the added NodeHandle. - */ - public Object set(int idx, Object obj) { - Object prevObj = super.set(idx, obj); - rebuildBorders((NodeHandle) prevObj); - updateBorders((NodeHandle) obj); - return prevObj; - } - - /** - * if the wrapped List is an instance of OrderedSubnodeList, - * the sortIn() method will be used. - */ - public boolean addAll(Collection col) { - return sortIn(col, true) > 0; - } -} diff --git a/src/helma/objectmodel/db/WrappedNodeManager.java b/src/helma/objectmodel/db/WrappedNodeManager.java index 472f6a00..1e327a42 100644 --- a/src/helma/objectmodel/db/WrappedNodeManager.java +++ b/src/helma/objectmodel/db/WrappedNodeManager.java @@ -19,6 +19,7 @@ package helma.objectmodel.db; import helma.objectmodel.ObjectNotFoundException; import java.util.Vector; +import java.util.List; /** * A wrapper around NodeManager that catches most Exceptions, or rethrows them as RuntimeExceptions. @@ -104,11 +105,11 @@ public final class WrappedNodeManager { * @param rel * @return */ - public SubnodeList getNodes(Node home, Relation rel) { + public List getNodes(Node home, Relation rel) { Transactor tx = checkLocalTransactor(); try { beginLocalTransaction(tx, "getNodes"); - SubnodeList list = nmgr.getNodes(home, rel); + List list = nmgr.getNodes(home, rel); commitLocalTransaction(tx); return list; } catch (Exception x) { @@ -125,7 +126,7 @@ public final class WrappedNodeManager { * @param rel * @return */ - public SubnodeList getNodeIDs(Node home, Relation rel) { + public List getNodeIDs(Node home, Relation rel) { try { return nmgr.getNodeIDs(home, rel); } catch (Exception x) { @@ -136,13 +137,13 @@ public final class WrappedNodeManager { /** * @see helma.objectmodel.db.NodeManager#updateSubnodeList(Node, Relation) */ - public int updateSubnodeList (Node home, Relation rel) { + /* public int updateSubnodeList (Node home, Relation rel) { try { return nmgr.updateSubnodeList(home, rel); } catch (Exception x) { throw new RuntimeException("Error retrieving NodeIDs", x); } - } + } */ /** * Count the nodes contained in the given Node's collection @@ -160,6 +161,15 @@ public final class WrappedNodeManager { } } + public void prefetchNodes(Node node, Relation rel, SubnodeList list, + int start, int length) { + try { + nmgr.prefetchNodes(node, rel, list, start, length); + } catch (Exception x) { + throw new RuntimeException("Error prefetching nodes", x); + } + } + /** * Delete a node from the database * diff --git a/src/helma/objectmodel/dom/XmlConverter.java b/src/helma/objectmodel/dom/XmlConverter.java index aa8a2c5c..e27f711d 100644 --- a/src/helma/objectmodel/dom/XmlConverter.java +++ b/src/helma/objectmodel/dom/XmlConverter.java @@ -17,7 +17,6 @@ package helma.objectmodel.dom; import helma.objectmodel.INode; -import helma.objectmodel.TransientNode; import helma.util.SystemProperties; import org.w3c.dom.*; import org.xml.sax.InputSource; @@ -75,17 +74,6 @@ public class XmlConverter implements XmlConstants { extractProperties(props); } - /** - * - * - * @param desc ... - * - * @return ... - */ - public INode convert(String desc) { - return convert(desc, new TransientNode()); - } - /** * * diff --git a/src/helma/objectmodel/dom/XmlDatabaseReader.java b/src/helma/objectmodel/dom/XmlDatabaseReader.java index b25bef91..bbd83e4f 100644 --- a/src/helma/objectmodel/dom/XmlDatabaseReader.java +++ b/src/helma/objectmodel/dom/XmlDatabaseReader.java @@ -123,7 +123,7 @@ public final class XmlDatabaseReader extends DefaultHandler implements XmlConsta subnodes = currentNode.createSubnodeList(); } - subnodes.addSorted(handle); + subnodes.add(handle); } else if ("hop:parent".equals(qName)) { currentNode.setParentHandle(handle); } else { diff --git a/src/helma/objectmodel/dom/XmlWriter.java b/src/helma/objectmodel/dom/XmlWriter.java index e73b7954..43a3ecfa 100644 --- a/src/helma/objectmodel/dom/XmlWriter.java +++ b/src/helma/objectmodel/dom/XmlWriter.java @@ -19,7 +19,6 @@ package helma.objectmodel.dom; import helma.objectmodel.INode; import helma.objectmodel.IProperty; -import helma.objectmodel.TransientNode; import helma.objectmodel.INodeState; import helma.objectmodel.db.DbMapping; import helma.objectmodel.db.Node; diff --git a/src/helma/scripting/rhino/HopObject.java b/src/helma/scripting/rhino/HopObject.java index ae320055..47b8b245 100644 --- a/src/helma/scripting/rhino/HopObject.java +++ b/src/helma/scripting/rhino/HopObject.java @@ -33,8 +33,8 @@ import java.io.IOException; */ public class HopObject extends ScriptableObject implements Wrapper, PropertyRecorder { - final String className; - INode node; + String className; + final NodeProxy proxy; RhinoCore core; // fields to implement PropertyRecorder @@ -48,10 +48,12 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @param core the RhinoCore */ protected HopObject(String className, RhinoCore core) { - this(className, core, null, null); + this.className = className; + this.core = core; + this.proxy = null; + setParentScope(core.global); } - /** * Creates a new HopObject. * @@ -64,7 +66,25 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco INode node, Scriptable proto) { this.className = className; this.core = core; - this.node = node; + this.proxy = new NodeProxy(node); + if (proto != null) + setPrototype(proto); + setParentScope(core.global); + } + + /** + * Creates a new HopObject. + * + * @param className the className + * @param core the RhinoCore + * @param handle the handle for the wrapped node + * @param proto the object's prototype + */ + protected HopObject(String className, RhinoCore core, + NodeHandle handle, Scriptable proto) { + this.className = className; + this.core = core; + this.proxy = new NodeProxy(handle); if (proto != null) setPrototype(proto); setParentScope(core.global); @@ -137,7 +157,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return the default value for the object */ public Object getDefaultValue(Class hint) { - return node == null ? toString() : node.toString(); + return proxy == null ? toString() : proxy.getNode().toString(); } /** @@ -146,10 +166,10 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return the wrapped INode instance */ public INode getNode() { - if (node != null) { - checkNode(); + if (proxy != null) { + return proxy.getNode(); } - return node; + return null; } /** @@ -157,51 +177,25 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * */ public Object unwrap() { - if (node != null) { - checkNode(); - return node; + if (proxy != null) { + return proxy.getNode(); } else { return this; } } - /** - * 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 != null && node.getState() == INode.INVALID) { - if (node instanceof Node) { - NodeHandle handle = ((Node) node).getHandle(); - node = handle.getNode(core.app.getWrappedNodeManager()); - if (node == null) { - // we probably have a deleted node. Replace with empty transient node - // to avoid throwing an exception. - node = new TransientNode(); - // throw new RuntimeException("Tried to access invalid/removed node " + handle + "."); - } - } - } - } - - /** * * * @return ... */ public Object jsGet_cache() { - if (node == null) { - return null; + if (proxy != null) { + INode cache = proxy.getNode().getCacheNode(); + if (cache != null) { + return Context.toObject(cache, core.global); + } } - - checkNode(); - - INode cache = node.getCacheNode(); - if (cache != null) { - return Context.toObject(node.getCacheNode(), core.global); - } - return null; } @@ -218,7 +212,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco RhinoEngine engine = RhinoEngine.getRhinoEngine(); Skin skin = engine.toSkin(skinobj, className); - checkNode(); + INode node = getNode(); if (skin != null) { skin.render(engine.reval, node, @@ -287,7 +281,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco throws UnsupportedEncodingException, IOException { RhinoEngine engine = RhinoEngine.getRhinoEngine(); Skin skin = engine.toSkin(skinobj, className); - checkNode(); + INode node = getNode(); if (skin != null) { return skin.renderAsString(engine.reval, node, @@ -306,13 +300,12 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco */ public Object jsFunction_href(Object action) throws UnsupportedEncodingException, IOException { - if (node == null) { + if (proxy == null) { return null; } String act = null; - - checkNode(); + INode node = getNode(); if (action != null) { if (action instanceof Wrapper) { @@ -335,24 +328,22 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return ... */ public Object jsFunction_get(Object id) { - if ((node == null) || (id == null)) { + if (proxy == null || id == null || id == Undefined.instance) { return null; } - Object n = null; - - checkNode(); + Object child; + INode node = getNode(); if (id instanceof Number) { - n = node.getSubnodeAt(((Number) id).intValue()); + child = node.getSubnodeAt(((Number) id).intValue()); } else { - n = node.getChildElement(id.toString()); + child = node.getChildElement(id.toString()); } - if (n != null) { - return Context.toObject(n, core.global); + if (child != null) { + return Context.toObject(child, core.global); } - return null; } @@ -364,12 +355,11 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return ... */ public Object jsFunction_getById(Object id) { - if ((node == null) || (id == null) || id == Undefined.instance) { + if (proxy == null || id == null || id == Undefined.instance) { return null; } - checkNode(); - + INode node = getNode(); String idString = (id instanceof Double) ? Long.toString(((Double) id).longValue()) : id.toString(); @@ -393,17 +383,16 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco if (id == Undefined.instance || value == Undefined.instance) { throw new EvaluatorException("HopObject.set() called with wrong number of arguments"); } - if ((node == null)) { + if (proxy == null) { return false; } if (id instanceof Number) { - - if (!(value instanceof HopObject)) { + if (!(value instanceof HopObject)) { throw new EvaluatorException("Can only set HopObjects as child objects in HopObject.set()"); } - checkNode(); + INode node = getNode(); int idx = (((Number) id).intValue()); INode n = ((HopObject) value).getNode(); @@ -423,12 +412,10 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return ... */ public int jsFunction_count() { - if (node == null) { + if (proxy == null) { return 0; } - - checkNode(); - + INode node = getNode(); return node.numberOfNodes(); } @@ -456,16 +443,11 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco } private void prefetchChildren(int start, int length) { - if (!(node instanceof Node)) { - return; - } - - checkNode(); - - try { - ((Node) node).prefetchChildren(start, length); - } catch (Exception x) { - core.app.logError("Error in HopObject.prefetchChildren: " + x, x); + if (proxy != null) { + INode node = getNode(); + if (node instanceof Node) { + ((Node) node).prefetchChildren(start, length); + } } } @@ -473,9 +455,10 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * Clear the node's cache node. */ public void jsFunction_clearCache() { - checkNode(); - - node.clearCacheNode(); + if (proxy != null) { + INode node = getNode(); + node.clearCacheNode(); + } } /** @@ -485,19 +468,17 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return A JavaScript Array containing all child objects */ private Scriptable list() { - checkNode(); - - // prefetchChildren(0, 1000); - Enumeration e = node.getSubnodes(); - ArrayList a = new ArrayList(); - - while ((e != null) && e.hasMoreElements()) { - Object obj = e.nextElement(); - if (obj != null) - a.add(Context.toObject(obj, core.global)); + Node node = (Node) getNode(); + node.loadNodes(); + SubnodeList list = node.getSubnodeList(); + if (list == null) { + return Context.getCurrentContext().newArray(core.global, 0); } - - return Context.getCurrentContext().newArray(core.global, a.toArray()); + Object[] array = list.toArray(); + for (int i = 0; i < array.length; i++) { + array[i] = core.getNodeWrapper((NodeHandle) array[i]); + } + return Context.getCurrentContext().newArray(core.global, array); } /** @@ -517,19 +498,23 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco throw new EvaluatorException("Arguments must not be negative in HopObject.list(start, length)"); } - checkNode(); - + Node node = (Node) getNode(); prefetchChildren(start, length); - ArrayList a = new ArrayList(); + SubnodeList list = node.getSubnodeList(); + length = Math.min(list.size() - start, length); + if (length <= 0) { + return Context.getCurrentContext().newArray(core.global, 0); + } + Object[] array = new Object[length]; - for (int i=start; i 0) { + if (proxy != null && name != null && name.length() > 0) { - checkNode(); + INode node = getNode(); // Property names starting with an underscore is interpreted // as internal properties if (name.charAt(0) == '_') { - Object value = getInternalProperty(name); + Object value = getInternalProperty(node, name); if (value != NOT_FOUND) return value; } @@ -931,7 +909,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco return NOT_FOUND; } - private Object getInternalProperty(String name) { + private Object getInternalProperty(INode node, String name) { if ("__id__".equals(name) || "_id".equals(name)) { return node.getID(); } @@ -981,7 +959,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return array containing the names of all properties defined in this object */ public Object[] getAllIds() { - if (node == null) { + if (proxy == null) { return super.getAllIds(); } return getIds(); @@ -992,14 +970,14 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return array containing the names of this object's data properties */ public Object[] getIds() { - if (node == null) { + if (proxy == null) { // HopObject prototypes always return an empty array in order not to // pollute actual HopObjects properties. Call getAllIds() to get // a list of properties from a HopObject prototype. return new Object[0]; } - checkNode(); + INode node = getNode(); Enumeration en = node.properties(); ArrayList list = new ArrayList(); @@ -1019,8 +997,8 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return ... */ public boolean has(int idx, Scriptable start) { - if (node != null) { - checkNode(); + if (proxy != null) { + INode node = getNode(); return (0 <= idx && idx < node.numberOfNodes()); } @@ -1037,8 +1015,8 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return ... */ public Object get(int idx, Scriptable start) { - if (node != null) { - checkNode(); + if (proxy != null) { + INode node = getNode(); INode n = node.getSubnodeAt(idx); @@ -1050,15 +1028,33 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco return NOT_FOUND; } + /** + * Custom == operator. + * Must return {@link org.mozilla.javascript.Scriptable#NOT_FOUND} if this object does not + * have custom equality operator for the given value, + * Boolean.TRUE if this object is equivalent to value, + * Boolean.FALSE if this object is not equivalent to + * value. + */ + protected Object equivalentValues(Object value) { + if (value == this) { + return Boolean.TRUE; + } + if (value instanceof HopObject && proxy != null) { + return proxy.equivalentValues(((HopObject) value).proxy); + } + return Scriptable.NOT_FOUND; + } + /** * Return a string representation of this HopObject. * @return a string representing this HopObject */ public String toString() { - if (node == null) { + if (proxy == null) { return "[HopObject prototype " + className + "]"; } else { - return "[HopObject " + node.getName() + "]"; + return "[HopObject " + proxy.getNode().getName() + "]"; } } @@ -1101,13 +1097,13 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * .) the id's of this collection must be in ascending order, meaning, that new records * do have a higher id than the last record loaded by this collection */ - public int jsFunction_update() { + /* public int jsFunction_update() { if (!(node instanceof Node)) throw new RuntimeException ("update only callabel on persistent HopObjects"); checkNode(); Node n = (Node) node; return n.updateSubnodes(); - } + } */ /** * Retrieve a view having a different order from this Node's subnodelist. @@ -1116,7 +1112,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @param expr the order (like sql-order using the properties instead) * @return ListViewWrapper holding the information of the ordered view */ - public Object jsFunction_getOrderedView(String expr) { + /* public Object jsFunction_getOrderedView(String expr) { if (!(node instanceof Node)) { throw new RuntimeException ( "getOrderedView only callable on persistent HopObjects"); @@ -1131,5 +1127,55 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco Node subnode = new Node("OrderedView", "HopObject", core.app.getWrappedNodeManager()); subnode.setSubnodes(subnodes.getOrderedView(expr)); return new HopObject("HopObject", core, subnode, core.getPrototype("HopObject")); + } */ + + class NodeProxy { + INode node; + NodeHandle handle; + + NodeProxy(INode node) { + this.node = node; + if (node instanceof Node) { + handle = ((Node) node).getHandle(); + } + } + + NodeProxy(NodeHandle handle) { + this.handle = handle; + } + + INode getNode() { + if (node == null || node.getState() == Node.INVALID) { + if (handle != null) { + node = handle.getNode(core.app.getWrappedNodeManager()); + String protoname = node.getPrototype(); + // the actual prototype name may vary from the node handle's prototype name + if (className == null || !className.equals(protoname)) { + Scriptable proto = core.getValidPrototype(protoname); + if (proto == null) { + protoname = "HopObject"; + proto = core.getValidPrototype("HopObject"); + } + className = protoname; + setPrototype(proto); + } + } else { + // we probably have a deleted node. Replace with empty transient node + // to avoid throwing an exception. + node = new Node("DeletedNode", null, core.app.getWrappedNodeManager()); + } + } + return node; + } + + public Boolean equivalentValues(NodeProxy other) { + if (handle == null) { + return other.node == this.node ? + Boolean.TRUE : Boolean.FALSE; + } else { + return handle.equals(other.handle) ? + Boolean.TRUE : Boolean.FALSE; + } + } } } diff --git a/src/helma/scripting/rhino/RhinoCore.java b/src/helma/scripting/rhino/RhinoCore.java index 33d8ca35..ca975e12 100644 --- a/src/helma/scripting/rhino/RhinoCore.java +++ b/src/helma/scripting/rhino/RhinoCore.java @@ -22,9 +22,26 @@ import helma.framework.core.*; import helma.framework.repository.Resource; import helma.objectmodel.*; import helma.objectmodel.db.DbMapping; +import helma.objectmodel.db.NodeHandle; import helma.scripting.*; import helma.util.*; -import org.mozilla.javascript.*; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.ContextAction; +import org.mozilla.javascript.ContextFactory; +import org.mozilla.javascript.BaseFunction; +import org.mozilla.javascript.EvaluatorException; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.JavaScriptException; +import org.mozilla.javascript.LazilyLoadedCtor; +import org.mozilla.javascript.NativeArray; +import org.mozilla.javascript.NativeJavaObject; +import org.mozilla.javascript.NativeObject; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.ScriptRuntime; +import org.mozilla.javascript.Undefined; +import org.mozilla.javascript.WrapFactory; +import org.mozilla.javascript.Wrapper; import org.mozilla.javascript.tools.debugger.ScopeProvider; import java.io.*; @@ -637,15 +654,15 @@ public final class RhinoCore implements ScopeProvider { /** * Get a script wrapper for an instance of helma.objectmodel.INode */ - public Scriptable getNodeWrapper(INode n) { - if (n == null) { + public Scriptable getNodeWrapper(INode node) { + if (node == null) { return null; } - HopObject hobj = (HopObject) wrappercache.get(n); + HopObject hobj = (HopObject) wrappercache.get(node); if (hobj == null) { - String protoname = n.getPrototype(); + String protoname = node.getPrototype(); Scriptable op = getValidPrototype(protoname); // no prototype found for this node @@ -654,7 +671,7 @@ public final class RhinoCore implements ScopeProvider { // deleted, but the storage layer was able to set a // DbMapping matching the relational table the object // was fetched from. - DbMapping dbmap = n.getDbMapping(); + DbMapping dbmap = node.getDbMapping(); if (dbmap != null && (protoname = dbmap.getTypeName()) != null) { op = getValidPrototype(protoname); } @@ -666,13 +683,42 @@ public final class RhinoCore implements ScopeProvider { } } - hobj = new HopObject(protoname, this, n, op); - wrappercache.put(n, hobj); + hobj = new HopObject(protoname, this, node, op); + wrappercache.put(node, hobj); } return hobj; } + /** + * Get a node wrapper for a node that may not have been fetched yet + * @param handle a node handle + * @return a wrapper for the node + */ + public Scriptable getNodeWrapper(NodeHandle handle) { + Scriptable hobj = (HopObject) wrappercache.get(handle); + if (hobj != null) { + return hobj; + } else if (handle.hasNode()) { + hobj = getNodeWrapper(handle.getNode(app.getWrappedNodeManager())); + } + + if (hobj == null) { + String protoName = handle.getKey().getStorageName(); + Scriptable op = getValidPrototype(protoName); + + // no prototype found for this node + if (op == null) { + protoName = "HopObject"; + op = getValidPrototype("HopObject"); + } + hobj = new HopObject(protoName, this, handle, op); + } + wrappercache.put(handle, hobj); + return hobj; + } + + protected String postProcessHref(Object obj, String protoName, String href) throws UnsupportedEncodingException, IOException { // check if the app.properties specify a href-function to post-process the @@ -1057,6 +1103,9 @@ public final class RhinoCore implements ScopeProvider { if (obj instanceof INode) { return getNodeWrapper((INode) obj); } + if (obj instanceof NodeHandle) { + return getNodeWrapper((NodeHandle) obj); + } // Masquerade SystemMap and WrappedMap as native JavaScript objects if (obj instanceof SystemMap || obj instanceof WrappedMap) { diff --git a/src/helma/scripting/rhino/SerializationProxy.java b/src/helma/scripting/rhino/SerializationProxy.java index ab7b72e5..ef49bd4b 100644 --- a/src/helma/scripting/rhino/SerializationProxy.java +++ b/src/helma/scripting/rhino/SerializationProxy.java @@ -85,7 +85,7 @@ class HopObjectProxy implements SerializationProxy { ref = obj.getClassName(); } else { if (n instanceof Node) { - ref = new NodeHandle((Node) n); + ref = ((Node) n).getHandle(); } else { ref = n; } diff --git a/src/helma/util/ResourceProperties.java b/src/helma/util/ResourceProperties.java index 3a375f24..0813f14d 100644 --- a/src/helma/util/ResourceProperties.java +++ b/src/helma/util/ResourceProperties.java @@ -46,7 +46,7 @@ public class ResourceProperties extends Properties { private long lastCheck = 0; // Time porperties were last modified - private long lastModified = 0; + private long lastModified = System.currentTimeMillis(); // Application where to fetch additional resources private Application app;