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:
parent
8b65614827
commit
1af914b6b1
21 changed files with 831 additions and 2560 deletions
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ public interface Key {
|
|||
public String getID();
|
||||
|
||||
/**
|
||||
* Get the key's storage id
|
||||
* Get the key's storage type name
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
255
src/helma/objectmodel/db/SegmentedSubnodeList.java
Normal file
255
src/helma/objectmodel/db/SegmentedSubnodeList.java
Normal 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 + "}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue