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:
+ *
+ * - For transient nodes there exists only one NodeHandle.
+ * - If a transient node becomes persistent its node handle is notified and
+ * converted into a persistent NodeHandle.
+ *
+ * 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;