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();
|
dbSources = new Hashtable();
|
||||||
modules = new SystemMap();
|
modules = new SystemMap();
|
||||||
|
|
||||||
cachenode = new TransientNode("app");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -455,6 +453,9 @@ public final class Application implements Runnable {
|
||||||
nmgr = new NodeManager(Application.this);
|
nmgr = new NodeManager(Application.this);
|
||||||
nmgr.init(dbDir.getAbsoluteFile(), props);
|
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
|
// create and init session manager
|
||||||
String sessionMgrImpl = props.getProperty("sessionManagerImpl",
|
String sessionMgrImpl = props.getProperty("sessionManagerImpl",
|
||||||
"helma.framework.core.SessionManager");
|
"helma.framework.core.SessionManager");
|
||||||
|
|
|
@ -67,7 +67,7 @@ public class Session implements Serializable {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.uid = null;
|
this.uid = null;
|
||||||
this.userHandle = null;
|
this.userHandle = null;
|
||||||
cacheNode = new TransientNode("session");
|
cacheNode = new Node("session", null, app.getWrappedNodeManager());
|
||||||
onSince = System.currentTimeMillis();
|
onSince = System.currentTimeMillis();
|
||||||
lastTouched = lastModified = onSince;
|
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();
|
public String getID();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the key's storage id
|
* Get the key's storage type name
|
||||||
*
|
*
|
||||||
* @return ...
|
* @return ...
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -22,7 +22,6 @@ import helma.framework.core.Application;
|
||||||
import helma.objectmodel.ConcurrencyException;
|
import helma.objectmodel.ConcurrencyException;
|
||||||
import helma.objectmodel.INode;
|
import helma.objectmodel.INode;
|
||||||
import helma.objectmodel.IProperty;
|
import helma.objectmodel.IProperty;
|
||||||
import helma.objectmodel.TransientNode;
|
|
||||||
import helma.util.EmptyEnumeration;
|
import helma.util.EmptyEnumeration;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -65,14 +64,11 @@ public final class Node implements INode, Serializable {
|
||||||
transient DbMapping dbmap;
|
transient DbMapping dbmap;
|
||||||
transient Key primaryKey = null;
|
transient Key primaryKey = null;
|
||||||
transient String subnodeRelation = null;
|
transient String subnodeRelation = null;
|
||||||
transient long lastSubnodeFetch = 0;
|
|
||||||
transient long lastSubnodeChange = 0;
|
|
||||||
transient long lastNameCheck = 0;
|
transient long lastNameCheck = 0;
|
||||||
transient long lastParentSet = 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 Transactor lock;
|
||||||
transient private volatile int state;
|
transient private volatile int state;
|
||||||
|
private static long idgen = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an empty, uninitialized Node. The init() method must be called on the
|
* 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();
|
DbMapping smap = (dbmap == null) ? null : dbmap.getSubnodeMapping();
|
||||||
|
|
||||||
if ((smap != null) && smap.isRelational()) {
|
if (smap != null && smap.isRelational()) {
|
||||||
out.writeObject(null);
|
out.writeObject(null);
|
||||||
} else {
|
} else {
|
||||||
out.writeObject(subnodes);
|
out.writeObject(subnodes);
|
||||||
|
@ -368,7 +364,9 @@ public final class Node implements INode, Serializable {
|
||||||
* child index as changed
|
* child index as changed
|
||||||
*/
|
*/
|
||||||
public void markSubnodesChanged() {
|
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() {
|
public String getID() {
|
||||||
// if we are transient, we generate an id on demand. It's possible that we'll never need
|
// 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.
|
// it, but if we do it's important to keep the one we have.
|
||||||
if ((state == TRANSIENT) && (id == null)) {
|
if (state == TRANSIENT && id == null) {
|
||||||
id = TransientNode.generateID();
|
id = generateTransientID();
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
@ -636,8 +634,8 @@ public final class Node implements INode, Serializable {
|
||||||
* @param rel ...
|
* @param rel ...
|
||||||
*/
|
*/
|
||||||
public synchronized void setSubnodeRelation(String rel) {
|
public synchronized void setSubnodeRelation(String rel) {
|
||||||
if (((rel == null) && (this.subnodeRelation == null)) ||
|
if ((rel == null && this.subnodeRelation == null) ||
|
||||||
((rel != null) && rel.equalsIgnoreCase(this.subnodeRelation))) {
|
(rel != null && rel.equalsIgnoreCase(this.subnodeRelation))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -646,9 +644,8 @@ public final class Node implements INode, Serializable {
|
||||||
|
|
||||||
DbMapping smap = (dbmap == null) ? null : dbmap.getSubnodeMapping();
|
DbMapping smap = (dbmap == null) ? null : dbmap.getSubnodeMapping();
|
||||||
|
|
||||||
if ((smap != null) && smap.isRelational()) {
|
if (subnodes != null && smap != null && smap.isRelational()) {
|
||||||
subnodes = null;
|
subnodes = null;
|
||||||
subnodeCount = -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1166,20 +1163,7 @@ public final class Node implements INode, Serializable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Node retval = null;
|
return subnodes.getNode(index);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Node getGroupbySubnode(Node node, boolean create) {
|
protected Node getGroupbySubnode(Node node, boolean create) {
|
||||||
|
@ -1232,7 +1216,7 @@ public final class Node implements INode, Serializable {
|
||||||
loadNodes();
|
loadNodes();
|
||||||
|
|
||||||
if (subnodes == null) {
|
if (subnodes == null) {
|
||||||
subnodes = new SubnodeList(nmgr, dbmap.getSubnodeRelation());
|
subnodes = new SubnodeList(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (create || subnodes.contains(new NodeHandle(new SyntheticKey(getKey(), sid)))) {
|
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.
|
* may actually load their IDs in order to do this.
|
||||||
*/
|
*/
|
||||||
public int numberOfNodes() {
|
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();
|
loadNodes();
|
||||||
|
|
||||||
return (subnodes == null) ? 0 : subnodes.size();
|
return (subnodes == null) ? 0 : subnodes.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1562,32 +1515,19 @@ public final class Node implements INode, Serializable {
|
||||||
*/
|
*/
|
||||||
public void loadNodes() {
|
public void loadNodes() {
|
||||||
// Don't do this for transient nodes which don't have an explicit subnode relation set
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DbMapping subMap = (dbmap == null) ? null : dbmap.getSubnodeMapping();
|
DbMapping subMap = (dbmap == null) ? null : dbmap.getSubnodeMapping();
|
||||||
|
|
||||||
if ((subMap != null) && subMap.isRelational()) {
|
if (subMap != null && subMap.isRelational()) {
|
||||||
// check if subnodes need to be reloaded
|
// check if subnodes need to be reloaded
|
||||||
Relation subRel = dbmap.getSubnodeRelation();
|
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
// also reload if the type mapping has changed.
|
if (subnodes == null) {
|
||||||
long lastChange = getLastSubnodeChange(subRel);
|
createSubnodeList();
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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
|
* @return List an empty List of the type used by this Node
|
||||||
*/
|
*/
|
||||||
public SubnodeList createSubnodeList() {
|
public SubnodeList createSubnodeList() {
|
||||||
Relation rel = this.dbmap == null ? null : this.dbmap.getSubnodeRelation();
|
subnodes = new SegmentedSubnodeList(this);
|
||||||
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);
|
|
||||||
}
|
|
||||||
return subnodes;
|
return subnodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute a serial number indicating the last change in subnode collection
|
* 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
|
* @return a serial number that increases with each subnode change
|
||||||
*/
|
*/
|
||||||
long getLastSubnodeChange(Relation subRel) {
|
long getLastSubnodeChange() {
|
||||||
// include dbmap.getLastTypeChange to also reload if the type mapping has changed.
|
// TODO check if we should compute this on demand
|
||||||
long checkSum = lastSubnodeChange + dbmap.getLastTypeChange();
|
return subnodes == null ? 0 : subnodes.getLastSubnodeChange();
|
||||||
return subRel.aggressiveCaching ?
|
|
||||||
checkSum : checkSum + subRel.otherType.getLastDataChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1629,65 +1559,46 @@ public final class Node implements INode, Serializable {
|
||||||
*
|
*
|
||||||
* @throws Exception ...
|
* @throws Exception ...
|
||||||
*/
|
*/
|
||||||
public void prefetchChildren(int startIndex, int length)
|
public void prefetchChildren(int startIndex, int length) {
|
||||||
throws Exception {
|
|
||||||
if (length < 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startIndex < 0) {
|
if (startIndex < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNodes();
|
loadNodes();
|
||||||
|
|
||||||
if (subnodes == null) {
|
if (subnodes == null || startIndex >= subnodes.size()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startIndex >= subnodes.size()) {
|
subnodes.prefetch(startIndex, length);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Enumerate through the subnodes of this node.
|
||||||
*
|
* @return an enumeration of this node's subnodes
|
||||||
* @return ...
|
|
||||||
*/
|
*/
|
||||||
public Enumeration getSubnodes() {
|
public Enumeration getSubnodes() {
|
||||||
loadNodes();
|
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() {
|
public boolean hasMoreElements() {
|
||||||
return count < numberOfNodes();
|
return pos < list.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object nextElement() {
|
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() {
|
public synchronized INode getCacheNode() {
|
||||||
if (cacheNode == null) {
|
if (cacheNode == null) {
|
||||||
cacheNode = new TransientNode();
|
cacheNode = new Node("cache", null, nmgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cacheNode;
|
return cacheNode;
|
||||||
|
@ -2679,6 +2590,12 @@ public final class Node implements INode, Serializable {
|
||||||
return nmgr == null;
|
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
|
* 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.
|
* 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)
|
* (somefiled1 > theHighestKnownValue value and somefield2 < theLowestKnownValue)
|
||||||
* @return the number of loaded nodes within this collection update
|
* @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
|
// TODO: what do we do if dbmap is null
|
||||||
if (dbmap == null) {
|
if (dbmap == null) {
|
||||||
throw new RuntimeException (this + " doesn't have a DbMapping");
|
throw new RuntimeException (this + " doesn't have a DbMapping");
|
||||||
|
@ -2723,7 +2640,7 @@ public final class Node implements INode, Serializable {
|
||||||
lastSubnodeFetch = getLastSubnodeChange(subRel);
|
lastSubnodeFetch = getLastSubnodeChange(subRel);
|
||||||
return nmgr.updateSubnodeList(this, subRel);
|
return nmgr.updateSubnodeList(this, subRel);
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the application this node belongs to.
|
* 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
|
* 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
|
* and reinstanciated since being set, NodeHandle will always return an up-to-date
|
||||||
* instance of its node.
|
* 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 {
|
public final class NodeHandle implements INodeState, Serializable {
|
||||||
static final long serialVersionUID = 3067763116576910931L;
|
static final long serialVersionUID = 3067763116576910931L;
|
||||||
|
@ -40,9 +49,12 @@ public final class NodeHandle implements INodeState, Serializable {
|
||||||
private Key key;
|
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();
|
int state = node.getState();
|
||||||
|
|
||||||
if (state == TRANSIENT) {
|
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,
|
* 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
|
* the node is ususally not yet created. It will be fetched on demand when accessed by
|
||||||
* application code.
|
* application code.
|
||||||
|
* @param key the key
|
||||||
*/
|
*/
|
||||||
public NodeHandle(Key key) {
|
public NodeHandle(Key key) {
|
||||||
this.node = null;
|
this.node = null;
|
||||||
|
@ -71,19 +84,22 @@ public final class NodeHandle implements INodeState, Serializable {
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodemgr.getNode(key);
|
return nodemgr.getNode(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the node is available without fetching it from the node manager
|
||||||
|
* @return true if we alreay have a reference to our node
|
||||||
|
*/
|
||||||
|
public boolean hasNode() {
|
||||||
|
return node != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the key for the node described by this handle.
|
* Get the key for the node described by this handle.
|
||||||
* This may only be called on persistent Nodes.
|
* This will return null for transient Nodes.
|
||||||
*/
|
*/
|
||||||
public Key getKey() {
|
public Key getKey() {
|
||||||
if (key == null) {
|
|
||||||
throw new RuntimeException("getKey called on transient Node");
|
|
||||||
}
|
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +111,6 @@ public final class NodeHandle implements INodeState, Serializable {
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
return node.getID();
|
return node.getID();
|
||||||
}
|
}
|
||||||
|
|
||||||
return key.getID();
|
return key.getID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,11 +130,12 @@ public final class NodeHandle implements INodeState, Serializable {
|
||||||
* @return ...
|
* @return ...
|
||||||
*/
|
*/
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
try {
|
if (other instanceof NodeHandle) {
|
||||||
return getObject().equals(((NodeHandle) other).getObject());
|
Object obj1 = getObject();
|
||||||
} catch (Exception x) {
|
Object obj2 = ((NodeHandle) other).getObject();
|
||||||
return false;
|
return obj1 == obj2 || obj1.equals(obj2);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -251,7 +251,7 @@ public final class NodeManager {
|
||||||
if ((node != null) && (node.getState() != Node.INVALID)) {
|
if ((node != null) && (node.getState() != Node.INVALID)) {
|
||||||
// check if node is null node (cached null)
|
// check if node is null node (cached null)
|
||||||
if (node.isNullNode()) {
|
if (node.isNullNode()) {
|
||||||
if (node.created != home.getLastSubnodeChange(rel)) {
|
if (node.created != home.getLastSubnodeChange()) {
|
||||||
node = null; // cached null not valid anymore
|
node = null; // cached null not valid anymore
|
||||||
}
|
}
|
||||||
} else if (!rel.virtual) {
|
} else if (!rel.virtual) {
|
||||||
|
@ -292,7 +292,7 @@ public final class NodeManager {
|
||||||
} else {
|
} else {
|
||||||
// node fetched from db is null, cache result using nullNode
|
// node fetched from db is null, cache result using nullNode
|
||||||
synchronized (cache) {
|
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
|
// we ignore the case that onother thread has created the node in the meantime
|
||||||
return null;
|
return null;
|
||||||
|
@ -876,14 +876,14 @@ public final class NodeManager {
|
||||||
* Loades subnodes via subnode relation. Only the ID index is loaded, the nodes are
|
* Loades subnodes via subnode relation. Only the ID index is loaded, the nodes are
|
||||||
* loaded later on demand.
|
* 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()) {
|
if ((rel == null) || (rel.otherType == null) || !rel.otherType.isRelational()) {
|
||||||
// this should never be called for embedded nodes
|
// this should never be called for embedded nodes
|
||||||
throw new RuntimeException("NodeMgr.getNodeIDs called for non-relational node " +
|
throw new RuntimeException("NodeMgr.getNodeIDs called for non-relational node " +
|
||||||
home);
|
home);
|
||||||
} else {
|
} else {
|
||||||
SubnodeList retval = home.createSubnodeList();
|
List retval = new ArrayList();
|
||||||
|
|
||||||
// if we do a groupby query (creating an intermediate layer of groupby nodes),
|
// if we do a groupby query (creating an intermediate layer of groupby nodes),
|
||||||
// retrieve the value of that field instead of the primary key
|
// 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 key = (rel.groupby == null)
|
||||||
? (Key) new DbKey(rel.otherType, kstr)
|
? (Key) new DbKey(rel.otherType, kstr)
|
||||||
: (Key) new SyntheticKey(k, 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 these are groupby nodes, evict nullNode keys
|
||||||
if (rel.groupby != null) {
|
if (rel.groupby != null) {
|
||||||
|
@ -980,79 +980,77 @@ public final class NodeManager {
|
||||||
* actually loades all nodes in one go, which is better for small node collections.
|
* actually loades all nodes in one go, which is better for small node collections.
|
||||||
* This method is used when xxx.loadmode=aggressive is specified.
|
* 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
|
// This does not apply for groupby nodes - use getNodeIDs instead
|
||||||
if (rel.groupby != null) {
|
assert rel.groupby == null;
|
||||||
return getNodeIDs(home, rel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((rel == null) || (rel.otherType == null) || !rel.otherType.isRelational()) {
|
if ((rel == null) || (rel.otherType == null) || !rel.otherType.isRelational()) {
|
||||||
// this should never be called for embedded nodes
|
// this should never be called for embedded nodes
|
||||||
throw new RuntimeException("NodeMgr.getNodes called for non-relational node " +
|
throw new RuntimeException("NodeMgr.getNodes called for non-relational node " +
|
||||||
home);
|
home);
|
||||||
} else {
|
}
|
||||||
SubnodeList retval = home.createSubnodeList();
|
|
||||||
DbMapping dbm = rel.otherType;
|
|
||||||
|
|
||||||
Connection con = dbm.getConnection();
|
List retval = new ArrayList();
|
||||||
// set connection to read-only mode
|
DbMapping dbm = rel.otherType;
|
||||||
if (!con.isReadOnly()) con.setReadOnly(true);
|
|
||||||
|
|
||||||
Statement stmt = con.createStatement();
|
Connection con = dbm.getConnection();
|
||||||
DbColumn[] columns = dbm.getColumns();
|
// set connection to read-only mode
|
||||||
Relation[] joins = dbm.getJoins();
|
if (!con.isReadOnly()) con.setReadOnly(true);
|
||||||
String query = null;
|
|
||||||
long logTimeStart = logSql ? System.currentTimeMillis() : 0;
|
|
||||||
|
|
||||||
try {
|
Statement stmt = con.createStatement();
|
||||||
StringBuffer b = dbm.getSelect(rel);
|
DbColumn[] columns = dbm.getColumns();
|
||||||
|
Relation[] joins = dbm.getJoins();
|
||||||
|
String query = null;
|
||||||
|
long logTimeStart = logSql ? System.currentTimeMillis() : 0;
|
||||||
|
|
||||||
if (home.getSubnodeRelation() != null) {
|
try {
|
||||||
b.append(home.getSubnodeRelation());
|
StringBuffer b = dbm.getSelect(rel);
|
||||||
} else {
|
|
||||||
// let relation object build the query
|
|
||||||
rel.buildQuery(b, home, null, " WHERE ", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
query = b.toString();
|
if (home.getSubnodeRelation() != null) {
|
||||||
|
b.append(home.getSubnodeRelation());
|
||||||
if (rel.maxSize > 0) {
|
} else {
|
||||||
stmt.setMaxRows(rel.maxSize);
|
// let relation object build the query
|
||||||
}
|
rel.buildQuery(b, home, null, " WHERE ", true);
|
||||||
|
|
||||||
ResultSet rs = stmt.executeQuery(query);
|
|
||||||
|
|
||||||
while (rs.next()) {
|
|
||||||
// create new Nodes.
|
|
||||||
Node node = createNode(rel.otherType, rs, columns, 0);
|
|
||||||
if (node == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Key primKey = node.getKey();
|
|
||||||
|
|
||||||
retval.addSorted(new NodeHandle(primKey));
|
|
||||||
|
|
||||||
registerNewNode(node, null);
|
|
||||||
|
|
||||||
fetchJoinedNodes(rs, joins, columns.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
if (logSql) {
|
|
||||||
long logTimeStop = System.currentTimeMillis();
|
|
||||||
logSqlStatement("SQL SELECT_ALL", dbm.getTableName(),
|
|
||||||
logTimeStart, logTimeStop, query);
|
|
||||||
}
|
|
||||||
if (stmt != null) {
|
|
||||||
try {
|
|
||||||
stmt.close();
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return retval;
|
query = b.toString();
|
||||||
|
|
||||||
|
if (rel.maxSize > 0) {
|
||||||
|
stmt.setMaxRows(rel.maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultSet rs = stmt.executeQuery(query);
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
// create new Nodes.
|
||||||
|
Node node = createNode(rel.otherType, rs, columns, 0);
|
||||||
|
if (node == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Key primKey = node.getKey();
|
||||||
|
|
||||||
|
retval.add(new NodeHandle(primKey));
|
||||||
|
|
||||||
|
registerNewNode(node, null);
|
||||||
|
|
||||||
|
fetchJoinedNodes(rs, joins, columns.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (logSql) {
|
||||||
|
long logTimeStop = System.currentTimeMillis();
|
||||||
|
logSqlStatement("SQL SELECT_ALL", dbm.getTableName(),
|
||||||
|
logTimeStart, logTimeStop, query);
|
||||||
|
}
|
||||||
|
if (stmt != null) {
|
||||||
|
try {
|
||||||
|
stmt.close();
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1068,7 +1066,7 @@ public final class NodeManager {
|
||||||
* @return A map having two properties of type String (newNodes (number of nodes retreived by the select-statment), addedNodes (nodes added to the collection))
|
* @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
|
* @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()) {
|
if ((rel == null) || (rel.otherType == null) || !rel.otherType.isRelational()) {
|
||||||
// this should never be called for embedded nodes
|
// this should never be called for embedded nodes
|
||||||
throw new RuntimeException("NodeMgr.updateSubnodeList called for non-relational node " +
|
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 {
|
throws Exception {
|
||||||
DbMapping dbm = rel.otherType;
|
DbMapping dbm = rel.otherType;
|
||||||
|
|
||||||
// this does nothing for objects in the embedded database
|
// this does nothing for objects in the embedded database
|
||||||
if (dbm != null && dbm.isRelational()) {
|
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();
|
Connection con = dbm.getConnection();
|
||||||
// set connection to read-only mode
|
// set connection to read-only mode
|
||||||
if (!con.isReadOnly()) con.setReadOnly(true);
|
if (!con.isReadOnly()) con.setReadOnly(true);
|
||||||
|
@ -1226,13 +1239,7 @@ public final class NodeManager {
|
||||||
try {
|
try {
|
||||||
StringBuffer b = dbm.getSelect(null).append(" WHERE ");
|
StringBuffer b = dbm.getSelect(null).append(" WHERE ");
|
||||||
String idfield = (rel.groupby != null) ? rel.groupby : dbm.getIDField();
|
String idfield = (rel.groupby != null) ? rel.groupby : dbm.getIDField();
|
||||||
|
String[] ids = (String[]) missing.toArray(new String[missing.size()]);
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
dbm.appendCondition(b, idfield, ids);
|
dbm.appendCondition(b, idfield, ids);
|
||||||
dbm.addJoinConstraints(b, " AND ");
|
dbm.addJoinConstraints(b, " AND ");
|
||||||
|
@ -1277,7 +1284,7 @@ public final class NodeManager {
|
||||||
// group nodes.
|
// group nodes.
|
||||||
String groupName = null;
|
String groupName = null;
|
||||||
|
|
||||||
if (groupbyProp != null) {
|
/* if (groupbyProp != null) {
|
||||||
groupName = node.getString(groupbyProp);
|
groupName = node.getString(groupbyProp);
|
||||||
|
|
||||||
SubnodeList sn = (SubnodeList) groupbySubnodes.get(groupName);
|
SubnodeList sn = (SubnodeList) groupbySubnodes.get(groupName);
|
||||||
|
@ -1287,8 +1294,8 @@ public final class NodeManager {
|
||||||
groupbySubnodes.put(groupName, sn);
|
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 relation doesn't use primary key as accessName, get secondary key
|
||||||
if (accessProp != null) {
|
if (accessProp != null) {
|
||||||
|
@ -1313,7 +1320,7 @@ public final class NodeManager {
|
||||||
|
|
||||||
// If these are grouped nodes, build the intermediary group nodes
|
// If these are grouped nodes, build the intermediary group nodes
|
||||||
// with the subnod lists we created
|
// with the subnod lists we created
|
||||||
if (groupbyProp != null) {
|
/* if (groupbyProp != null) {
|
||||||
for (Iterator i = groupbySubnodes.keySet().iterator();
|
for (Iterator i = groupbySubnodes.keySet().iterator();
|
||||||
i.hasNext();) {
|
i.hasNext();) {
|
||||||
String groupname = (String) i.next();
|
String groupname = (String) i.next();
|
||||||
|
@ -1328,7 +1335,7 @@ public final class NodeManager {
|
||||||
groupnode.lastSubnodeFetch =
|
groupnode.lastSubnodeFetch =
|
||||||
groupnode.getLastSubnodeChange(groupnode.dbmap.getSubnodeRelation());
|
groupnode.getLastSubnodeChange(groupnode.dbmap.getSubnodeRelation());
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
app.logError("Error in prefetchNodes()", x);
|
app.logError("Error in prefetchNodes()", x);
|
||||||
} finally {
|
} 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)
|
public Vector getPropertyNames(Node home, Relation rel)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
@ -1467,7 +1474,7 @@ public final class NodeManager {
|
||||||
String n = rs.getString(1);
|
String n = rs.getString(1);
|
||||||
|
|
||||||
if (n != null) {
|
if (n != null) {
|
||||||
retval.addElement(n);
|
retval.add(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} 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
|
* 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.
|
// Note: prototype, groupby, groupbyPrototype and groupbyOrder aren't copied here.
|
||||||
// these are set by the individual get*Relation() methods as appropriate.
|
// these are set by the individual get*Relation() methods as appropriate.
|
||||||
this.ownType = rel.ownType;
|
this.ownType = rel.ownType;
|
||||||
|
@ -790,7 +791,7 @@ public final class Relation {
|
||||||
virtualMapping.propRelation = getVirtualPropertyRelation();
|
virtualMapping.propRelation = getVirtualPropertyRelation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
virtualMapping.lastTypeChange = ownType.lastTypeChange;
|
||||||
return virtualMapping;
|
return virtualMapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1119,6 +1120,19 @@ public final class Relation {
|
||||||
return readonly;
|
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.
|
* 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,
|
* 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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.HashMap;
|
import java.io.Serializable;
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A subclass of ArrayList that adds an addSorted(Object) method to
|
* A subclass of ArrayList that adds an addSorted(Object) method to
|
||||||
*/
|
*/
|
||||||
public class SubnodeList extends ArrayList {
|
public class SubnodeList implements Serializable {
|
||||||
|
|
||||||
transient WrappedNodeManager nmgr;
|
Node node;
|
||||||
transient HashMap views = null;
|
List list;
|
||||||
transient Relation rel;
|
|
||||||
|
transient long lastSubnodeFetch = 0;
|
||||||
|
transient long lastSubnodeChange = 0;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide/disable zero argument constructor for subclasses
|
* Hide/disable zero argument constructor for subclasses
|
||||||
|
@ -37,21 +39,11 @@ public class SubnodeList extends ArrayList {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new subnode list
|
* Creates a new subnode list
|
||||||
* @param nmgr
|
* @param node the node we belong to
|
||||||
*/
|
*/
|
||||||
public SubnodeList(WrappedNodeManager nmgr, Relation rel) {
|
public SubnodeList(Node node) {
|
||||||
this.nmgr = nmgr;
|
this.node = node;
|
||||||
this.rel = rel;
|
this.list = new ArrayList();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,8 +53,7 @@ public class SubnodeList extends ArrayList {
|
||||||
* @param obj element to be inserted.
|
* @param obj element to be inserted.
|
||||||
*/
|
*/
|
||||||
public boolean add(Object obj) {
|
public boolean add(Object obj) {
|
||||||
addToViews(obj);
|
return list.add(obj);
|
||||||
return super.add(obj);
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Adds the specified object to the list at the given position
|
* 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
|
* @param obj the object t add
|
||||||
*/
|
*/
|
||||||
public void add(int idx, Object obj) {
|
public void add(int idx, Object obj) {
|
||||||
addToViews(obj);
|
list.add(idx, obj);
|
||||||
super.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
|
* @param idx the index-position of the NodeHandle to remove
|
||||||
*/
|
*/
|
||||||
public Object remove (int idx) {
|
public Object remove (int idx) {
|
||||||
Object obj = get(idx);
|
return list.remove(idx);
|
||||||
if (obj != null) {
|
|
||||||
removeFromViews(obj);
|
|
||||||
}
|
|
||||||
return super.remove(idx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,40 +109,78 @@ public class SubnodeList extends ArrayList {
|
||||||
* @param obj the NodeHandle to remove
|
* @param obj the NodeHandle to remove
|
||||||
*/
|
*/
|
||||||
public boolean remove (Object obj) {
|
public boolean remove (Object obj) {
|
||||||
removeFromViews(obj);
|
return list.remove(obj);
|
||||||
return super.remove(obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeFromViews(Object obj) {
|
public Object[] toArray() {
|
||||||
if (views == null || views.isEmpty())
|
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;
|
return;
|
||||||
for (Iterator i = views.values().iterator(); i.hasNext(); ) {
|
|
||||||
OrderedSubnodeList osl = (OrderedSubnodeList) i.next();
|
|
||||||
osl.remove(obj);
|
|
||||||
}
|
}
|
||||||
}
|
length = (length < 0) ?
|
||||||
|
size() - start : Math.min(length, size() - start);
|
||||||
public SubnodeList getOrderedView (String order) {
|
if (length < 0) {
|
||||||
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())
|
|
||||||
return;
|
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 helma.objectmodel.ObjectNotFoundException;
|
||||||
|
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around NodeManager that catches most Exceptions, or rethrows them as RuntimeExceptions.
|
* A wrapper around NodeManager that catches most Exceptions, or rethrows them as RuntimeExceptions.
|
||||||
|
@ -104,11 +105,11 @@ public final class WrappedNodeManager {
|
||||||
* @param rel
|
* @param rel
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public SubnodeList getNodes(Node home, Relation rel) {
|
public List getNodes(Node home, Relation rel) {
|
||||||
Transactor tx = checkLocalTransactor();
|
Transactor tx = checkLocalTransactor();
|
||||||
try {
|
try {
|
||||||
beginLocalTransaction(tx, "getNodes");
|
beginLocalTransaction(tx, "getNodes");
|
||||||
SubnodeList list = nmgr.getNodes(home, rel);
|
List list = nmgr.getNodes(home, rel);
|
||||||
commitLocalTransaction(tx);
|
commitLocalTransaction(tx);
|
||||||
return list;
|
return list;
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
|
@ -125,7 +126,7 @@ public final class WrappedNodeManager {
|
||||||
* @param rel
|
* @param rel
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public SubnodeList getNodeIDs(Node home, Relation rel) {
|
public List getNodeIDs(Node home, Relation rel) {
|
||||||
try {
|
try {
|
||||||
return nmgr.getNodeIDs(home, rel);
|
return nmgr.getNodeIDs(home, rel);
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
|
@ -136,13 +137,13 @@ public final class WrappedNodeManager {
|
||||||
/**
|
/**
|
||||||
* @see helma.objectmodel.db.NodeManager#updateSubnodeList(Node, Relation)
|
* @see helma.objectmodel.db.NodeManager#updateSubnodeList(Node, Relation)
|
||||||
*/
|
*/
|
||||||
public int updateSubnodeList (Node home, Relation rel) {
|
/* public int updateSubnodeList (Node home, Relation rel) {
|
||||||
try {
|
try {
|
||||||
return nmgr.updateSubnodeList(home, rel);
|
return nmgr.updateSubnodeList(home, rel);
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
throw new RuntimeException("Error retrieving NodeIDs", x);
|
throw new RuntimeException("Error retrieving NodeIDs", x);
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count the nodes contained in the given Node's collection
|
* 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
|
* Delete a node from the database
|
||||||
*
|
*
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package helma.objectmodel.dom;
|
package helma.objectmodel.dom;
|
||||||
|
|
||||||
import helma.objectmodel.INode;
|
import helma.objectmodel.INode;
|
||||||
import helma.objectmodel.TransientNode;
|
|
||||||
import helma.util.SystemProperties;
|
import helma.util.SystemProperties;
|
||||||
import org.w3c.dom.*;
|
import org.w3c.dom.*;
|
||||||
import org.xml.sax.InputSource;
|
import org.xml.sax.InputSource;
|
||||||
|
@ -75,17 +74,6 @@ public class XmlConverter implements XmlConstants {
|
||||||
extractProperties(props);
|
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 = currentNode.createSubnodeList();
|
||||||
}
|
}
|
||||||
|
|
||||||
subnodes.addSorted(handle);
|
subnodes.add(handle);
|
||||||
} else if ("hop:parent".equals(qName)) {
|
} else if ("hop:parent".equals(qName)) {
|
||||||
currentNode.setParentHandle(handle);
|
currentNode.setParentHandle(handle);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -19,7 +19,6 @@ package helma.objectmodel.dom;
|
||||||
|
|
||||||
import helma.objectmodel.INode;
|
import helma.objectmodel.INode;
|
||||||
import helma.objectmodel.IProperty;
|
import helma.objectmodel.IProperty;
|
||||||
import helma.objectmodel.TransientNode;
|
|
||||||
import helma.objectmodel.INodeState;
|
import helma.objectmodel.INodeState;
|
||||||
import helma.objectmodel.db.DbMapping;
|
import helma.objectmodel.db.DbMapping;
|
||||||
import helma.objectmodel.db.Node;
|
import helma.objectmodel.db.Node;
|
||||||
|
|
|
@ -33,8 +33,8 @@ import java.io.IOException;
|
||||||
*/
|
*/
|
||||||
public class HopObject extends ScriptableObject implements Wrapper, PropertyRecorder {
|
public class HopObject extends ScriptableObject implements Wrapper, PropertyRecorder {
|
||||||
|
|
||||||
final String className;
|
String className;
|
||||||
INode node;
|
final NodeProxy proxy;
|
||||||
RhinoCore core;
|
RhinoCore core;
|
||||||
|
|
||||||
// fields to implement PropertyRecorder
|
// fields to implement PropertyRecorder
|
||||||
|
@ -48,10 +48,12 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* @param core the RhinoCore
|
* @param core the RhinoCore
|
||||||
*/
|
*/
|
||||||
protected HopObject(String className, RhinoCore core) {
|
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.
|
* Creates a new HopObject.
|
||||||
*
|
*
|
||||||
|
@ -64,7 +66,25 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
INode node, Scriptable proto) {
|
INode node, Scriptable proto) {
|
||||||
this.className = className;
|
this.className = className;
|
||||||
this.core = core;
|
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)
|
if (proto != null)
|
||||||
setPrototype(proto);
|
setPrototype(proto);
|
||||||
setParentScope(core.global);
|
setParentScope(core.global);
|
||||||
|
@ -137,7 +157,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* @return the default value for the object
|
* @return the default value for the object
|
||||||
*/
|
*/
|
||||||
public Object getDefaultValue(Class hint) {
|
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
|
* @return the wrapped INode instance
|
||||||
*/
|
*/
|
||||||
public INode getNode() {
|
public INode getNode() {
|
||||||
if (node != null) {
|
if (proxy != null) {
|
||||||
checkNode();
|
return proxy.getNode();
|
||||||
}
|
}
|
||||||
return node;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -157,51 +177,25 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public Object unwrap() {
|
public Object unwrap() {
|
||||||
if (node != null) {
|
if (proxy != null) {
|
||||||
checkNode();
|
return proxy.getNode();
|
||||||
return node;
|
|
||||||
} else {
|
} else {
|
||||||
return this;
|
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 ...
|
* @return ...
|
||||||
*/
|
*/
|
||||||
public Object jsGet_cache() {
|
public Object jsGet_cache() {
|
||||||
if (node == null) {
|
if (proxy != null) {
|
||||||
return null;
|
INode cache = proxy.getNode().getCacheNode();
|
||||||
|
if (cache != null) {
|
||||||
|
return Context.toObject(cache, core.global);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNode();
|
|
||||||
|
|
||||||
INode cache = node.getCacheNode();
|
|
||||||
if (cache != null) {
|
|
||||||
return Context.toObject(node.getCacheNode(), core.global);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,7 +212,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
RhinoEngine engine = RhinoEngine.getRhinoEngine();
|
RhinoEngine engine = RhinoEngine.getRhinoEngine();
|
||||||
Skin skin = engine.toSkin(skinobj, className);
|
Skin skin = engine.toSkin(skinobj, className);
|
||||||
|
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
if (skin != null) {
|
if (skin != null) {
|
||||||
skin.render(engine.reval, node,
|
skin.render(engine.reval, node,
|
||||||
|
@ -287,7 +281,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
throws UnsupportedEncodingException, IOException {
|
throws UnsupportedEncodingException, IOException {
|
||||||
RhinoEngine engine = RhinoEngine.getRhinoEngine();
|
RhinoEngine engine = RhinoEngine.getRhinoEngine();
|
||||||
Skin skin = engine.toSkin(skinobj, className);
|
Skin skin = engine.toSkin(skinobj, className);
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
if (skin != null) {
|
if (skin != null) {
|
||||||
return skin.renderAsString(engine.reval, node,
|
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,
|
public Object jsFunction_href(Object action) throws UnsupportedEncodingException,
|
||||||
IOException {
|
IOException {
|
||||||
if (node == null) {
|
if (proxy == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String act = null;
|
String act = null;
|
||||||
|
INode node = getNode();
|
||||||
checkNode();
|
|
||||||
|
|
||||||
if (action != null) {
|
if (action != null) {
|
||||||
if (action instanceof Wrapper) {
|
if (action instanceof Wrapper) {
|
||||||
|
@ -335,24 +328,22 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* @return ...
|
* @return ...
|
||||||
*/
|
*/
|
||||||
public Object jsFunction_get(Object id) {
|
public Object jsFunction_get(Object id) {
|
||||||
if ((node == null) || (id == null)) {
|
if (proxy == null || id == null || id == Undefined.instance) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object n = null;
|
Object child;
|
||||||
|
INode node = getNode();
|
||||||
checkNode();
|
|
||||||
|
|
||||||
if (id instanceof Number) {
|
if (id instanceof Number) {
|
||||||
n = node.getSubnodeAt(((Number) id).intValue());
|
child = node.getSubnodeAt(((Number) id).intValue());
|
||||||
} else {
|
} else {
|
||||||
n = node.getChildElement(id.toString());
|
child = node.getChildElement(id.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (n != null) {
|
if (child != null) {
|
||||||
return Context.toObject(n, core.global);
|
return Context.toObject(child, core.global);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,12 +355,11 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* @return ...
|
* @return ...
|
||||||
*/
|
*/
|
||||||
public Object jsFunction_getById(Object id) {
|
public Object jsFunction_getById(Object id) {
|
||||||
if ((node == null) || (id == null) || id == Undefined.instance) {
|
if (proxy == null || id == null || id == Undefined.instance) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
String idString = (id instanceof Double) ?
|
String idString = (id instanceof Double) ?
|
||||||
Long.toString(((Double) id).longValue()) :
|
Long.toString(((Double) id).longValue()) :
|
||||||
id.toString();
|
id.toString();
|
||||||
|
@ -393,17 +383,16 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
if (id == Undefined.instance || value == Undefined.instance) {
|
if (id == Undefined.instance || value == Undefined.instance) {
|
||||||
throw new EvaluatorException("HopObject.set() called with wrong number of arguments");
|
throw new EvaluatorException("HopObject.set() called with wrong number of arguments");
|
||||||
}
|
}
|
||||||
if ((node == null)) {
|
if (proxy == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id instanceof Number) {
|
if (id instanceof Number) {
|
||||||
|
if (!(value instanceof HopObject)) {
|
||||||
if (!(value instanceof HopObject)) {
|
|
||||||
throw new EvaluatorException("Can only set HopObjects as child objects in HopObject.set()");
|
throw new EvaluatorException("Can only set HopObjects as child objects in HopObject.set()");
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
int idx = (((Number) id).intValue());
|
int idx = (((Number) id).intValue());
|
||||||
INode n = ((HopObject) value).getNode();
|
INode n = ((HopObject) value).getNode();
|
||||||
|
@ -423,12 +412,10 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* @return ...
|
* @return ...
|
||||||
*/
|
*/
|
||||||
public int jsFunction_count() {
|
public int jsFunction_count() {
|
||||||
if (node == null) {
|
if (proxy == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
INode node = getNode();
|
||||||
checkNode();
|
|
||||||
|
|
||||||
return node.numberOfNodes();
|
return node.numberOfNodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -456,16 +443,11 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prefetchChildren(int start, int length) {
|
private void prefetchChildren(int start, int length) {
|
||||||
if (!(node instanceof Node)) {
|
if (proxy != null) {
|
||||||
return;
|
INode node = getNode();
|
||||||
}
|
if (node instanceof Node) {
|
||||||
|
((Node) node).prefetchChildren(start, length);
|
||||||
checkNode();
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
((Node) node).prefetchChildren(start, length);
|
|
||||||
} catch (Exception x) {
|
|
||||||
core.app.logError("Error in HopObject.prefetchChildren: " + x, x);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,9 +455,10 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* Clear the node's cache node.
|
* Clear the node's cache node.
|
||||||
*/
|
*/
|
||||||
public void jsFunction_clearCache() {
|
public void jsFunction_clearCache() {
|
||||||
checkNode();
|
if (proxy != null) {
|
||||||
|
INode node = getNode();
|
||||||
node.clearCacheNode();
|
node.clearCacheNode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -485,19 +468,17 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* @return A JavaScript Array containing all child objects
|
* @return A JavaScript Array containing all child objects
|
||||||
*/
|
*/
|
||||||
private Scriptable list() {
|
private Scriptable list() {
|
||||||
checkNode();
|
Node node = (Node) getNode();
|
||||||
|
node.loadNodes();
|
||||||
// prefetchChildren(0, 1000);
|
SubnodeList list = node.getSubnodeList();
|
||||||
Enumeration e = node.getSubnodes();
|
if (list == null) {
|
||||||
ArrayList a = new ArrayList();
|
return Context.getCurrentContext().newArray(core.global, 0);
|
||||||
|
|
||||||
while ((e != null) && e.hasMoreElements()) {
|
|
||||||
Object obj = e.nextElement();
|
|
||||||
if (obj != null)
|
|
||||||
a.add(Context.toObject(obj, core.global));
|
|
||||||
}
|
}
|
||||||
|
Object[] array = list.toArray();
|
||||||
return Context.getCurrentContext().newArray(core.global, a.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)");
|
throw new EvaluatorException("Arguments must not be negative in HopObject.list(start, length)");
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNode();
|
Node node = (Node) getNode();
|
||||||
|
|
||||||
prefetchChildren(start, length);
|
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++) {
|
for (int i = 0; i < length; i++) {
|
||||||
INode n = node.getSubnodeAt(i);
|
Object obj = list.get(start + i);
|
||||||
if (n != null) {
|
if (obj != null) {
|
||||||
a.add(Context.toObject(n, core.global));
|
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 ...
|
* @return ...
|
||||||
*/
|
*/
|
||||||
public boolean jsFunction_add(Object child) {
|
public boolean jsFunction_add(Object child) {
|
||||||
if ((node == null) || (child == null)) {
|
if (proxy == null || child == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
if (child instanceof HopObject) {
|
if (child instanceof HopObject) {
|
||||||
node.addNode(((HopObject) child).node);
|
node.addNode(((HopObject) child).getNode());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (child instanceof INode) {
|
} else if (child instanceof INode) {
|
||||||
node.addNode((INode) child);
|
node.addNode((INode) child);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,10 +555,10 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
if (child instanceof HopObject) {
|
if (child instanceof HopObject) {
|
||||||
node.addNode(((HopObject) child).node, index);
|
node.addNode(((HopObject) child).getNode(), index);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (child instanceof INode) {
|
} 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)");
|
throw new RuntimeException("Caught deprecated usage of HopObject.remove(child)");
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
return node.remove();
|
return node.remove();
|
||||||
}
|
}
|
||||||
|
@ -616,13 +599,13 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
*/
|
*/
|
||||||
public boolean jsFunction_removeChild(Object child) {
|
public boolean jsFunction_removeChild(Object child) {
|
||||||
|
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
if (child instanceof HopObject) {
|
if (child instanceof HopObject) {
|
||||||
HopObject hobj = (HopObject) child;
|
HopObject hobj = (HopObject) child;
|
||||||
|
|
||||||
if (hobj.node != null) {
|
if (hobj.proxy != null) {
|
||||||
node.removeNode(hobj.node);
|
node.removeNode(hobj.getNode());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -637,7 +620,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
*/
|
*/
|
||||||
public Object jsFunction_persist() {
|
public Object jsFunction_persist() {
|
||||||
|
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
if (node instanceof Node) {
|
if (node instanceof Node) {
|
||||||
((Node) node).persist();
|
((Node) node).persist();
|
||||||
|
@ -650,18 +633,17 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* Invalidate the node itself or a subnode
|
* Invalidate the node itself or a subnode
|
||||||
*/
|
*/
|
||||||
public boolean jsFunction_invalidate(Object childId) {
|
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 (childId == Undefined.instance) {
|
||||||
|
|
||||||
if (node.getState() == INode.INVALID) {
|
if (node.getState() == INode.INVALID) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
((Node) node).invalidate();
|
((Node) node).invalidate();
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
checkNode();
|
|
||||||
|
|
||||||
((Node) node).invalidateNode(childId.toString());
|
((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.
|
* @return true if the the wrapped Node has a valid database id.
|
||||||
*/
|
*/
|
||||||
public boolean jsFunction_isPersistent() {
|
public boolean jsFunction_isPersistent() {
|
||||||
if (!(node instanceof Node)) {
|
if (proxy == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
int nodeState = node.getState();
|
int nodeState = node.getState();
|
||||||
return nodeState != INode.TRANSIENT;
|
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.
|
* @return true if the the wrapped Node is not stored in a database.
|
||||||
*/
|
*/
|
||||||
public boolean jsFunction_isTransient() {
|
public boolean jsFunction_isTransient() {
|
||||||
if (!(node instanceof Node)) {
|
if (proxy == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
int nodeState = node.getState();
|
int nodeState = node.getState();
|
||||||
return nodeState == INode.TRANSIENT;
|
return nodeState == INode.TRANSIENT;
|
||||||
}
|
}
|
||||||
|
@ -705,14 +687,10 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
*/
|
*/
|
||||||
public int jsFunction_indexOf(Object obj) {
|
public int jsFunction_indexOf(Object obj) {
|
||||||
|
|
||||||
checkNode();
|
if (proxy != null && obj instanceof HopObject) {
|
||||||
|
INode node = getNode();
|
||||||
if ((node != null) && obj instanceof HopObject) {
|
return node.contains(((HopObject) obj).getNode());
|
||||||
checkNode();
|
|
||||||
|
|
||||||
return node.contains(((HopObject) obj).node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -724,7 +702,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
public int jsFunction_contains(Object obj) {
|
public int jsFunction_contains(Object obj) {
|
||||||
return jsFunction_indexOf(obj);
|
return jsFunction_indexOf(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a property in this HopObject
|
* Set a property in this HopObject
|
||||||
*
|
*
|
||||||
|
@ -733,7 +711,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* @param value ...
|
* @param value ...
|
||||||
*/
|
*/
|
||||||
public void put(String name, Scriptable start, Object value) {
|
public void put(String name, Scriptable start, Object value) {
|
||||||
if (node == null) {
|
if (proxy == null) {
|
||||||
// redirect the scripted constructor to __constructor__,
|
// redirect the scripted constructor to __constructor__,
|
||||||
// constructor is set to the native constructor method.
|
// constructor is set to the native constructor method.
|
||||||
if ("constructor".equals(name) && value instanceof NativeFunction) {
|
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
|
// use ScriptableObject.put to set it
|
||||||
super.put(name, start, value);
|
super.put(name, start, value);
|
||||||
} else {
|
} else {
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
if ("subnodeRelation".equals(name)) {
|
if ("subnodeRelation".equals(name)) {
|
||||||
node.setSubnodeRelation(value == null ? null : value.toString());
|
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
|
* @return true if the property was found
|
||||||
*/
|
*/
|
||||||
public boolean has(String name, Scriptable start) {
|
public boolean has(String name, Scriptable start) {
|
||||||
if (node != null) {
|
if (proxy != null) {
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
if (node.get(name) != null) {
|
if (node.get(name) != null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -828,8 +806,8 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* @param name ...
|
* @param name ...
|
||||||
*/
|
*/
|
||||||
public void delete(String name) {
|
public void delete(String name) {
|
||||||
if ((node != null)) {
|
if ((proxy != null)) {
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
node.unset(name);
|
node.unset(name);
|
||||||
}
|
}
|
||||||
super.delete(name);
|
super.delete(name);
|
||||||
|
@ -845,7 +823,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
*/
|
*/
|
||||||
public Object get(String name, Scriptable start) {
|
public Object get(String name, Scriptable start) {
|
||||||
Object obj = super.get(name, start);
|
Object obj = super.get(name, start);
|
||||||
if (obj == Scriptable.NOT_FOUND && node != null) {
|
if (obj == Scriptable.NOT_FOUND && proxy != null) {
|
||||||
obj = getFromNode(name);
|
obj = getFromNode(name);
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
|
@ -857,14 +835,14 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* to return the prototype functions in that case.
|
* to return the prototype functions in that case.
|
||||||
*/
|
*/
|
||||||
private Object getFromNode(String name) {
|
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
|
// Property names starting with an underscore is interpreted
|
||||||
// as internal properties
|
// as internal properties
|
||||||
if (name.charAt(0) == '_') {
|
if (name.charAt(0) == '_') {
|
||||||
Object value = getInternalProperty(name);
|
Object value = getInternalProperty(node, name);
|
||||||
if (value != NOT_FOUND)
|
if (value != NOT_FOUND)
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -931,7 +909,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
return NOT_FOUND;
|
return NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object getInternalProperty(String name) {
|
private Object getInternalProperty(INode node, String name) {
|
||||||
if ("__id__".equals(name) || "_id".equals(name)) {
|
if ("__id__".equals(name) || "_id".equals(name)) {
|
||||||
return node.getID();
|
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
|
* @return array containing the names of all properties defined in this object
|
||||||
*/
|
*/
|
||||||
public Object[] getAllIds() {
|
public Object[] getAllIds() {
|
||||||
if (node == null) {
|
if (proxy == null) {
|
||||||
return super.getAllIds();
|
return super.getAllIds();
|
||||||
}
|
}
|
||||||
return getIds();
|
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
|
* @return array containing the names of this object's data properties
|
||||||
*/
|
*/
|
||||||
public Object[] getIds() {
|
public Object[] getIds() {
|
||||||
if (node == null) {
|
if (proxy == null) {
|
||||||
// HopObject prototypes always return an empty array in order not to
|
// HopObject prototypes always return an empty array in order not to
|
||||||
// pollute actual HopObjects properties. Call getAllIds() to get
|
// pollute actual HopObjects properties. Call getAllIds() to get
|
||||||
// a list of properties from a HopObject prototype.
|
// a list of properties from a HopObject prototype.
|
||||||
return new Object[0];
|
return new Object[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
Enumeration en = node.properties();
|
Enumeration en = node.properties();
|
||||||
ArrayList list = new ArrayList();
|
ArrayList list = new ArrayList();
|
||||||
|
@ -1019,8 +997,8 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* @return ...
|
* @return ...
|
||||||
*/
|
*/
|
||||||
public boolean has(int idx, Scriptable start) {
|
public boolean has(int idx, Scriptable start) {
|
||||||
if (node != null) {
|
if (proxy != null) {
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
return (0 <= idx && idx < node.numberOfNodes());
|
return (0 <= idx && idx < node.numberOfNodes());
|
||||||
}
|
}
|
||||||
|
@ -1037,8 +1015,8 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
* @return ...
|
* @return ...
|
||||||
*/
|
*/
|
||||||
public Object get(int idx, Scriptable start) {
|
public Object get(int idx, Scriptable start) {
|
||||||
if (node != null) {
|
if (proxy != null) {
|
||||||
checkNode();
|
INode node = getNode();
|
||||||
|
|
||||||
INode n = node.getSubnodeAt(idx);
|
INode n = node.getSubnodeAt(idx);
|
||||||
|
|
||||||
|
@ -1050,15 +1028,33 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco
|
||||||
return NOT_FOUND;
|
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 representation of this HopObject.
|
||||||
* @return a string representing this HopObject
|
* @return a string representing this HopObject
|
||||||
*/
|
*/
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (node == null) {
|
if (proxy == null) {
|
||||||
return "[HopObject prototype " + className + "]";
|
return "[HopObject prototype " + className + "]";
|
||||||
} else {
|
} 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
|
* .) 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
|
* 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))
|
if (!(node instanceof Node))
|
||||||
throw new RuntimeException ("update only callabel on persistent HopObjects");
|
throw new RuntimeException ("update only callabel on persistent HopObjects");
|
||||||
checkNode();
|
checkNode();
|
||||||
Node n = (Node) node;
|
Node n = (Node) node;
|
||||||
return n.updateSubnodes();
|
return n.updateSubnodes();
|
||||||
}
|
} */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a view having a different order from this Node's subnodelist.
|
* 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)
|
* @param expr the order (like sql-order using the properties instead)
|
||||||
* @return ListViewWrapper holding the information of the ordered view
|
* @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)) {
|
if (!(node instanceof Node)) {
|
||||||
throw new RuntimeException (
|
throw new RuntimeException (
|
||||||
"getOrderedView only callable on persistent HopObjects");
|
"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());
|
Node subnode = new Node("OrderedView", "HopObject", core.app.getWrappedNodeManager());
|
||||||
subnode.setSubnodes(subnodes.getOrderedView(expr));
|
subnode.setSubnodes(subnodes.getOrderedView(expr));
|
||||||
return new HopObject("HopObject", core, subnode, core.getPrototype("HopObject"));
|
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.framework.repository.Resource;
|
||||||
import helma.objectmodel.*;
|
import helma.objectmodel.*;
|
||||||
import helma.objectmodel.db.DbMapping;
|
import helma.objectmodel.db.DbMapping;
|
||||||
|
import helma.objectmodel.db.NodeHandle;
|
||||||
import helma.scripting.*;
|
import helma.scripting.*;
|
||||||
import helma.util.*;
|
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 org.mozilla.javascript.tools.debugger.ScopeProvider;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
@ -637,15 +654,15 @@ public final class RhinoCore implements ScopeProvider {
|
||||||
/**
|
/**
|
||||||
* Get a script wrapper for an instance of helma.objectmodel.INode
|
* Get a script wrapper for an instance of helma.objectmodel.INode
|
||||||
*/
|
*/
|
||||||
public Scriptable getNodeWrapper(INode n) {
|
public Scriptable getNodeWrapper(INode node) {
|
||||||
if (n == null) {
|
if (node == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
HopObject hobj = (HopObject) wrappercache.get(n);
|
HopObject hobj = (HopObject) wrappercache.get(node);
|
||||||
|
|
||||||
if (hobj == null) {
|
if (hobj == null) {
|
||||||
String protoname = n.getPrototype();
|
String protoname = node.getPrototype();
|
||||||
Scriptable op = getValidPrototype(protoname);
|
Scriptable op = getValidPrototype(protoname);
|
||||||
|
|
||||||
// no prototype found for this node
|
// 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
|
// deleted, but the storage layer was able to set a
|
||||||
// DbMapping matching the relational table the object
|
// DbMapping matching the relational table the object
|
||||||
// was fetched from.
|
// was fetched from.
|
||||||
DbMapping dbmap = n.getDbMapping();
|
DbMapping dbmap = node.getDbMapping();
|
||||||
if (dbmap != null && (protoname = dbmap.getTypeName()) != null) {
|
if (dbmap != null && (protoname = dbmap.getTypeName()) != null) {
|
||||||
op = getValidPrototype(protoname);
|
op = getValidPrototype(protoname);
|
||||||
}
|
}
|
||||||
|
@ -666,13 +683,42 @@ public final class RhinoCore implements ScopeProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hobj = new HopObject(protoname, this, n, op);
|
hobj = new HopObject(protoname, this, node, op);
|
||||||
wrappercache.put(n, hobj);
|
wrappercache.put(node, hobj);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 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)
|
protected String postProcessHref(Object obj, String protoName, String href)
|
||||||
throws UnsupportedEncodingException, IOException {
|
throws UnsupportedEncodingException, IOException {
|
||||||
// check if the app.properties specify a href-function to post-process the
|
// 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) {
|
if (obj instanceof INode) {
|
||||||
return getNodeWrapper((INode) obj);
|
return getNodeWrapper((INode) obj);
|
||||||
}
|
}
|
||||||
|
if (obj instanceof NodeHandle) {
|
||||||
|
return getNodeWrapper((NodeHandle) obj);
|
||||||
|
}
|
||||||
|
|
||||||
// Masquerade SystemMap and WrappedMap as native JavaScript objects
|
// Masquerade SystemMap and WrappedMap as native JavaScript objects
|
||||||
if (obj instanceof SystemMap || obj instanceof WrappedMap) {
|
if (obj instanceof SystemMap || obj instanceof WrappedMap) {
|
||||||
|
|
|
@ -85,7 +85,7 @@ class HopObjectProxy implements SerializationProxy {
|
||||||
ref = obj.getClassName();
|
ref = obj.getClassName();
|
||||||
} else {
|
} else {
|
||||||
if (n instanceof Node) {
|
if (n instanceof Node) {
|
||||||
ref = new NodeHandle((Node) n);
|
ref = ((Node) n).getHandle();
|
||||||
} else {
|
} else {
|
||||||
ref = n;
|
ref = n;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class ResourceProperties extends Properties {
|
||||||
private long lastCheck = 0;
|
private long lastCheck = 0;
|
||||||
|
|
||||||
// Time porperties were last modified
|
// Time porperties were last modified
|
||||||
private long lastModified = 0;
|
private long lastModified = System.currentTimeMillis();
|
||||||
|
|
||||||
// Application where to fetch additional resources
|
// Application where to fetch additional resources
|
||||||
private Application app;
|
private Application app;
|
||||||
|
|
Loading…
Add table
Reference in a new issue