Merge lazy_collections branch.

* Remove helma.objectmodel.TransientNode, replace it with transient db.Nodes
 * Optimize code path for NodeManager.prefetchNodes()
 * Refactor HopObject to allow wrapping of Nodes that aren't loaded yet
 * Do not load all child objects in HopObject.list()
 * Prefetch the requested child object range in HopObject.list(start, length)
 * Implement segmented loading of collection keys for very large (> 10000 elements) collections
 * Make sure lastModified field is set in ResourceProperties, and lastTypeChange in colleciton mappings
This commit is contained in:
hns 2009-04-17 14:49:26 +00:00
parent 8b65614827
commit 1af914b6b1
21 changed files with 831 additions and 2560 deletions

View file

@ -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");

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -38,7 +38,7 @@ public interface Key {
public String getID();
/**
* Get the key's storage id
* Get the key's storage type name
*
* @return ...
*/

View file

@ -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.

View file

@ -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:
* <ol>
* <li> For transient nodes there exists only one NodeHandle.</li>
* <li> If a transient node becomes persistent its node handle is notified and
* converted into a persistent NodeHandle.</li>
* </ol>
* 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);
}
/**
* Get the key for the node described by this handle.
* This may only be called on persistent Nodes.
* 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 Key getKey() {
if (key == null) {
throw new RuntimeException("getKey called on transient Node");
public boolean hasNode() {
return node != null;
}
/**
* Get the key for the node described by this handle.
* This will return null for transient Nodes.
*/
public Key getKey() {
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;
}
/**

View file

@ -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,18 +980,17 @@ 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();
}
List retval = new ArrayList();
DbMapping dbm = rel.otherType;
Connection con = dbm.getConnection();
@ -1030,7 +1029,7 @@ public final class NodeManager {
}
Key primKey = node.getKey();
retval.addSorted(new NodeHandle(primKey));
retval.add(new NodeHandle(primKey));
registerNewNode(node, null);
@ -1053,7 +1052,6 @@ public final class NodeManager {
return retval;
}
}
/**
* Update a UpdateableSubnodeList retrieving all values having
@ -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 {

View file

@ -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);
}
}
}

View file

@ -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,

View file

@ -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 + "}";
}
}
}

View file

@ -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 {
Node node;
List list;
transient long lastSubnodeFetch = 0;
transient long lastSubnodeChange = 0;
transient WrappedNodeManager nmgr;
transient HashMap views = null;
transient Relation rel;
/**
* 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();
}
}

View file

@ -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;
}
}

View file

@ -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
*

View file

@ -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());
}
/**
*
*

View file

@ -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 {

View file

@ -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;

View file

@ -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;
}
checkNode();
INode cache = node.getCacheNode();
if (proxy != null) {
INode cache = proxy.getNode().getCacheNode();
if (cache != null) {
return Context.toObject(node.getCacheNode(), core.global);
return Context.toObject(cache, 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)) {
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 {
if (proxy != null) {
INode node = getNode();
if (node instanceof Node) {
((Node) node).prefetchChildren(start, length);
} catch (Exception x) {
core.app.logError("Error in HopObject.prefetchChildren: " + x, x);
}
}
}
@ -473,10 +455,11 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
* Clear the node's cache node.
*/
public void jsFunction_clearCache() {
checkNode();
if (proxy != null) {
INode node = getNode();
node.clearCacheNode();
}
}
/**
* Return the full list of child objects in a JavaScript Array.
@ -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<start+length; i++) {
INode n = node.getSubnodeAt(i);
if (n != null) {
a.add(Context.toObject(n, core.global));
for (int i = 0; i < length; i++) {
Object obj = list.get(start + i);
if (obj != null) {
array[i] = Context.toObject(obj, core.global);
}
}
return Context.getCurrentContext().newArray(core.global, a.toArray());
return Context.getCurrentContext().newArray(core.global, array);
}
/**
@ -540,19 +525,17 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
* @return ...
*/
public boolean jsFunction_add(Object child) {
if ((node == null) || (child == null)) {
if (proxy == null || child == null) {
return false;
}
checkNode();
INode node = getNode();
if (child instanceof HopObject) {
node.addNode(((HopObject) child).node);
node.addNode(((HopObject) child).getNode());
return true;
} else if (child instanceof INode) {
node.addNode((INode) child);
return true;
}
@ -572,10 +555,10 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
return false;
}
checkNode();
INode node = getNode();
if (child instanceof HopObject) {
node.addNode(((HopObject) child).node, index);
node.addNode(((HopObject) child).getNode(), index);
return true;
} else if (child instanceof INode) {
@ -605,7 +588,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
throw new RuntimeException("Caught deprecated usage of HopObject.remove(child)");
}
checkNode();
INode node = getNode();
return node.remove();
}
@ -616,13 +599,13 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
*/
public boolean jsFunction_removeChild(Object child) {
checkNode();
INode node = getNode();
if (child instanceof HopObject) {
HopObject hobj = (HopObject) child;
if (hobj.node != null) {
node.removeNode(hobj.node);
if (hobj.proxy != null) {
node.removeNode(hobj.getNode());
return true;
}
}
@ -637,7 +620,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
*/
public Object jsFunction_persist() {
checkNode();
INode node = getNode();
if (node instanceof Node) {
((Node) node).persist();
@ -650,18 +633,17 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
* Invalidate the node itself or a subnode
*/
public boolean jsFunction_invalidate(Object childId) {
if (childId != null && node instanceof Node) {
if (childId != null && proxy != null) {
INode node = getNode();
if (!(node instanceof Node)) {
return true;
}
if (childId == Undefined.instance) {
if (node.getState() == INode.INVALID) {
return true;
}
((Node) node).invalidate();
} else {
checkNode();
((Node) node).invalidateNode(childId.toString());
}
}
@ -676,10 +658,10 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
* @return true if the the wrapped Node has a valid database id.
*/
public boolean jsFunction_isPersistent() {
if (!(node instanceof Node)) {
if (proxy == null) {
return false;
}
checkNode();
INode node = getNode();
int nodeState = node.getState();
return nodeState != INode.TRANSIENT;
}
@ -691,10 +673,10 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
* @return true if the the wrapped Node is not stored in a database.
*/
public boolean jsFunction_isTransient() {
if (!(node instanceof Node)) {
if (proxy == null) {
return true;
}
checkNode();
INode node = getNode();
int nodeState = node.getState();
return nodeState == INode.TRANSIENT;
}
@ -705,14 +687,10 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
*/
public int jsFunction_indexOf(Object obj) {
checkNode();
if ((node != null) && obj instanceof HopObject) {
checkNode();
return node.contains(((HopObject) obj).node);
if (proxy != null && obj instanceof HopObject) {
INode node = getNode();
return node.contains(((HopObject) obj).getNode());
}
return -1;
}
@ -733,7 +711,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
* @param value ...
*/
public void put(String name, Scriptable start, Object value) {
if (node == null) {
if (proxy == null) {
// redirect the scripted constructor to __constructor__,
// constructor is set to the native constructor method.
if ("constructor".equals(name) && value instanceof NativeFunction) {
@ -763,7 +741,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
// use ScriptableObject.put to set it
super.put(name, start, value);
} else {
checkNode();
INode node = getNode();
if ("subnodeRelation".equals(name)) {
node.setSubnodeRelation(value == null ? null : value.toString());
@ -813,8 +791,8 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
* @return true if the property was found
*/
public boolean has(String name, Scriptable start) {
if (node != null) {
checkNode();
if (proxy != null) {
INode node = getNode();
if (node.get(name) != null) {
return true;
}
@ -828,8 +806,8 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
* @param name ...
*/
public void delete(String name) {
if ((node != null)) {
checkNode();
if ((proxy != null)) {
INode node = getNode();
node.unset(name);
}
super.delete(name);
@ -845,7 +823,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
*/
public Object get(String name, Scriptable start) {
Object obj = super.get(name, start);
if (obj == Scriptable.NOT_FOUND && node != null) {
if (obj == Scriptable.NOT_FOUND && proxy != null) {
obj = getFromNode(name);
}
return obj;
@ -857,14 +835,14 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
* to return the prototype functions in that case.
*/
private Object getFromNode(String name) {
if (node != null && name != null && name.length() > 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 <tt>==</tt> operator.
* Must return {@link org.mozilla.javascript.Scriptable#NOT_FOUND} if this object does not
* have custom equality operator for the given value,
* <tt>Boolean.TRUE</tt> if this object is equivalent to <tt>value</tt>,
* <tt>Boolean.FALSE</tt> if this object is not equivalent to
* <tt>value</tt>.
*/
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;
}
}
}
}

View file

@ -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) {

View file

@ -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;
}

View file

@ -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;