helma/src/helma/objectmodel/db/Node.java

1746 lines
54 KiB
Java
Raw Normal View History

// Node.java
// Copyright (c) Hannes Walln<6C>fer 1997-2000
package helma.objectmodel.db;
import java.util.*;
import java.io.*;
import java.sql.Types;
import helma.objectmodel.*;
import helma.util.*;
import com.workingdogs.village.*;
/**
* An implementation of INode that can be stored in the internal database or
* an external relational database.
*/
public class Node implements INode, Serializable {
// The ID of this node's parent node
protected String parentID;
// Ordered list of subnodes of this node
private Vector subnodes;
// Named subnodes (properties) of this node
private Hashtable propMap;
// Other nodes that link to this node. Used for reference counting/checking
private Vector links;
// Other nodes that refer to this node as property. Used for reference counting/checking
private Vector proplinks;
// the name of the (Hop) prototype - this is stored as standard property instead.
// private String prototype;
private String contentType;
private byte content[];
private long created;
private long lastmodified;
private String id, name;
// is this node's main identity as a named property or an
// anonymous node in a subnode collection?
protected boolean anonymous = false;
private void readObject (ObjectInputStream in) throws IOException {
try {
// as a general rule of thumb, if a string can bu null use read/writeObject,
// if not it's save to use read/writeUTF.
// version indicates the serialization version
int version = in.readShort ();
id = in.readUTF ();
name = in.readUTF ();
parentID = (String) in.readObject ();
created = in.readLong ();
lastmodified = in.readLong ();
content = (byte[]) in.readObject ();
contentType = (String) in.readObject ();
subnodes = (Vector) in.readObject ();
links = (Vector) in.readObject ();
proplinks = (Vector) in.readObject ();
propMap = (Hashtable) in.readObject ();
anonymous = in.readBoolean ();
if (version == 2)
prototype = in.readUTF ();
else if (version == 3)
prototype = (String) in.readObject ();
} catch (ClassNotFoundException x) {
throw new IOException (x.toString ());
}
}
private void writeObject (ObjectOutputStream out) throws IOException {
out.writeShort (3); // serialization version
out.writeUTF (id);
out.writeUTF (name);
out.writeObject (parentID);
out.writeLong (created);
out.writeLong (lastmodified);
out.writeObject (content);
out.writeObject (contentType);
DbMapping smap = dbmap == null ? null : dbmap.getSubnodeMapping ();
if (smap != null && smap.isRelational ())
out.writeObject (null);
else
out.writeObject (subnodes);
out.writeObject (links);
out.writeObject (proplinks);
out.writeObject (propMap);
out.writeBoolean (anonymous);
out.writeObject (prototype);
}
transient String prototype;
transient INode cacheNode;
transient WrappedNodeManager nmgr;
transient DbMapping dbmap;
transient Key primaryKey = null;
transient DbMapping parentmap;
transient String subnodeRelation = null;
transient long lastSubnodeFetch = 0;
transient long lastSubnodeChange = 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 int state;
// transient String nameProp; // the name of the property which defines the name of this node.
transient boolean adoptName = true; // little helper to know if this node is being converted
static final long serialVersionUID = -3740339688506633675L;
/**
* Constructor used for virtual nodes.
*/
public Node (Node home, String propname, WrappedNodeManager nmgr, String prototype) {
this.nmgr = nmgr;
setParent (home);
// this.dbmap = null;
// generate a key for the virtual node that can't be mistaken for a JDBC-URL
this.id = Key.makeVirtualID (parentmap, parentID, propname);
this.name = propname;
this.anonymous = false;
setPrototype (prototype);
this.state = VIRTUAL;
}
/**
* Constructor used for nodes being stored in a relational database table.
*/
public Node (DbMapping dbmap, Record rec, WrappedNodeManager nmgr) throws DataSetException {
this.nmgr = nmgr;
this.dbmap = dbmap;
id = rec.getValue (dbmap.getIDField ()).asString ();
checkWriteLock ();
String nameField = dbmap.getNameField ();
name = nameField == null ? id : rec.getValue (nameField).asString ();
2001-01-11 19:25:38 +00:00
if (name == null || name.length() == 0)
name = id;
setPrototype (dbmap.getTypeName ());
for (Enumeration e=dbmap.db2prop.elements (); e.hasMoreElements(); ) {
Relation rel = (Relation) e.nextElement ();
// NOTE: this should never be the case, since we're just looping through
// mappnigs with a local db column
if (rel.direction != Relation.PRIMITIVE && rel.direction != Relation.FORWARD)
continue;
Value val = rec.getValue (rel.getDbField ());
if (val.isNull ())
continue;
Property newprop = new Property (rel.propname, this);
switch (val.type ()) {
case Types.BIT:
newprop.setBooleanValue (val.asBoolean());
break;
case Types.TINYINT:
case Types.BIGINT:
case Types.SMALLINT:
case Types.INTEGER:
newprop.setIntegerValue (val.asLong());
break;
case Types.REAL:
case Types.FLOAT:
case Types.DOUBLE:
newprop.setFloatValue (val.asDouble());
break;
case Types.DECIMAL:
case Types.NUMERIC:
java.math.BigDecimal num = val.asBigDecimal ();
if (num.scale() > 0)
newprop.setFloatValue (val.asDouble());
else
newprop.setIntegerValue (val.asLong());
break;
case Types.LONGVARBINARY:
case Types.VARBINARY:
case Types.BINARY:
newprop.setStringValue (val.asString());
break;
case Types.LONGVARCHAR:
case Types.CHAR:
case Types.VARCHAR:
case Types.OTHER:
newprop.setStringValue (val.asString());
break;
case Types.DATE:
case Types.TIME:
case Types.TIMESTAMP:
newprop.setDateValue (val.asTimestamp());
break;
case Types.NULL:
continue;
default:
newprop.setStringValue (val.asString());
break;
}
if(propMap == null)
propMap = new Hashtable ();
propMap.put (rel.propname.toLowerCase(), newprop);
// mark property as clean, since it's fresh from the db
newprop.dirty = false;
// if the property is a pointer to another node, change the property type to NODE
if (rel.direction == Relation.FORWARD) {
newprop.nvalueID = newprop.getStringValue ();
newprop.type = IProperty.NODE;
}
}
markAs (CLEAN);
}
/**
* Creates a new Node with the given name.
*/
public Node (String n, WrappedNodeManager nmgr) {
this.nmgr = nmgr;
id = nmgr.generateID (dbmap);
checkWriteLock ();
this.name = n == null || "".equals (n) ? id : n;
created = lastmodified = System.currentTimeMillis ();
adoptName = true;
markAs (TRANSIENT);
// nmgr.registerNode (this);
}
/**
* Creates a new Node with the given name. Only used by NodeManager for "root nodes" and
* not in a Transaction context, which is why we can immediately mark it as CLEAN.
*/
protected Node (String name, String id, String prototype, WrappedNodeManager nmgr) {
this.nmgr = nmgr;
this.id = id;
this.name = name == null || "".equals (name) ? id : name;
if (prototype != null)
setPrototype (prototype);
created = lastmodified = System.currentTimeMillis ();
markAs (CLEAN);
}
/**
* Creates a new instance of Node, transforming from another implementation of
* interface INode. This Constructor is used when a transient
* node is converted into a persistent-capable one, hence the status is set to NEW.
*/
private Node (INode node, Hashtable ntable, boolean conversionRoot, WrappedNodeManager nmgr) {
this.nmgr = nmgr;
this.dbmap = node.getDbMapping ();
this.id = nmgr.generateID (dbmap);
checkWriteLock ();
this.name = node.getName ();
this.prototype = node.getPrototype ();
created = lastmodified = System.currentTimeMillis ();
setContent (node.getContent (), node.getContentType ());
created = lastmodified = System.currentTimeMillis ();
ntable.put (node, this);
// only take over name from property if this is not the root of the current node conversion
adoptName = !conversionRoot;
for (Enumeration e = node.getSubnodes (); e.hasMoreElements (); ) {
INode next = (INode) e.nextElement ();
Node nextc = (next instanceof Node) ? (Node) next : (Node) ntable.get (next); // is this a Node already?
if (nextc == null)
nextc = new Node (next, ntable, true, nmgr);
if (nextc.state == INVALID)
nextc = nmgr.getNode (nextc.getID (), nextc.getDbMapping ());
addNode (nextc);
}
for (Enumeration e = node.properties (); e.hasMoreElements (); ) {
IProperty next = node.get ((String) e.nextElement (), false);
if (next == null)
continue;
int t = next.getType ();
if (t == IProperty.NODE) {
INode n = next.getNodeValue ();
Node nextc = (n instanceof Node) ? (Node) n : (Node) ntable.get (n); // is this a Node already?
if (nextc == null)
nextc = new Node (n, ntable, true, nmgr);
if (nextc.state == INVALID)
nextc = nmgr.getNode (nextc.getID (), nextc.getDbMapping ());
setNode (next.getName (), nextc);
} else if (t == IProperty.STRING) {
setString (next.getName (), next.getStringValue ());
} else if (t == IProperty.INTEGER) {
setInteger (next.getName (), next.getIntegerValue ());
} else if (t == IProperty.FLOAT) {
setFloat (next.getName (), next.getFloatValue ());
} else if (t == IProperty.BOOLEAN) {
setBoolean (next.getName (), next.getBooleanValue ());
} else if (t == IProperty.DATE) {
setDate (next.getName (), next.getDateValue ());
} else if (t == IProperty.JAVAOBJECT) {
setJavaObject (next.getName (), next.getJavaObjectValue ());
}
}
adoptName = true; // switch back to normal name adoption behaviour
markAs (NEW);
// nmgr.registerNode (this);
}
protected synchronized void checkWriteLock () {
// System.err.println ("registering writelock for "+this.getName ()+" ("+lock+") to "+Thread.currentThread ());
if (state == TRANSIENT)
return; // no need to lock transient node
Transactor current = (Transactor) Thread.currentThread ();
if (!current.isActive ())
throw new helma.framework.TimeoutException ();
if (state == INVALID) {
IServer.getLogger().log ("Got Invalid Node: "+this);
Thread.dumpStack ();
throw new ConcurrencyException ("Node "+this+" was invalidated by another thread.");
}
if (lock != null && lock != current && lock.isAlive () && lock.isActive ()) {
IServer.getLogger().log ("Concurrency conflict for "+this+", lock held by "+lock);
throw new ConcurrencyException ("Tried to modify "+this+" from two threads at the same time.");
}
current.visitNode (this);
lock = current;
}
protected synchronized void clearWriteLock () {
lock = null;
// check if the subnodes are relational.
// If so, clear the subnode vector.
// DbMapping smap = dbmap == null ? null : dbmap.getSubnodeMapping ();
// if (smap != null && smap.isRelational ())
// subnodes = null;
}
protected void markAs (int s) {
if (state == INVALID || state == VIRTUAL || state == TRANSIENT)
return;
state = s;
if (Thread.currentThread () instanceof Transactor) {
Transactor tx = (Transactor) Thread.currentThread ();
if (s == CLEAN) {
clearWriteLock ();
tx.dropNode (this);
} else {
tx.visitNode (this);
if (s == NEW) {
clearWriteLock ();
tx.visitCleanNode (this);
}
}
}
}
public int getState () {
return state;
}
public void setState (int s) {
this.state = s;
}
/* Mark node as invalid so it is re-fetched from the database */
public void invalidate () {
checkWriteLock ();
nmgr.evictNode (this);
}
/**
* navigation-related
*/
public String getID () {
return id;
}
protected void setID (String id) {
this.id = id;
((Transactor) Thread.currentThread()).visitCleanNode (this);
}
public boolean isAnonymous () {
return anonymous;
}
public String getName () {
return name;
}
/**
* Get something to identify this node within a URL. This is the ID for anonymous nodes
* and a property value for named properties.
*/
public String getNameOrID () {
// if subnodes are also mounted as properties, try to get the "nice" prop value
// instead of the id by turning the anonymous flag off.
if (anonymous && parentmap != null) {
Relation prel = parentmap.getPropertyRelation();
if (prel != null && prel.subnodesAreProperties && !prel.usesPrimaryKey ()) try {
Relation localrel = dbmap.columnNameToProperty (prel.getRemoteField ());
String propvalue = getString (localrel.propname, false);
if (propvalue != null && propvalue.length() > 0) {
setName (propvalue);
anonymous = false;
// nameProp = localrel.propname;
}
} catch (Exception ignore) {} // just fall back to ID
}
2001-01-11 19:25:38 +00:00
return anonymous || name == null || name.length() == 0 ? id : name;
}
public String getFullName () {
return getFullName (null);
}
public String getFullName (INode root) {
String fullname = "";
String divider = null;
StringBuffer b = new StringBuffer ();
INode p = this;
int loopWatch = 0;
while (p != null && p.getParent () != null && p != root) {
if (divider != null)
b.insert (0, divider);
else
divider = "/";
b.insert (0, p.getNameOrID ());
p = p.getParent ();
loopWatch++;
if (loopWatch > 10) {
b.insert (0, "...");
break;
}
}
return b.toString ();
}
public INode[] getPath () {
int pathSize = 1;
INode p = getParent ();
while (p != null) {
pathSize +=1;
p = p.getParent ();
if (pathSize > 100) // sanity check
break;
}
INode path[] = new INode[pathSize];
p = this;
for (int i = pathSize-1; i>=0; i--) {
path[i] = p;
p = p.getParent ();
}
return path;
}
public String getPrototype () {
if (prototype == null && propMap != null) {
// retrieve prototype name from properties
Property pp = (Property) propMap.get ("prototype");
if (pp != null)
prototype = pp.getStringValue ();
}
return prototype;
}
public void setPrototype (String proto) {
this.prototype = proto;
}
public void setDbMapping (DbMapping dbmap) {
if (this.dbmap != dbmap) {
this.dbmap = dbmap;
primaryKey = null;
((Transactor) Thread.currentThread()).visitCleanNode (this);
}
}
public DbMapping getDbMapping () {
return dbmap;
}
public Key getKey () {
if (primaryKey == null)
primaryKey = new Key (dbmap, id);
return primaryKey;
}
public void setSubnodeRelation (String rel) {
if ((rel == null && this.subnodeRelation == null)
|| (rel != null && rel.equalsIgnoreCase (this.subnodeRelation)))
return;
checkWriteLock ();
this.subnodeRelation = rel;
DbMapping smap = dbmap == null ? null : dbmap.getSubnodeMapping ();
if (smap != null && smap.isRelational ()) {
subnodes = null;
subnodeCount = -1;
}
}
public String getSubnodeRelation () {
return subnodeRelation;
}
public void setName (String name) {
// "/" is used as delimiter, so it's not a legal char
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; // use id as name
else
this.name = name;
}
/**
* Register a node as parent of the present node. We can't refer to the node directly, so we use
* the ID + DB map combo.
*/
public void setParent (Node parent) {
this.parentID = parent == null ? null : parent.getID();
this.parentmap = parent == null ? null : parent.getDbMapping();
}
/**
* This version of setParent additionally marks the node as anonymous or non-anonymous,
* depending on the string argument.
*/
public void setParent (Node parent, String propertyName) {
this.parentID = parent == null ? null : parent.getID();
this.parentmap = parent == null ? null : parent.getDbMapping();
if (propertyName == null) {
this.anonymous = true;
} else {
this.anonymous = false;
this.name = propertyName;
}
}
public INode getParent () {
// check what's specified in the type.properties for this node.
String[] parentProps = null;
if (dbmap != null && dbmap.isRelational ())
parentProps = dbmap.getParentPropNames ();
// check if current parent candidate matches presciption, if not, try to get it
if (parentProps != null) {
for (int i=0; i<parentProps.length; i++) {
INode pn = getNode (parentProps[i], false);
if (pn != null) {
// see if dbmapping specifies anonymity for this node
Boolean[] ano = dbmap.getAnonymous ();
if (ano != null && ano.length > i)
anonymous = ano[i].booleanValue();
return pn;
}
}
}
// fall back to heuristic parent (the node that fetched this one from db)
if (parentID == null)
return null;
return nmgr.getNode (parentID, parentmap);
}
/**
* INode-related
*/
public INode addNode (INode elem) {
return addNode (elem, numberOfNodes ());
}
public INode addNode (INode elem, int where) {
Node node = null;
if (elem instanceof Node)
node = (Node) elem;
else
node = convert (elem);
// if the new node is marked as TRANSIENT and this node is not, mark new node as NEW
if (state != TRANSIENT && node.state == TRANSIENT)
node.makePersistentCapable ();
String n = node.getName();
if (n.indexOf('/') > -1)
throw new RuntimeException ("\"/\" found in Node name.");
// only lock node if it has to be modified for a change in subnodes
if (!ignoreSubnodeChange ())
checkWriteLock ();
node.checkWriteLock ();
// only mark this node as modified if subnodes are not in relational db
// pointing to this node.
if (!ignoreSubnodeChange () && (state == CLEAN || state == DELETED))
markAs (MODIFIED);
if (node.state == CLEAN || node.state == DELETED)
node.markAs (MODIFIED);
loadNodes ();
if (where < 0 || where > numberOfNodes ())
where = numberOfNodes ();
if (node.getParent () != null) {
// this makes the job of addLink, which means that addLink and addNode
// are functionally equivalent now.
if (node.getParent () != this || !node.anonymous) {
node.registerLink (this);
}
}
if (subnodes != null && subnodes.contains (node.getID ())) {
// Node is already subnode of this - just move to new position
subnodes.removeElement (node.getID ());
where = Math.min (where, numberOfNodes ());
subnodes.insertElementAt (node.getID (), where);
} else {
if (subnodes == null) subnodes = new ExternalizableVector ();
subnodes.insertElementAt (node.getID (), where);
// check if properties are subnodes (_properties.aresubnodes=true)
if (dbmap != null && node.dbmap != null) {
Relation prel = dbmap.getPropertyRelation();
if (prel != null && prel.subnodesAreProperties && !prel.usesPrimaryKey ()) {
Relation localrel = node.dbmap.columnNameToProperty (prel.getRemoteField ());
// if no relation from db column to prop name is found, assume that both are equal
String propname = localrel == null ? prel.getRemoteField() : localrel.propname;
String prop = node.getString (propname, false);
if (prop != null && prop.length() > 0) {
INode old = getNode (prop, false);
if (old != null && old != node) {
unset (prop);
removeNode (old);
}
// throw new RuntimeException ("Property "+prop+" is already defined for "+this);
setNode (prop, node);
}
}
}
if (node.getParent () == null && !"root".equalsIgnoreCase (node.getPrototype ())) {
node.setParent (this);
node.anonymous = true;
}
}
// check if the subnode is in relational db and needs to link back to this
// in order to make it a subnode
if (dbmap != null) {
Relation srel = dbmap.getSubnodeRelation ();
if (srel != null && srel.direction == Relation.BACKWARD) {
Relation backlink = srel.other.columnNameToProperty (srel.getRemoteField());
if (backlink != null && backlink.propname != null) {
if (node.get (backlink.propname, false) == null) {
if (this.state == VIRTUAL)
node.setString (backlink.propname, getParent().getID());
else
node.setString (backlink.propname, getID());
}
}
}
}
lastmodified = System.currentTimeMillis ();
lastSubnodeChange = lastmodified;
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.SUBNODE_ADDED, node));
return node;
}
public INode createNode () {
return createNode (null, numberOfNodes ()); // create new node at end of subnode array
}
public INode createNode (int where) {
return createNode (null, where);
}
public INode createNode (String nm) {
// parameter where is ignored if nm != null so we try to avoid calling numberOfNodes()
return createNode (nm, nm == null ? numberOfNodes () : 0);
}
public INode createNode (String nm, int where) {
checkWriteLock ();
boolean anon = false;
if (nm == null || "".equals (nm.trim ()))
anon = true;
Node n = new Node (nm, nmgr);
if (anon)
addNode (n, where);
else
setNode (nm, n);
return n;
}
/**
* register a node that links to this node.
*/
protected void registerLink (Node from) {
if (links == null)
links = new ExternalizableVector ();
Object fromID = from.getID ();
if (!links.contains (fromID))
links.addElement (fromID);
}
public INode getSubnode (String path) {
StringTokenizer st = new StringTokenizer (path, "/");
Node retval = this, runner;
while (st.hasMoreTokens () && retval != null) {
runner = retval;
String next = st.nextToken().trim().toLowerCase ();
if ("".equals (next)) {
retval = this;
} else {
runner.loadNodes ();
boolean found = runner.subnodes == null ? false : runner.subnodes.contains (next);
if (!found)
retval = null;
else {
Relation srel = null;
DbMapping smap = null;
if (runner.dbmap != null) {
srel = runner.dbmap.getSubnodeRelation ();
smap = runner.dbmap.getSubnodeMapping ();
}
// check if there is a group-by relation
if (srel != null && srel.groupby != null)
retval = nmgr.getNode (this, next, srel);
else
retval = nmgr.getNode (next, smap);
}
if (retval != null && retval.parentID == null && !"root".equalsIgnoreCase (retval.getPrototype ())) {
retval.setParent (runner);
retval.anonymous = true;
}
}
if (retval == null) {
retval = (Node) runner.getNode (next, false);
}
}
return retval;
}
public INode getSubnodeAt (int index) {
loadNodes ();
if (subnodes == null)
return null;
Relation srel = null;
DbMapping smap = null;
if (dbmap != null) {
srel = dbmap.getSubnodeRelation ();
smap = dbmap.getSubnodeMapping ();
}
Node retval = null;
if (subnodes.size () > index) {
// check if there is a group-by relation
if (srel != null && srel.groupby != null)
retval = nmgr.getNode (this, (String) subnodes.elementAt (index), srel);
else
retval = nmgr.getNode ((String) subnodes.elementAt (index), smap);
if (retval != null && retval.parentID == null && !"root".equalsIgnoreCase (retval.getPrototype ())) {
retval.setParent (this);
retval.anonymous = true;
}
}
return retval;
}
public Node getGroupbySubnode (String sid) {
2001-01-02 15:28:56 +00:00
loadNodes ();
if (subnodes.contains (sid)) try {
2001-01-02 15:28:56 +00:00
Node node = new Node (this, sid, nmgr, null);
// set "groupname" property to value of groupby field
node.setString ("groupname", sid);
Relation srel = dbmap.getSubnodeRelation ();
Relation prel = dbmap.getPropertyRelation ();
2001-01-02 15:28:56 +00:00
DbMapping dbm = new DbMapping ();
Relation gsrel = srel.getGroupbySubnodeRelation();
dbm.setSubnodeMapping (srel.other);
dbm.setSubnodeRelation (gsrel);
if (prel != null) {
dbm.setPropertyMapping (prel.other);
dbm.setPropertyRelation (prel.getGroupbyPropertyRelation());
}
2001-01-02 15:28:56 +00:00
node.setDbMapping (dbm);
String snrel = "WHERE "+srel.groupby +"='"+sid+"'";
if (gsrel.direction == Relation.BACKWARD)
snrel += " AND "+gsrel.getRemoteField()+"='"+getNonVirtualHomeID()+"'";
if (gsrel.order != null)
snrel += " ORDER BY "+gsrel.order;
2001-01-02 15:28:56 +00:00
node.setSubnodeRelation (snrel);
return node;
} catch (Exception noluck) {
IServer.getLogger ().log ("Error creating group-by node for "+sid+": "+noluck);
}
2001-01-02 15:28:56 +00:00
return null;
}
public boolean remove () {
checkWriteLock ();
if (anonymous)
getParent ().unset (name);
else
getParent ().removeNode (this);
return true;
}
public void removeNode (INode node) {
IServer.getLogger().log ("removing: "+ node);
Node n = (Node) node;
checkWriteLock ();
n.checkWriteLock ();
releaseNode (n);
if (n.getParent () == this) {
n.deepRemoveNode ();
} else {
// removed just a link, not the main node.
if (n.links != null) {
n.links.removeElement (this.id);
if (n.state == CLEAN) n.markAs (MODIFIED);
}
}
}
/**
* "Locally" remove a subnode from the subnodes table.
* The logical stuff necessary for keeping data consistent is done in removeNode().
*/
protected void releaseNode (Node node) {
if (subnodes != null)
subnodes.removeElement (node.getID ());
lastSubnodeChange = System.currentTimeMillis ();
// check if the subnode is in relational db and has a link back to this
// which needs to be unset
if (dbmap != null) {
Relation srel = dbmap.getSubnodeRelation ();
if (srel != null && srel.direction == Relation.BACKWARD) {
Relation backlink = srel.other.columnNameToProperty (srel.getRemoteField ());
if (backlink != null && id.equals (node.getString (backlink.propname, false)))
node.unset (backlink.propname);
}
}
// check if subnodes are handled as virtual fs
if (dbmap != null && node.dbmap != null) {
Relation prel = dbmap.getPropertyRelation();
if (prel != null && prel.subnodesAreProperties && !prel.usesPrimaryKey ()) {
Relation localrel = node.dbmap.columnNameToProperty (prel.getRemoteField());
// if no relation from db column to prop name is found, assume that both are equal
String propname = localrel == null ? prel.getRemoteField () : localrel.propname;
String prop = node.getString (propname, false);
if (prop != null && getNode (prop, false) == node)
unset (prop);
}
}
// If subnodes are relational no need to mark this node as modified
if (ignoreSubnodeChange ())
return;
// 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);
if (state == CLEAN) markAs (MODIFIED);
}
/**
* Delete the node from the db. This mainly tries to notify all nodes referring to this that
* it's going away. For nodes from the embedded db it also does a cascading delete, since
* it can tell which nodes are actual children and which are just linked in.
*/
protected void deepRemoveNode () {
// notify nodes that link to this node that it is being deleted.
int l = links == null ? 0 : links.size ();
for (int i = 0; i < l; i++) {
// TODO: solve dbmap problem
Node link = nmgr.getNode ((String) links.elementAt (i), null);
if (link != null) link.releaseNode (this);
}
// clean up all nodes that use n as a property
if (proplinks != null) {
for (Enumeration e1 = proplinks.elements (); e1.hasMoreElements (); ) try {
String pid = (String) e1.nextElement ();
Node pnode = nmgr.getNode (pid, null);
if (pnode != null) {
IServer.getLogger().log("Warning: Can't unset node property of "+pnode.getFullName ());
}
} catch (Exception ignore) {}
}
// tell all nodes that are properties of n that they are no longer used as such
if (propMap != null) {
for (Enumeration e2 = propMap.elements (); e2.hasMoreElements (); ) {
Property p = (Property) e2.nextElement ();
if (p != null && p.type == Property.NODE)
p.unregisterNode ();
}
}
// cascading delete of all subnodes. This is never done for relational subnodes, because
// the parent info is not 100% accurate for them.
if (subnodes != null) {
Vector v = new Vector ();
// removeElement modifies the Vector we are enumerating, so we are extra careful.
for (Enumeration e3 = getSubnodes (); e3.hasMoreElements(); ) {
v.addElement (e3.nextElement());
}
int m = v.size ();
for (int i=0; i<m; i++) {
// getParent() is heuristical/implicit for relational nodes, so we don't base
// a cascading delete on that criterium for relational nodes.
Node n = (Node) v.elementAt (i);
if (n.dbmap == null || !n.dbmap.isRelational())
removeNode (n);
}
}
// mark the node as deleted
setParent (null);
markAs (DELETED);
}
public int contains (INode n) {
if (n == null)
return -1;
loadNodes ();
if (subnodes == null)
return -1;
return subnodes.indexOf (n.getID ());
}
/**
* Count the subnodes of this node. If they're stored in a relational data source, we
* may actually load their IDs in order to do this.
*/
public int numberOfNodes () {
// If the subnodes are loaded aggressively, we really just
// do a count statement, otherwise we just return the size of the id index.
// (after loading it, if it's coming from a relational data source).
DbMapping smap = dbmap == null ? null : dbmap.getSubnodeMapping ();
if (smap != null && smap.isRelational ()) {
// check if subnodes need to be rechecked
Relation subRel = dbmap.getSubnodeRelation ();
if (subRel.aggressiveLoading) {
// we don't want to load *all* nodes if we just want to count them
long lastChange = subRel.aggressiveCaching ? lastSubnodeChange : smap.lastDataChange;
// also reload if the type mapping has changed.
lastChange = Math.max (lastChange, dbmap.getLastTypeChange ());
if (lastChange < lastSubnodeFetch && subnodes != null) {
// we can use the nodes vector to determine number of subnodes
subnodeCount = subnodes.size();
lastSubnodeCount = System.currentTimeMillis ();
} else if (lastChange >= lastSubnodeCount || subnodeCount < 0) {
// count nodes in db without fetching anything
subnodeCount = nmgr.countNodes (this, subRel);
lastSubnodeCount = System.currentTimeMillis ();
}
return subnodeCount;
}
}
loadNodes ();
return subnodes == null ? 0 : subnodes.size ();
}
/**
* Make sure the subnode index is loaded for subnodes stored in a relational data source.
* Depending on the subnode.loadmode specified in the type.properties, we'll load just the
* ID index or the actual nodes.
*/
protected void loadNodes () {
DbMapping smap = dbmap == null ? null : dbmap.getSubnodeMapping ();
if (smap != null && smap.isRelational ()) {
// check if subnodes need to be reloaded
Relation subRel = dbmap.getSubnodeRelation ();
synchronized (this) {
long lastChange = subRel.aggressiveCaching ? lastSubnodeChange : smap.lastDataChange;
// also reload if the type mapping has changed.
lastChange = Math.max (lastChange, dbmap.getLastTypeChange ());
if (lastChange >= lastSubnodeFetch || subnodes == null) {
if (subRel.aggressiveLoading)
subnodes = nmgr.getNodes (this, dbmap.getSubnodeRelation());
else
subnodes = nmgr.getNodeIDs (this, dbmap.getSubnodeRelation());
lastSubnodeFetch = System.currentTimeMillis ();
}
}
}
}
public Enumeration getSubnodes () {
loadNodes ();
class Enum implements Enumeration {
int count = 0;
public boolean hasMoreElements () {
return count < numberOfNodes ();
}
public Object nextElement () {
return getSubnodeAt (count++);
}
}
return new Enum ();
}
private boolean ignoreSubnodeChange () {
// return true if a change in subnodes can be ignored because it is
// stored in the subnodes themselves.
Relation rel = dbmap == null ? null : dbmap.getSubnodeRelation();
return (rel != null && rel.direction == Relation.BACKWARD);
}
/**
* Get all properties of this node.
*/
public Enumeration properties () {
/* final Relation prel = dbmap == null ? null : dbmap.getPropertyRelation ();
final DbMapping pmap = prel == null ? null : prel.other;
if (pmap != null && pmap.isRelational ()) {
class Enum implements Enumeration {
// get relational property nodes from db
Vector propnodes = nmgr.getNodes (Node.this, prel.subnodesAreProperties ?
dbmap.getSubnodeRelation () : prel);
// add non-relational properties from propMap
Enumeration penum = propMap == null ? null : propMap.elements ();
int size = propnodes.size ();
int psize = propMap == null ? 0 : propMap.size();
int count = 0;
public boolean hasMoreElements () {
return count < size+psize;
}
public Object nextElement () {
// first deliver local, non-relational properties ....
if (penum != null && penum.hasMoreElements ()) {
count++;
return penum.nextElement ();
}
// .... then the relational ones.
Node n = nmgr.getNode ((String) propnodes.elementAt (count - psize), pmap);
Property prop = new Property (n.getName (), Node.this, n);
count++;
return prop;
}
}
return new Enum ();
} */
if (dbmap != null && dbmap.prop2db.size() > 0)
return dbmap.prop2db.keys();
else
return propMap == null ? new EmptyEnumeration () : propMap.keys ();
// NOTE: we don't enumerate node properties here
// return propMap == null ? new Vector ().elements () : propMap.elements ();
}
public IProperty get (String propname, boolean inherit) {
return getProperty (propname, inherit);
}
public String getParentInfo () {
return "anonymous:"+anonymous+",parentID:"+parentID+",parentmap:"+parentmap+",parent:"+getParent();
}
protected Property getProperty (String propname, boolean inherit) {
// IServer.getLogger().log ("GETTING PROPERTY: "+propname);
if (propname == null)
return null;
Property prop = propMap == null ? null : (Property) propMap.get (propname.toLowerCase ());
// See if this could be a relationally linked node which still doesn't know
// (i.e, still thinks it's just the key as a string)
DbMapping pmap = dbmap == null ? null : dbmap.getExactPropertyMapping (propname);
if (pmap != null && prop != null && prop.type != IProperty.NODE) {
// this is a relational node stored by id but we still think it's just a string. fix it
prop.nvalueID = prop.getStringValue ();
prop.type = IProperty.NODE;
}
if (prop == null && dbmap != null) {
Relation prel = dbmap.getPropertyRelation (propname);
if (prel == null)
prel = dbmap.getPropertyRelation ();
if (prel != null && (prel.direction == Relation.DIRECT || prel.virtual)) {
// this *may* be a relational node stored by property name
try {
Node pn = nmgr.getNode (this, propname, prel);
if (pn != null) {
if (pn.parentID == null && !"root".equalsIgnoreCase (pn.getPrototype ())) {
pn.setParent (this);
pn.name = propname;
pn.anonymous = false;
}
prop = new Property (propname, this, pn);
}
} catch (RuntimeException nonode) {
// wasn't a node after all
}
}
}
if (prop == null && inherit && getParent () != null) {
prop = ((Node) getParent ()).getProperty (propname, inherit);
}
return prop;
}
public String getString (String propname, String defaultValue, boolean inherit) {
String propValue = getString (propname, inherit);
return propValue == null ? defaultValue : propValue;
}
public String getString (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getStringValue ();
} catch (Exception ignore) {}
return null;
}
public long getInteger (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getIntegerValue ();
} catch (Exception ignore) {}
return 0;
}
public double getFloat (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getFloatValue ();
} catch (Exception ignore) {}
return 0.0;
}
public Date getDate (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getDateValue ();
} catch (Exception ignore) {}
return null;
}
public boolean getBoolean (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getBooleanValue ();
} catch (Exception ignore) {}
return false;
}
public INode getNode (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getNodeValue ();
} catch (Exception ignore) {}
return null;
}
public Object getJavaObject (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getJavaObjectValue ();
} catch (Exception ignore) {}
return null;
}
public void setString (String propname, String value) {
// IServer.getLogger().log ("setting String prop");
checkWriteLock ();
if (propMap == null)
propMap = new Hashtable ();
propname = propname.trim ();
String p2 = propname.toLowerCase ();
Property prop = (Property) propMap.get (p2);
String oldvalue = null;
if (prop != null) {
oldvalue = prop.getStringValue ();
// check if the value has changed
if (value != null && value.equals (oldvalue))
return;
prop.setStringValue (value);
} else {
prop = new Property (propname, this);
prop.setStringValue (value);
propMap.put (p2, prop);
}
// check if this may have an effect on the node's URL when using subnodesAreProperties
INode parent = getParent ();
if (parent != null && parent.getDbMapping() != null) {
// check if this node is already registered with the old name; if so, remove it.
// then set parent's property to this node for the new name value
parentmap = parent.getDbMapping ();
Relation prel = parentmap.getPropertyRelation ();
if (prel != null && prel.subnodesAreProperties && propname.equals (prel.getRemoteField())) {
INode n = parent.getNode (value, false);
if (n != null && n != this) {
parent.unset (value);
parent.removeNode (n);
}
if (oldvalue != null) {
n = parent.getNode (oldvalue, false);
if (n == this) {
parent.unset (oldvalue);
parent.addNode (this);
// let the node cache know this key's not for this node anymore.
nmgr.evictKey (new Key (prel.other, prel.getKeyID (parent, oldvalue)));
}
}
parent.setNode (value, this);
setName (value);
}
}
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
if (state == CLEAN) markAs (MODIFIED);
}
public void setInteger (String propname, long value) {
// IServer.getLogger().log ("setting bool prop");
checkWriteLock ();
if (propMap == null)
propMap = new Hashtable ();
propname = propname.trim ();
String p2 = propname.toLowerCase ();
Property prop = (Property) propMap.get (p2);
if (prop != null) {
prop.setIntegerValue (value);
} else {
prop = new Property (propname, this);
prop.setIntegerValue (value);
propMap.put (p2, prop);
}
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
if (state == CLEAN) markAs (MODIFIED);
}
public void setFloat (String propname, double value) {
// IServer.getLogger().log ("setting bool prop");
checkWriteLock ();
if (propMap == null)
propMap = new Hashtable ();
propname = propname.trim ();
String p2 = propname.toLowerCase ();
Property prop = (Property) propMap.get (p2);
if (prop != null) {
prop.setFloatValue (value);
} else {
prop = new Property (propname, this);
prop.setFloatValue (value);
propMap.put (p2, prop);
}
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
if (state == CLEAN) markAs (MODIFIED);
}
public void setBoolean (String propname, boolean value) {
// IServer.getLogger().log ("setting bool prop");
checkWriteLock ();
if (propMap == null)
propMap = new Hashtable ();
propname = propname.trim ();
String p2 = propname.toLowerCase ();
Property prop = (Property) propMap.get (p2);
if (prop != null) {
prop.setBooleanValue (value);
} else {
prop = new Property (propname, this);
prop.setBooleanValue (value);
propMap.put (p2, prop);
}
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
if (state == CLEAN) markAs (MODIFIED);
}
public void setDate (String propname, Date value) {
// IServer.getLogger().log ("setting date prop");
checkWriteLock ();
if (propMap == null)
propMap = new Hashtable ();
propname = propname.trim ();
String p2 = propname.toLowerCase ();
Property prop = (Property) propMap.get (p2);
if (prop != null) {
prop.setDateValue (value);
} else {
prop = new Property (propname, this);
prop.setDateValue (value);
propMap.put (p2, prop);
}
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
if (state == CLEAN) markAs (MODIFIED);
}
public void setJavaObject (String propname, Object value) {
// IServer.getLogger().log ("setting jobject prop");
checkWriteLock ();
if (propMap == null)
propMap = new Hashtable ();
propname = propname.trim ();
String p2 = propname.toLowerCase ();
Property prop = (Property) propMap.get (p2);
if (prop != null) {
prop.setJavaObjectValue (value);
} else {
prop = new Property (propname, this);
prop.setJavaObjectValue (value);
propMap.put (p2, prop);
}
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
if (state == CLEAN) markAs (MODIFIED);
}
public void setNode (String propname, INode value) {
// IServer.getLogger().log ("setting node prop");
checkWriteLock ();
Node n = null;
if (value instanceof Node)
n = (Node) value;
else
n = convert (value);
// if the new node is marked as TRANSIENT and this node is not, mark new node as NEW
if (state != TRANSIENT && n.state == TRANSIENT)
n.makePersistentCapable ();
n.checkWriteLock ();
// check if the main identity of this node is as a named property
// or as an anonymous node in a collection
if (n.getParent () == null && n.adoptName && !"root".equalsIgnoreCase (n.getPrototype ())) {
n.setParent (this);
n.name = propname;
n.anonymous = false;
// IServer.getLogger().log ("adopted named node: "+n.getFullName ());
} // else IServer.getLogger().log ("not adopted: "+n.getFullName ());
if (propMap == null)
propMap = new Hashtable ();
propname = propname.trim ();
String p2 = propname.toLowerCase ();
DbMapping nmap = dbmap == null ? null : dbmap.getPropertyMapping (propname);
if (nmap != null && nmap != n.getDbMapping()) {
n.setDbMapping (nmap);
n.setPrototype (nmap.getTypeName ());
}
Property prop = (Property) propMap.get (p2);
if (prop != null) {
if (prop.type == IProperty.NODE && n.getID ().equals (prop.nvalueID)) {
// nothing to do, just clean up locks and return
if (state == CLEAN) clearWriteLock ();
if (n.state == CLEAN) n.clearWriteLock ();
return;
}
} else {
prop = new Property (propname, this);
}
prop.setNodeValue (n);
Relation rel = dbmap == null ? null : dbmap.getPropertyRelation (propname);
if (rel == null || rel.direction == Relation.FORWARD || rel.virtual || rel.other == null || !rel.other.isRelational()) {
// the node must be stored as explicit property
propMap.put (p2, prop);
}
String nID = n.getID();
2001-01-31 19:17:05 +00:00
// check node in with transactor cache
Transactor tx = (Transactor) Thread.currentThread ();
tx.visitCleanNode (new Key (nmap, nID), n);
// if the field is not the primary key of the property, also register it
if (rel != null && !rel.getKeyID(this, p2).equals (nID))
2001-01-31 19:17:05 +00:00
tx.visitCleanNode (new Key (rel.other, rel.getKeyID(this, p2)), n);
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.SUBNODE_ADDED, n));
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
if (state == CLEAN) markAs (MODIFIED);
if (n.state == DELETED) n.markAs (MODIFIED);
}
/**
* Remove a property. Note that this works only for explicitly set properties, not for those
* specified via property relation.
*/
public void unset (String propname) {
if (propMap == null)
return;
try {
Property p = (Property) propMap.remove (propname.toLowerCase ());
if (p != null) {
checkWriteLock ();
if (p.type == Property.NODE)
p.unregisterNode ();
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
if (state == CLEAN)
markAs (MODIFIED);
}
} catch (Exception ignore) {}
}
protected void registerPropLink (INode n) {
if (proplinks == null)
proplinks = new ExternalizableVector ();
String plid = n.getID ();
if (!proplinks.contains (plid))
proplinks.addElement (n.getID ());
if (state == CLEAN || state == DELETED)
markAs (MODIFIED);
}
protected void unregisterPropLink (INode n) {
if (proplinks != null)
proplinks.removeElement (n.getID ());
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.NODE_REMOVED));
// Server.throwNodeEvent (new NodeEvent (n, NodeEvent.SUBNODE_REMOVED, this));
if (state == CLEAN)
markAs (MODIFIED);
}
public void sanityCheck () {
checkSubnodes ();
checkProperties ();
checkLinks ();
checkPropLinks ();
if (getParent () == null && !"root".equals (name)) {
System.out.println ("*** parent: "+parentID+": "+this.getName ());
}
}
private void checkLinks () {
Vector v = links == null ? null : (Vector) links.clone ();
int l = v == null ? 0 : v.size ();
for (int i = 0; i < l; i++) {
String k = (String) v.elementAt (i);
Node link = nmgr.getNode (k, null);
if (link == null) {
links.removeElement (k);
System.out.println ("**** link "+k+": "+this.getFullName ());
markAs (MODIFIED);
}
}
}
private void checkPropLinks () {
Vector v = proplinks == null ? null : (Vector) proplinks.clone ();
int l = v == null ? 0 : v.size ();
for (int i = 0; i < l; i++) {
String k = (String) v.elementAt (i);
Node link = nmgr.getNode (k, null);
if (link == null) {
proplinks.removeElement (k);
System.out.println ("**** proplink "+k+": "+this.getFullName ());
markAs (MODIFIED);
}
}
}
private void checkSubnodes () {
Vector v = subnodes == null ? null : (Vector) subnodes.clone ();
int l = v == null ? 0 : v.size ();
for (int i = 0; i < l; i++) {
String k = (String) v.elementAt (i);
Node link = nmgr.getNode (k, null);
if (link == null) {
subnodes.removeElement (k);
System.out.println ("**** subnode "+k+": "+this.getFullName ());
markAs (MODIFIED);
}
}
}
private void checkProperties () {
Hashtable ht = propMap == null ? new Hashtable () : (Hashtable) propMap.clone ();
for (Enumeration ps = ht.elements (); ps.hasMoreElements (); ) {
Property p = (Property) ps.nextElement ();
if (p.getType () == IProperty.NODE && p.getNodeValue () == null) {
System.out.println ("**** property "+p.propname+"->"+p.nvalueID+": "+this.getFullName ());
// INode par = getParent ();
// if (par != null)
// par.removeNode (this);
// markAs (DELETED);
}
}
}
/**
* content-related
*/
public boolean isText () throws IOException {
return getContentType().indexOf ("text/") == 0;
}
public boolean isBinary () throws IOException {
return getContentType().indexOf ("text/") != 0;
}
public String getContentType () {
if (contentType == null)
return "text/plain";
return contentType;
}
public void setContentType (String type) {
checkWriteLock ();
contentType = type;
lastmodified = System.currentTimeMillis ();
if (state == CLEAN) markAs (MODIFIED);
}
public int getContentLength () {
if (content == null)
return 0;
return content.length;
}
public void setContent (byte cnt[], String type) {
checkWriteLock ();
if (type != null)
contentType = type;
content = cnt;
lastmodified = System.currentTimeMillis ();
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.CONTENT_CHANGED));
if (state == CLEAN) markAs (MODIFIED);
}
public void setContent (String cstr) {
checkWriteLock ();
content = cstr.getBytes ();
lastmodified = System.currentTimeMillis ();
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.CONTENT_CHANGED));
if (state == CLEAN) markAs (MODIFIED);
}
public byte[] getContent () {
if (content == null || content.length == 0)
return "".getBytes ();
byte retval[] = new byte[content.length];
System.arraycopy (content, 0, retval, 0, content.length);
// IServer.getLogger().log ("copied "+retval.length+ " bytes");
return retval;
}
public String getText () {
if (content != null) {
if (getContentType ().startsWith ("text/")) {
return new String (content);
} else {
return null;
}
}
return null;
}
public String getHref (INode root, INode userroot, String tmpname, String prefix) {
return prefix + getUrl (root, userroot, tmpname);
}
/**
* Get the path to eiter the general data-root or the user root, depending on
* where this node is located.
*/
public String getUrl (INode root, INode userroot, String tmpname) {
// String fullname = "";
String divider = "/";
StringBuffer b = new StringBuffer ();
INode p = this;
int loopWatch = 0;
while (p != null && p.getParent () != null && p != root && p != userroot) {
b.insert (0, divider);
b.insert (0, UrlEncoder.encode (p.getNameOrID ()));
if (p.getParent () == userroot)
b.insert (0, "users"+divider);
p = p.getParent ();
if (loopWatch++ > 10)
break;
}
return b.toString()+UrlEncoder.encode (tmpname);
}
public long lastModified () {
return lastmodified;
}
public long created () {
return created;
}
public String toString () {
return "Node " + name;
}
/**
* Recursively convert other implementations of INode into helma.objectmodel.db.Node.
*/
protected Node convert (INode n) {
Hashtable ntable = new Hashtable ();
Node converted = new Node (n, ntable, false, nmgr);
return converted;
}
/**
* Recursively turn node status from TRANSIENT to NEW so that the Transactor will
* know it has to insert this node.
*/
protected void makePersistentCapable () {
for (Enumeration e = getSubnodes (); e.hasMoreElements (); ) {
Node n = (Node) e.nextElement ();
if (n.state == TRANSIENT)
n.makePersistentCapable ();
}
for (Enumeration e = properties (); e.hasMoreElements (); ) {
IProperty next = get ((String) e.nextElement (), false);
if (next != null && next.getType () == IProperty.NODE) {
Node n = (Node) next.getNodeValue ();
if (n != null && n.state == TRANSIENT)
n.makePersistentCapable ();
}
}
state = NEW;
}
/**
* 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 helma.objectmodel.Node();
return cacheNode;
}
2001-01-02 15:28:56 +00:00
// walk down node path to the first non-virtual node and return its id.
// limit max depth to 3, since there shouldn't be more then 2 layers of virtual nodes.
public String getNonVirtualHomeID () {
INode node = this;
for (int i=0; i<3; i++) {
if (node == null) break;
if (node.getState() != Node.VIRTUAL)
return node.getID ();
node = node.getParent ();
}
return null;
}
public void dumpSubnodes () {
System.err.println (subnodes);
}
}