/*
* 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: root $
* $Revision: 8604 $
* $Date: 2007-09-28 15:16:38 +0200 (Fre, 28 Sep 2007) $
*/
package helma.objectmodel;
import helma.framework.IPathElement;
import helma.objectmodel.db.DbMapping;
import helma.objectmodel.db.Relation;
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 helma.objectmodel.db.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) {
propname = propname.toLowerCase();
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) {
propname = propname.toLowerCase();
Property prop = getProperty(propname);
try {
return prop.getStringValue();
} catch (Exception ignore) {
}
return null;
}
/**
*
*
* @param propname ...
*
* @return ...
*/
public long getInteger(String propname) {
propname = propname.toLowerCase();
Property prop = getProperty(propname);
try {
return prop.getIntegerValue();
} catch (Exception ignore) {
}
return 0;
}
/**
*
*
* @param propname ...
*
* @return ...
*/
public double getFloat(String propname) {
propname = propname.toLowerCase();
Property prop = getProperty(propname);
try {
return prop.getFloatValue();
} catch (Exception ignore) {
}
return 0.0;
}
/**
*
*
* @param propname ...
*
* @return ...
*/
public Date getDate(String propname) {
propname = propname.toLowerCase();
Property prop = getProperty(propname);
try {
return prop.getDateValue();
} catch (Exception ignore) {
}
return null;
}
/**
*
*
* @param propname ...
*
* @return ...
*/
public boolean getBoolean(String propname) {
propname = propname.toLowerCase();
Property prop = getProperty(propname);
try {
return prop.getBooleanValue();
} catch (Exception ignore) {
}
return false;
}
/**
*
*
* @param propname ...
*
* @return ...
*/
public INode getNode(String propname) {
propname = propname.toLowerCase();
Property prop = getProperty(propname);
try {
return prop.getNodeValue();
} catch (Exception ignore) {
}
return null;
}
/**
*
*
* @param propname ...
*
* @return ...
*/
public Object getJavaObject(String propname) {
propname = propname.toLowerCase();
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();
String p2 = propname.toLowerCase();
Property prop = (Property) propMap.get(p2);
if (prop == null) {
prop = new Property(propname, this);
propMap.put(p2, 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) {
return;
}
try {
propMap.remove(propname.toLowerCase());
lastmodified = System.currentTimeMillis();
} catch (Exception ignore) {
}
}
/* public String getUrl (INode root, INode users, String tmpname, String rootproto) {
throw new RuntimeException ("HREFs on transient (non-db based) Nodes not supported");
} */
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;
}
}
|