0001: /*
0002: * Helma License Notice
0003: *
0004: * The contents of this file are subject to the Helma License
0005: * Version 2.0 (the "License"). You may not use this file except in
0006: * compliance with the License. A copy of the License is available at
0007: * http://adele.helma.org/download/helma/license.txt
0008: *
0009: * Copyright 1998-2003 Helma Software. All Rights Reserved.
0010: *
0011: * $RCSfile$
0012: * $Author: hannes $
0013: * $Revision: 8674 $
0014: * $Date: 2007-11-28 16:32:09 +0100 (Mit, 28 Nov 2007) $
0015: */
0016:
0017: package helma.objectmodel.db;
0018:
0019: import helma.framework.IPathElement;
0020: import helma.framework.core.RequestEvaluator;
0021: import helma.framework.core.Application;
0022: import helma.objectmodel.ConcurrencyException;
0023: import helma.objectmodel.INode;
0024: import helma.objectmodel.IProperty;
0025: import helma.objectmodel.TransientNode;
0026: import helma.util.EmptyEnumeration;
0027:
0028: import java.io.IOException;
0029: import java.io.ObjectInputStream;
0030: import java.io.ObjectOutputStream;
0031: import java.io.Serializable;
0032: import java.util.*;
0033:
0034: /**
0035: * An implementation of INode that can be stored in the internal database or
0036: * an external relational database.
0037: */
0038: public final class Node implements INode, Serializable {
0039: static final long serialVersionUID = -3740339688506633675L;
0040:
0041: // The handle to the node's parent
0042: protected NodeHandle parentHandle;
0043:
0044: // Ordered list of subnodes of this node
0045: private SubnodeList subnodes;
0046:
0047: // Named subnodes (properties) of this node
0048: private Hashtable propMap;
0049:
0050: protected long created;
0051: protected long lastmodified;
0052: private String id;
0053: private String name;
0054:
0055: // is this node's main identity as a named property or an
0056: // anonymous node in a subnode collection?
0057: protected boolean anonymous = false;
0058:
0059: // the serialization version this object was read from (see readObject())
0060: protected short version = 0;
0061: private transient String prototype;
0062: private transient NodeHandle handle;
0063: private transient INode cacheNode;
0064: transient WrappedNodeManager nmgr;
0065: transient DbMapping dbmap;
0066: transient Key primaryKey = null;
0067: transient String subnodeRelation = null;
0068: transient long lastSubnodeFetch = 0;
0069: transient long lastSubnodeChange = 0;
0070: transient long lastNameCheck = 0;
0071: transient long lastParentSet = 0;
0072: transient long lastSubnodeCount = 0; // these two are only used
0073: transient int subnodeCount = -1; // for aggressive loading relational subnodes
0074: transient private volatile Transactor lock;
0075: transient private volatile int state;
0076:
0077: /**
0078: * Creates an empty, uninitialized Node. The init() method must be called on the
0079: * Node before it can do anything useful.
0080: */
0081: protected Node() {
0082: created = lastmodified = System.currentTimeMillis();
0083: }
0084:
0085: /**
0086: * Creates an empty, uninitialized Node with the given create and modify time.
0087: * This is used for null-node references in the node cache.
0088: * @param timestamp
0089: */
0090: protected Node(long timestamp) {
0091: created = lastmodified = timestamp;
0092: }
0093:
0094: /**
0095: * Creates a new Node with the given name. Used by NodeManager for creating "root nodes"
0096: * outside of a Transaction context, which is why we can immediately mark it as CLEAN.
0097: * Also used by embedded database to re-create an existing Node.
0098: */
0099: public Node(String name, String id, String prototype,
0100: WrappedNodeManager nmgr) {
0101: if (prototype == null) {
0102: prototype = "HopObject";
0103: }
0104: init(nmgr.getDbMapping(prototype), id, name, prototype, null,
0105: nmgr);
0106: }
0107:
0108: /**
0109: * Constructor used to create a Node with a given name from a embedded database.
0110: */
0111: public Node(String name, String id, String prototype,
0112: WrappedNodeManager nmgr, long created, long lastmodified) {
0113: this (name, id, prototype, nmgr);
0114: this .created = created;
0115: this .lastmodified = lastmodified;
0116: }
0117:
0118: /**
0119: * Constructor used for virtual nodes.
0120: */
0121: public Node(Node home, String propname, WrappedNodeManager nmgr,
0122: String prototype) {
0123: this .nmgr = nmgr;
0124: setParent(home);
0125: // generate a key for the virtual node that can't be mistaken for a Database Key
0126: primaryKey = new SyntheticKey(home.getKey(), propname);
0127: this .id = primaryKey.getID();
0128: this .name = propname;
0129: this .prototype = prototype;
0130: this .anonymous = false;
0131:
0132: // set the collection's state according to the home node's state
0133: if (home.state == NEW || home.state == TRANSIENT) {
0134: this .state = TRANSIENT;
0135: } else {
0136: this .state = VIRTUAL;
0137: }
0138: }
0139:
0140: /**
0141: * Creates a new Node with the given name. This is used for ordinary transient nodes.
0142: */
0143: public Node(String name, String prototype, WrappedNodeManager nmgr) {
0144: this .nmgr = nmgr;
0145: this .prototype = prototype;
0146: dbmap = nmgr.getDbMapping(prototype);
0147:
0148: // the id is only generated when the node is actually checked into db,
0149: // or when it's explicitly requested.
0150: id = null;
0151: this .name = (name == null) ? "" : name;
0152: created = lastmodified = System.currentTimeMillis();
0153: state = TRANSIENT;
0154:
0155: if (prototype != null && dbmap != null) {
0156: String protoProperty = dbmap.columnNameToProperty(dbmap
0157: .getPrototypeField());
0158: if (protoProperty != null) {
0159: setString(protoProperty, dbmap.getExtensionId());
0160: }
0161: }
0162: }
0163:
0164: /**
0165: * Initializer used for nodes being instanced from an embedded or relational database.
0166: */
0167: public synchronized void init(DbMapping dbm, String id,
0168: String name, String prototype, Hashtable propMap,
0169: WrappedNodeManager nmgr) {
0170: this .nmgr = nmgr;
0171: this .dbmap = dbm;
0172: this .prototype = prototype;
0173: this .id = id;
0174: this .name = name;
0175: // If name was not set from resultset, create a synthetical name now.
0176: if ((name == null) || (name.length() == 0)) {
0177: this .name = prototype + " " + id;
0178: }
0179:
0180: this .propMap = propMap;
0181:
0182: // set lastmodified and created timestamps and mark as clean
0183: created = lastmodified = System.currentTimeMillis();
0184:
0185: if (state != CLEAN) {
0186: markAs(CLEAN);
0187: }
0188: }
0189:
0190: /**
0191: * Read this object instance from a stream. This does some smart conversion to
0192: * update from previous serialization formats.
0193: */
0194: private void readObject(ObjectInputStream in) throws IOException {
0195: try {
0196: // as a general rule of thumb, if a string can be null use read/writeObject,
0197: // if not it's save to use read/writeUTF.
0198: // version indicates the serialization version
0199: version = in.readShort();
0200:
0201: if (version < 9) {
0202: throw new IOException("Can't read pre 1.3.0 HopObject");
0203: }
0204:
0205: id = (String) in.readObject();
0206: name = (String) in.readObject();
0207: state = in.readInt();
0208: parentHandle = (NodeHandle) in.readObject();
0209: created = in.readLong();
0210: lastmodified = in.readLong();
0211:
0212: subnodes = (SubnodeList) in.readObject();
0213: // left-over from links vector
0214: in.readObject();
0215: propMap = (Hashtable) in.readObject();
0216: anonymous = in.readBoolean();
0217: prototype = (String) in.readObject();
0218:
0219: } catch (ClassNotFoundException x) {
0220: throw new IOException(x.toString());
0221: }
0222: }
0223:
0224: /**
0225: * Write out this instance to a stream
0226: */
0227: private void writeObject(ObjectOutputStream out) throws IOException {
0228: out.writeShort(9); // serialization version
0229: out.writeObject(id);
0230: out.writeObject(name);
0231: out.writeInt(state);
0232: out.writeObject(parentHandle);
0233: out.writeLong(created);
0234: out.writeLong(lastmodified);
0235:
0236: DbMapping smap = (dbmap == null) ? null : dbmap
0237: .getSubnodeMapping();
0238:
0239: if ((smap != null) && smap.isRelational()) {
0240: out.writeObject(null);
0241: } else {
0242: out.writeObject(subnodes);
0243: }
0244:
0245: // left-over from links vector
0246: out.writeObject(null);
0247: out.writeObject(propMap);
0248: out.writeBoolean(anonymous);
0249: out.writeObject(prototype);
0250: }
0251:
0252: /**
0253: * used by Xml deserialization
0254: */
0255: public synchronized void setPropMap(Hashtable propMap) {
0256: this .propMap = propMap;
0257: }
0258:
0259: /**
0260: * used by Xml deserialization
0261: */
0262: public synchronized void setSubnodes(SubnodeList subnodes) {
0263: this .subnodes = subnodes;
0264: }
0265:
0266: /**
0267: * Get the write lock on this node, throwing a ConcurrencyException if the
0268: * lock is already held by another thread.
0269: */
0270: synchronized void checkWriteLock() {
0271: if (state == TRANSIENT) {
0272: return; // no need to lock transient node
0273: }
0274:
0275: Transactor current = (Transactor) Thread.currentThread();
0276:
0277: if (!current.isActive()) {
0278: throw new helma.framework.TimeoutException();
0279: }
0280:
0281: if (state == INVALID) {
0282: nmgr.logEvent("Got Invalid Node: " + this );
0283: Thread.dumpStack();
0284: throw new ConcurrencyException("Node " + this
0285: + " was invalidated by another thread.");
0286: }
0287:
0288: if ((lock != null) && (lock != current) && lock.isAlive()
0289: && lock.isActive()) {
0290: // nmgr.logEvent("Concurrency conflict for " + this + ", lock held by " + lock);
0291: throw new ConcurrencyException("Tried to modify " + this
0292: + " from two threads at the same time.");
0293: }
0294:
0295: current.visitDirtyNode(this );
0296: lock = current;
0297: }
0298:
0299: /**
0300: * Clear the write lock on this node.
0301: */
0302: synchronized void clearWriteLock() {
0303: lock = null;
0304: }
0305:
0306: /**
0307: * Set this node's state, registering it with the transactor if necessary.
0308: */
0309: void markAs(int s) {
0310: if (s == state || state == INVALID || state == VIRTUAL
0311: || state == TRANSIENT) {
0312: return;
0313: }
0314:
0315: state = s;
0316:
0317: if (Thread.currentThread() instanceof Transactor) {
0318: Transactor tx = (Transactor) Thread.currentThread();
0319:
0320: if (s == CLEAN) {
0321: clearWriteLock();
0322: tx.dropDirtyNode(this );
0323: } else {
0324: tx.visitDirtyNode(this );
0325:
0326: if (s == NEW) {
0327: clearWriteLock();
0328: tx.visitCleanNode(this );
0329: }
0330: }
0331: }
0332: }
0333:
0334: /**
0335: * Register this node as parent node with the transactor so that
0336: * setLastSubnodeChange is called when the transaction completes.
0337: */
0338: void registerSubnodeChange() {
0339: // we do not fetch subnodes for nodes that haven't been persisted yet or are in
0340: // the process of being persistified - except if "manual" subnoderelation is set.
0341: if ((state == TRANSIENT || state == NEW)
0342: && subnodeRelation == null) {
0343: return;
0344: } else if (Thread.currentThread() instanceof Transactor) {
0345: Transactor tx = (Transactor) Thread.currentThread();
0346: tx.visitParentNode(this );
0347: }
0348: }
0349:
0350: /**
0351: * Notify the node's parent that its child collection needs to be reloaded
0352: * in case the changed property has an affect on collection order or content.
0353: *
0354: * @param propname the name of the property being changed
0355: */
0356: void notifyPropertyChange(String propname) {
0357: Node parent = (parentHandle == null) ? null
0358: : (Node) getParent();
0359:
0360: if ((parent != null) && (parent.getDbMapping() != null)) {
0361: // check if this node is already registered with the old name; if so, remove it.
0362: // then set parent's property to this node for the new name value
0363: DbMapping parentmap = parent.getDbMapping();
0364: Relation subrel = parentmap.getSubnodeRelation();
0365: String dbcolumn = dbmap.propertyToColumnName(propname);
0366: if (subrel == null || dbcolumn == null)
0367: return;
0368:
0369: if (subrel.order != null
0370: && subrel.order.indexOf(dbcolumn) > -1) {
0371: parent.registerSubnodeChange();
0372: }
0373: }
0374: }
0375:
0376: /**
0377: * Called by the transactor on registered parent nodes to mark the
0378: * child index as changed
0379: */
0380: public void markSubnodesChanged() {
0381: lastSubnodeChange += 1;
0382: }
0383:
0384: /**
0385: * Gets this node's stateas defined in the INode interface
0386: *
0387: * @return this node's state
0388: */
0389: public int getState() {
0390: return state;
0391: }
0392:
0393: /**
0394: * Sets this node's state as defined in the INode interface
0395: *
0396: * @param s this node's new state
0397: */
0398: public void setState(int s) {
0399: state = s;
0400: }
0401:
0402: /**
0403: * Mark node as invalid so it is re-fetched from the database
0404: */
0405: public void invalidate() {
0406: // This doesn't make sense for transient nodes
0407: if ((state == TRANSIENT) || (state == NEW)) {
0408: return;
0409: }
0410:
0411: checkWriteLock();
0412: nmgr.evictNode(this );
0413: }
0414:
0415: /**
0416: * Check for a child mapping and evict the object specified by key from the cache
0417: */
0418: public void invalidateNode(String key) {
0419: // This doesn't make sense for transient nodes
0420: if ((state == TRANSIENT) || (state == NEW)) {
0421: return;
0422: }
0423:
0424: Relation rel = getDbMapping().getSubnodeRelation();
0425:
0426: if (rel != null) {
0427: if (rel.usesPrimaryKey()) {
0428: nmgr.evictNodeByKey(new DbKey(getDbMapping()
0429: .getSubnodeMapping(), key));
0430: } else {
0431: nmgr.evictNodeByKey(new SyntheticKey(getKey(), key));
0432: }
0433: }
0434: }
0435:
0436: /**
0437: * Get the ID of this Node. This is the primary database key and used as part of the
0438: * key for the internal node cache.
0439: */
0440: public String getID() {
0441: // if we are transient, we generate an id on demand. It's possible that we'll never need
0442: // it, but if we do it's important to keep the one we have.
0443: if ((state == TRANSIENT) && (id == null)) {
0444: id = TransientNode.generateID();
0445: }
0446: return id;
0447: }
0448:
0449: /**
0450: * Returns true if this node is accessed by id from its aprent, false if it
0451: * is accessed by name
0452: */
0453: public boolean isAnonymous() {
0454: return anonymous;
0455: }
0456:
0457: /**
0458: * Return this node' name, which may or may not have some meaning
0459: */
0460: public String getName() {
0461: return name;
0462: }
0463:
0464: /**
0465: * Get something to identify this node within a URL. This is the ID for anonymous nodes
0466: * and a property value for named properties.
0467: */
0468: public String getElementName() {
0469: // check element name - this is either the Node's id or name.
0470: long lastmod = lastmodified;
0471:
0472: if (dbmap != null) {
0473: lastmod = Math.max(lastmod, dbmap.getLastTypeChange());
0474: }
0475:
0476: if ((parentHandle != null) && (lastNameCheck <= lastmod)) {
0477: try {
0478: Node p = parentHandle.getNode(nmgr);
0479: DbMapping parentmap = p.getDbMapping();
0480: Relation prel = parentmap.getSubnodeRelation();
0481:
0482: if (prel != null) {
0483: if (prel.groupby != null) {
0484: setName(getString("groupname"));
0485: anonymous = false;
0486: } else if (prel.accessName != null) {
0487: String propname = dbmap
0488: .columnNameToProperty(prel.accessName);
0489: String propvalue = getString(propname);
0490:
0491: if ((propvalue != null)
0492: && (propvalue.length() > 0)) {
0493: setName(propvalue);
0494: anonymous = false;
0495: } else if (!anonymous && p.isParentOf(this )) {
0496: anonymous = true;
0497: }
0498: } else if (!anonymous && p.isParentOf(this )) {
0499: anonymous = true;
0500: }
0501: } else if (!anonymous && p.isParentOf(this )) {
0502: anonymous = true;
0503: }
0504: } catch (Exception ignore) {
0505: // FIXME: add proper NullPointer checks in try statement
0506: // just fall back to default method
0507: }
0508:
0509: lastNameCheck = System.currentTimeMillis();
0510: }
0511:
0512: return (anonymous || (name == null) || (name.length() == 0)) ? id
0513: : name;
0514: }
0515:
0516: /**
0517: *
0518: *
0519: * @return ...
0520: */
0521: public String getFullName() {
0522: return getFullName(null);
0523: }
0524:
0525: /**
0526: *
0527: *
0528: * @param root ...
0529: *
0530: * @return ...
0531: */
0532: public String getFullName(INode root) {
0533: String divider = null;
0534: StringBuffer b = new StringBuffer();
0535: INode p = this ;
0536: int loopWatch = 0;
0537:
0538: while ((p != null) && (p.getParent() != null) && (p != root)) {
0539: if (divider != null) {
0540: b.insert(0, divider);
0541: } else {
0542: divider = "/";
0543: }
0544:
0545: b.insert(0, p.getElementName());
0546: p = p.getParent();
0547:
0548: loopWatch++;
0549:
0550: if (loopWatch > 10) {
0551: b.insert(0, "...");
0552:
0553: break;
0554: }
0555: }
0556:
0557: return b.toString();
0558: }
0559:
0560: /**
0561: *
0562: *
0563: * @return ...
0564: */
0565: public String getPrototype() {
0566: // if prototype is null, it's a vanilla HopObject.
0567: if (prototype == null) {
0568: return "HopObject";
0569: }
0570:
0571: return prototype;
0572: }
0573:
0574: /**
0575: *
0576: *
0577: * @param proto ...
0578: */
0579: public void setPrototype(String proto) {
0580: this .prototype = proto;
0581: // Note: we mustn't set the DbMapping according to the prototype,
0582: // because some nodes have custom dbmappings, e.g. the groupby
0583: // dbmappings created in DbMapping.initGroupbyMapping().
0584: }
0585:
0586: /**
0587: *
0588: *
0589: * @param dbmap ...
0590: */
0591: public void setDbMapping(DbMapping dbmap) {
0592: this .dbmap = dbmap;
0593: }
0594:
0595: /**
0596: *
0597: *
0598: * @return ...
0599: */
0600: public DbMapping getDbMapping() {
0601: return dbmap;
0602: }
0603:
0604: /**
0605: *
0606: *
0607: * @param nmgr
0608: */
0609: public void setWrappedNodeManager(WrappedNodeManager nmgr) {
0610: this .nmgr = nmgr;
0611: }
0612:
0613: /**
0614: *
0615: *
0616: * @return ...
0617: */
0618: public Key getKey() {
0619: if (primaryKey == null && state == TRANSIENT) {
0620: throw new RuntimeException(
0621: "getKey called on transient Node: " + this );
0622: }
0623:
0624: if ((dbmap == null) && (prototype != null) && (nmgr != null)) {
0625: dbmap = nmgr.getDbMapping(prototype);
0626: }
0627:
0628: if (primaryKey == null) {
0629: primaryKey = new DbKey(dbmap, id);
0630: }
0631:
0632: return primaryKey;
0633: }
0634:
0635: /**
0636: *
0637: *
0638: * @return ...
0639: */
0640: public NodeHandle getHandle() {
0641: if (handle == null) {
0642: handle = new NodeHandle(this );
0643: }
0644:
0645: return handle;
0646: }
0647:
0648: /**
0649: *
0650: *
0651: * @param rel ...
0652: */
0653: public synchronized void setSubnodeRelation(String rel) {
0654: if (((rel == null) && (this .subnodeRelation == null))
0655: || ((rel != null) && rel
0656: .equalsIgnoreCase(this .subnodeRelation))) {
0657: return;
0658: }
0659:
0660: checkWriteLock();
0661: this .subnodeRelation = rel;
0662:
0663: DbMapping smap = (dbmap == null) ? null : dbmap
0664: .getSubnodeMapping();
0665:
0666: if ((smap != null) && smap.isRelational()) {
0667: subnodes = null;
0668: subnodeCount = -1;
0669: }
0670: }
0671:
0672: /**
0673: *
0674: *
0675: * @return ...
0676: */
0677: public synchronized String getSubnodeRelation() {
0678: return subnodeRelation;
0679: }
0680:
0681: /**
0682: *
0683: *
0684: * @param name ...
0685: */
0686: public void setName(String name) {
0687: if ((name == null) || (name.trim().length() == 0)) {
0688: // use id as name
0689: this .name = id;
0690: } else if (name.indexOf('/') > -1) {
0691: // "/" is used as delimiter, so it's not a legal char
0692: return;
0693: } else {
0694: this .name = name;
0695: }
0696: }
0697:
0698: /**
0699: * Set this node's parent node.
0700: */
0701: public void setParent(Node parent) {
0702: parentHandle = (parent == null) ? null : parent.getHandle();
0703: }
0704:
0705: /**
0706: * Set this node's parent node to the node referred to by the NodeHandle.
0707: */
0708: public void setParentHandle(NodeHandle parent) {
0709: parentHandle = parent;
0710: }
0711:
0712: /**
0713: * Get parent, retrieving it if necessary.
0714: */
0715: public INode getParent() {
0716: // check what's specified in the type.properties for this node.
0717: ParentInfo[] parentInfo = null;
0718:
0719: if (isRelational()
0720: && lastParentSet <= Math.max(dbmap.getLastTypeChange(),
0721: lastmodified)) {
0722: parentInfo = dbmap.getParentInfo();
0723: }
0724:
0725: // check if current parent candidate matches presciption,
0726: // if not, try to get one that does.
0727: if (parentInfo != null) {
0728:
0729: for (int i = 0; i < parentInfo.length; i++) {
0730:
0731: ParentInfo pinfo = parentInfo[i];
0732: Node pn = null;
0733:
0734: // see if there is an explicit relation defined for this parent info
0735: // we only try to fetch a node if an explicit relation is specified for the prop name
0736: Relation rel = dbmap.propertyToRelation(pinfo.propname);
0737: if ((rel != null)
0738: && (rel.isReference() || rel
0739: .isComplexReference())) {
0740: pn = (Node) getNode(pinfo.propname);
0741: }
0742:
0743: // the parent of this node is the app's root node...
0744: if ((pn == null) && pinfo.isroot) {
0745: pn = nmgr.getRootNode();
0746: }
0747:
0748: // if we found a parent node, check if we ought to use a virtual or groupby node as parent
0749: if (pn != null) {
0750: // see if dbmapping specifies anonymity for this node
0751: if (pinfo.virtualname != null) {
0752: Node pn2 = (Node) pn.getNode(pinfo.virtualname);
0753: if (pn2 == null) {
0754: getApp().logError(
0755: "Error: Can't retrieve parent node "
0756: + pinfo + " for " + this );
0757: } else if (pinfo.collectionname != null) {
0758: pn2 = (Node) pn2
0759: .getNode(pinfo.collectionname);
0760: } else if (pn2.equals(this )) {
0761: // a special case we want to support: virtualname is actually
0762: // a reference to this node, not a collection containing this node.
0763: parentHandle = pn.getHandle();
0764: name = pinfo.virtualname;
0765: anonymous = false;
0766: return pn;
0767: }
0768:
0769: pn = pn2;
0770: }
0771:
0772: DbMapping dbm = (pn == null) ? null : pn
0773: .getDbMapping();
0774:
0775: try {
0776: if ((dbm != null)
0777: && (dbm.getSubnodeGroupby() != null)) {
0778: // check for groupby
0779: rel = dbmap.columnNameToRelation(dbm
0780: .getSubnodeGroupby());
0781: pn = (Node) pn
0782: .getChildElement(getString(rel.propName));
0783: }
0784:
0785: if (pn != null) {
0786: parentHandle = pn.getHandle();
0787: lastParentSet = System.currentTimeMillis();
0788:
0789: return pn;
0790: }
0791: } catch (Exception x) {
0792: getApp().logError(
0793: "Error retrieving parent node " + pinfo
0794: + " for " + this , x);
0795: }
0796: }
0797: if (i == parentInfo.length - 1) {
0798: // if we came till here and we didn't find a parent.
0799: // set parent to null.
0800: parentHandle = null;
0801: lastParentSet = System.currentTimeMillis();
0802: }
0803: }
0804: if (parentHandle == null && !nmgr.isRootNode(this )
0805: && state != TRANSIENT) {
0806: getApp().logEvent(
0807: "*** Couldn't resolve parent for " + this );
0808: getApp()
0809: .logEvent(
0810: "*** Please check _parent info in type.properties!");
0811: }
0812: }
0813:
0814: if (parentHandle == null) {
0815: return null;
0816: }
0817: return parentHandle.getNode(nmgr);
0818: }
0819:
0820: /**
0821: * Get parent, using cached info if it exists.
0822: */
0823: public Node getCachedParent() {
0824: if (parentHandle == null) {
0825: return null;
0826: }
0827:
0828: return parentHandle.getNode(nmgr);
0829: }
0830:
0831: /**
0832: * INode-related
0833: */
0834: public INode addNode(INode elem) {
0835: return addNode(elem, -1);
0836: }
0837:
0838: /**
0839: * Add a node to this Node's subnodes, making the added node persistent if it
0840: * hasn't been before and this Node is already persistent.
0841: *
0842: * @param elem the node to add to this Nodes subnode-list
0843: * @param where the index-position where this node has to be added
0844: *
0845: * @return the added node itselve
0846: */
0847: public INode addNode(INode elem, int where) {
0848: Node node = null;
0849:
0850: if (elem instanceof Node) {
0851: node = (Node) elem;
0852: } else {
0853: throw new RuntimeException(
0854: "Can't add fixed-transient node to a persistent node");
0855: }
0856:
0857: // only lock nodes if parent node is not transient
0858: if (state != TRANSIENT) {
0859: // only lock parent if it has to be modified for a change in subnodes
0860: if (!ignoreSubnodeChange()) {
0861: checkWriteLock();
0862: }
0863:
0864: node.checkWriteLock();
0865: }
0866:
0867: // if subnodes are defined via relation, make sure its constraints are enforced.
0868: if ((dbmap != null) && (dbmap.getSubnodeRelation() != null)) {
0869: dbmap.getSubnodeRelation().setConstraints(this , node);
0870: }
0871:
0872: // if the new node is marked as TRANSIENT and this node is not, mark new node as NEW
0873: if ((state != TRANSIENT) && (node.state == TRANSIENT)) {
0874: node.makePersistable();
0875: }
0876:
0877: // only mark this node as modified if subnodes are not in relational db
0878: // pointing to this node.
0879: if (!ignoreSubnodeChange()
0880: && ((state == CLEAN) || (state == DELETED))) {
0881: markAs(MODIFIED);
0882: }
0883:
0884: // TODO this is a rather minimal fix for bug http://helma.org/bugs/show_bug.cgi?id=554
0885: // - eventually we want to get rid of this code as a whole.
0886: if (state != TRANSIENT
0887: && (node.state == CLEAN || node.state == DELETED)) {
0888: node.markAs(MODIFIED);
0889: }
0890:
0891: loadNodes();
0892:
0893: // check if this node has a group-by subnode-relation
0894: if (dbmap != null) {
0895: Relation srel = dbmap.getSubnodeRelation();
0896:
0897: if ((srel != null) && (srel.groupby != null)) {
0898: Relation groupbyRel = srel.otherType
0899: .columnNameToRelation(srel.groupby);
0900: String groupbyProp = (groupbyRel != null) ? groupbyRel.propName
0901: : srel.groupby;
0902: String groupbyValue = node.getString(groupbyProp);
0903: INode groupbyNode = (INode) getChildElement(groupbyValue);
0904:
0905: // if group-by node doesn't exist, we'll create it
0906: if (groupbyNode == null) {
0907: groupbyNode = getGroupbySubnode(groupbyValue, true);
0908: } else {
0909: groupbyNode.setDbMapping(dbmap.getGroupbyMapping());
0910: }
0911:
0912: groupbyNode.addNode(node);
0913: return node;
0914: }
0915: }
0916:
0917: NodeHandle nhandle = node.getHandle();
0918:
0919: if ((subnodes != null) && subnodes.contains(nhandle)) {
0920: // Node is already subnode of this - just move to new position
0921: synchronized (subnodes) {
0922: subnodes.remove(nhandle);
0923: // check if index is out of bounds when adding
0924: if (where < 0 || where > subnodes.size()) {
0925: subnodes.add(nhandle);
0926: } else {
0927: subnodes.add(where, nhandle);
0928: }
0929: }
0930: } else {
0931: // create subnode list if necessary
0932: if (subnodes == null) {
0933: subnodes = createSubnodeList();
0934: }
0935:
0936: // check if subnode accessname is set. If so, check if another node
0937: // uses the same access name, throwing an exception if so.
0938: if (dbmap != null && node.dbmap != null) {
0939: Relation prel = dbmap.getSubnodeRelation();
0940:
0941: if (prel != null && prel.accessName != null) {
0942: Relation localrel = node.dbmap
0943: .columnNameToRelation(prel.accessName);
0944:
0945: // if no relation from db column to prop name is found,
0946: // assume that both are equal
0947: String propname = (localrel == null) ? prel.accessName
0948: : localrel.propName;
0949: String prop = node.getString(propname);
0950:
0951: if (prop != null && prop.length() > 0) {
0952: INode old = (INode) getChildElement(prop);
0953:
0954: if (old != null && old != node) {
0955: // A node with this name already exists. This is a
0956: // programming error, throw an exception.
0957: throw new RuntimeException(
0958: "An object named \""
0959: + prop
0960: + "\" is already contained in the collection.");
0961: }
0962:
0963: if (state != TRANSIENT) {
0964: Transactor tx = (Transactor) Thread
0965: .currentThread();
0966: SyntheticKey key = new SyntheticKey(this
0967: .getKey(), prop);
0968: tx.visitCleanNode(key, node);
0969: nmgr.registerNode(node, key);
0970: }
0971: }
0972: }
0973: }
0974:
0975: // actually add the new child to the subnode list
0976: synchronized (subnodes) {
0977: // check if index is out of bounds when adding
0978: if (where < 0 || where > subnodes.size()) {
0979: subnodes.add(nhandle);
0980: } else {
0981: subnodes.add(where, nhandle);
0982: }
0983: }
0984:
0985: if (node != this && !nmgr.isRootNode(node)) {
0986: // avoid calling getParent() because it would return bogus results
0987: // for the not-anymore transient node
0988: Node nparent = (node.parentHandle == null) ? null
0989: : node.parentHandle.getNode(nmgr);
0990:
0991: // if the node doesn't have a parent yet, or it has one but it's
0992: // transient while we are persistent, make this the nodes new parent.
0993: if ((nparent == null)
0994: || ((state != TRANSIENT) && (nparent.getState() == TRANSIENT))) {
0995: node.setParent(this );
0996: node.anonymous = true;
0997: }
0998: }
0999: }
1000:
1001: lastmodified = System.currentTimeMillis();
1002: // we want the element name to be recomputed on the child node
1003: node.lastNameCheck = 0;
1004: registerSubnodeChange();
1005:
1006: return node;
1007: }
1008:
1009: /**
1010: *
1011: *
1012: * @return ...
1013: */
1014: public INode createNode() {
1015: // create new node at end of subnode array
1016: return createNode(null, -1);
1017: }
1018:
1019: /**
1020: *
1021: *
1022: * @param where ...
1023: *
1024: * @return ...
1025: */
1026: public INode createNode(int where) {
1027: return createNode(null, where);
1028: }
1029:
1030: /**
1031: *
1032: *
1033: * @param nm ...
1034: *
1035: * @return ...
1036: */
1037: public INode createNode(String nm) {
1038: // parameter where is ignored if nm != null so we try to avoid calling numberOfNodes()
1039: return createNode(nm, -1);
1040: }
1041:
1042: /**
1043: *
1044: *
1045: * @param nm ...
1046: * @param where ...
1047: *
1048: * @return ...
1049: */
1050: public INode createNode(String nm, int where) {
1051: // checkWriteLock();
1052:
1053: boolean anon = false;
1054:
1055: if ((nm == null) || "".equals(nm.trim())) {
1056: anon = true;
1057: }
1058:
1059: String proto = null;
1060:
1061: // try to get proper prototype for new node
1062: if (dbmap != null) {
1063: DbMapping childmap = anon ? dbmap.getSubnodeMapping()
1064: : dbmap.getPropertyMapping(nm);
1065: if (childmap != null) {
1066: proto = childmap.getTypeName();
1067: }
1068: }
1069:
1070: Node n = new Node(nm, proto, nmgr);
1071:
1072: if (anon) {
1073: addNode(n, where);
1074: } else {
1075: setNode(nm, n);
1076: }
1077:
1078: return n;
1079: }
1080:
1081: /**
1082: * This implements the getChildElement() method of the IPathElement interface
1083: */
1084: public IPathElement getChildElement(String name) {
1085: if (dbmap != null) {
1086: // if a dbmapping is provided, check what it tells us about
1087: // getting this specific child element
1088: Relation rel = dbmap.getExactPropertyRelation(name);
1089:
1090: if (rel != null && !rel.isPrimitive()) {
1091: return getNode(name);
1092: }
1093:
1094: rel = dbmap.getSubnodeRelation();
1095:
1096: if ((rel != null)
1097: && (rel.groupby != null || rel.accessName != null)) {
1098: if (state != TRANSIENT && rel.otherType != null
1099: && rel.otherType.isRelational()) {
1100: return nmgr.getNode(this , name, rel);
1101: } else {
1102: // Do what we have to do: loop through subnodes and
1103: // check if any one matches
1104: String propname = rel.groupby != null ? "groupname"
1105: : rel.accessName;
1106: INode node = null;
1107: Enumeration e = getSubnodes();
1108: while (e.hasMoreElements()) {
1109: Node n = (Node) e.nextElement();
1110: if (name
1111: .equalsIgnoreCase(n.getString(propname))) {
1112: node = n;
1113: break;
1114: }
1115: }
1116: // set DbMapping for embedded db group nodes
1117: if (node != null && rel.groupby != null) {
1118: node.setDbMapping(dbmap.getGroupbyMapping());
1119: }
1120: return node;
1121: }
1122: }
1123:
1124: return getSubnode(name);
1125: } else {
1126: // no dbmapping - just try child collection first, then named property.
1127: INode child = getSubnode(name);
1128:
1129: if (child == null) {
1130: child = getNode(name);
1131: }
1132:
1133: return child;
1134: }
1135: }
1136:
1137: /**
1138: * This implements the getParentElement() method of the IPathElement interface
1139: */
1140: public IPathElement getParentElement() {
1141: return getParent();
1142: }
1143:
1144: /**
1145: *
1146: *
1147: * @param subid ...
1148: *
1149: * @return ...
1150: */
1151: public INode getSubnode(String subid) {
1152: if (subid == null || subid.length() == 0) {
1153: return null;
1154: }
1155:
1156: Node retval = null;
1157:
1158: if (subid != null) {
1159: loadNodes();
1160:
1161: if ((subnodes == null) || (subnodes.size() == 0)) {
1162: return null;
1163: }
1164:
1165: NodeHandle nhandle = null;
1166: int l = subnodes.size();
1167:
1168: for (int i = 0; i < l; i++)
1169: try {
1170: NodeHandle shandle = (NodeHandle) subnodes.get(i);
1171:
1172: if (subid.equals(shandle.getID())) {
1173: // System.err.println ("FOUND SUBNODE: "+shandle);
1174: nhandle = shandle;
1175:
1176: break;
1177: }
1178: } catch (Exception x) {
1179: break;
1180: }
1181:
1182: if (nhandle != null) {
1183: retval = nhandle.getNode(nmgr);
1184: }
1185:
1186: // This would be an alternative way to do it, without loading the subnodes,
1187: // but it currently isn't supported by NodeManager.
1188: // if (dbmap != null && dbmap.getSubnodeRelation () != null)
1189: // retval = nmgr.getNode (this, subid, dbmap.getSubnodeRelation ());
1190:
1191: if ((retval != null) && (retval.parentHandle == null)
1192: && !nmgr.isRootNode(retval)) {
1193: retval.setParent(this );
1194: retval.anonymous = true;
1195: }
1196: }
1197:
1198: return retval;
1199: }
1200:
1201: /**
1202: *
1203: *
1204: * @param index ...
1205: *
1206: * @return ...
1207: */
1208: public INode getSubnodeAt(int index) {
1209: loadNodes();
1210:
1211: if (subnodes == null) {
1212: return null;
1213: }
1214:
1215: Node retval = null;
1216:
1217: if (subnodes.size() > index) {
1218: // check if there is a group-by relation
1219: retval = ((NodeHandle) subnodes.get(index)).getNode(nmgr);
1220:
1221: if ((retval != null) && (retval.parentHandle == null)
1222: && !nmgr.isRootNode(retval)) {
1223: retval.setParent(this );
1224: retval.anonymous = true;
1225: }
1226: }
1227:
1228: return retval;
1229: }
1230:
1231: /**
1232: *
1233: *
1234: * @param sid ...
1235: * @param create ...
1236: *
1237: * @return ...
1238: */
1239: protected Node getGroupbySubnode(String sid, boolean create) {
1240: if (sid == null) {
1241: throw new IllegalArgumentException(
1242: "Can't create group by null");
1243: }
1244:
1245: if (state == TRANSIENT) {
1246: throw new RuntimeException(
1247: "Can't add grouped child on transient node. "
1248: + "Make parent persistent before adding grouped nodes.");
1249: }
1250:
1251: loadNodes();
1252:
1253: if (subnodes == null) {
1254: subnodes = new SubnodeList(nmgr, dbmap.getSubnodeRelation());
1255: }
1256:
1257: if (create
1258: || subnodes.contains(new NodeHandle(new SyntheticKey(
1259: getKey(), sid)))) {
1260: try {
1261: DbMapping groupbyMapping = dbmap.getGroupbyMapping();
1262: boolean relational = groupbyMapping.getSubnodeMapping()
1263: .isRelational();
1264:
1265: if (relational || create) {
1266: Node node = relational ? new Node(this , sid, nmgr,
1267: null) : new Node(sid, null, nmgr);
1268:
1269: // set "groupname" property to value of groupby field
1270: node.setString("groupname", sid);
1271:
1272: node.setDbMapping(groupbyMapping);
1273:
1274: if (!relational) {
1275: // if we're not transient, make new node persistable
1276: if (state != TRANSIENT) {
1277: node.makePersistable();
1278: node.checkWriteLock();
1279: }
1280: subnodes.add(node.getHandle());
1281: }
1282:
1283: // Set the dbmapping on the group node
1284: node.setPrototype(groupbyMapping.getTypeName());
1285: // If we created the group node, we register it with the
1286: // nodemanager. Otherwise, we just evict whatever was there before
1287: if (create) {
1288: // register group node with transactor
1289: Transactor tx = (Transactor) Thread
1290: .currentThread();
1291: tx.visitCleanNode(node);
1292: nmgr.registerNode(node);
1293: } else {
1294: nmgr.evictKey(node.getKey());
1295: }
1296:
1297: return node;
1298: }
1299: } catch (Exception noluck) {
1300: nmgr.logEvent("Error creating group-by node for " + sid
1301: + ": " + noluck);
1302: noluck.printStackTrace();
1303: }
1304: }
1305:
1306: return null;
1307: }
1308:
1309: /**
1310: *
1311: *
1312: * @return ...
1313: */
1314: public boolean remove() {
1315: INode parent = getParent();
1316: if (parent != null) {
1317: parent.removeNode(this );
1318: }
1319: deepRemoveNode();
1320: return true;
1321: }
1322:
1323: /**
1324: *
1325: *
1326: * @param node ...
1327: */
1328: public void removeNode(INode node) {
1329: Node n = (Node) node;
1330: releaseNode(n);
1331: }
1332:
1333: /**
1334: * "Locally" remove a subnode from the subnodes table.
1335: * The logical stuff necessary for keeping data consistent is done in
1336: * {@link #removeNode(INode)}.
1337: */
1338: protected void releaseNode(Node node) {
1339: INode parent = node.getParent();
1340:
1341: checkWriteLock();
1342: node.checkWriteLock();
1343:
1344: // load subnodes in case they haven't been loaded.
1345: // this is to prevent subsequent access to reload the
1346: // index which would potentially still contain the removed child
1347: loadNodes();
1348:
1349: if (subnodes != null) {
1350: boolean removed = false;
1351: synchronized (subnodes) {
1352: removed = subnodes.remove(node.getHandle());
1353: }
1354: if (removed) {
1355: registerSubnodeChange();
1356: }
1357: }
1358:
1359: // check if subnodes are also accessed as properties. If so, also unset the property
1360: if ((dbmap != null) && (node.dbmap != null)) {
1361: Relation prel = dbmap.getSubnodeRelation();
1362:
1363: if (prel != null) {
1364: if (prel.accessName != null) {
1365: Relation localrel = node.dbmap
1366: .columnNameToRelation(prel.accessName);
1367:
1368: // if no relation from db column to prop name is found, assume that both are equal
1369: String propname = (localrel == null) ? prel.accessName
1370: : localrel.propName;
1371: String prop = node.getString(propname);
1372:
1373: if (prop != null) {
1374: if (getNode(prop) == node) {
1375: unset(prop);
1376: }
1377: // let the node cache know this key's not for this node anymore.
1378: if (state != TRANSIENT) {
1379: nmgr.evictKey(new SyntheticKey(getKey(),
1380: prop));
1381: }
1382: }
1383: }
1384: // TODO: We should unset constraints to actually remove subnodes here,
1385: // but omit it by convention and to keep backwards compatible.
1386: // if (prel.countConstraints() > 1) {
1387: // prel.unsetConstraints(this, node);
1388: // }
1389: }
1390: }
1391:
1392: if (parent == this ) {
1393: // node.markAs(MODIFIED);
1394: node.setParentHandle(null);
1395: }
1396:
1397: // If subnodes are relational no need to mark this node as modified
1398: if (ignoreSubnodeChange()) {
1399: return;
1400: }
1401:
1402: lastmodified = System.currentTimeMillis();
1403:
1404: if (state == CLEAN) {
1405: markAs(MODIFIED);
1406: }
1407: }
1408:
1409: /**
1410: * Delete the node from the db. This mainly tries to notify all nodes referring to this that
1411: * it's going away. For nodes from the embedded db it also does a cascading delete, since
1412: * it can tell which nodes are actual children and which are just linked in.
1413: */
1414: protected void deepRemoveNode() {
1415:
1416: // tell all nodes that are properties of n that they are no longer used as such
1417: if (propMap != null) {
1418: for (Enumeration en = propMap.elements(); en
1419: .hasMoreElements();) {
1420: Property p = (Property) en.nextElement();
1421:
1422: if ((p != null) && (p.getType() == Property.NODE)) {
1423: Node n = (Node) p.getNodeValue();
1424: if (n != null && !n.isRelational()
1425: && n.getParent() == this ) {
1426: n.deepRemoveNode();
1427: }
1428: }
1429: }
1430: }
1431:
1432: // cascading delete of all subnodes. This is never done for relational subnodes, because
1433: // the parent info is not 100% accurate for them.
1434: if (subnodes != null) {
1435: Vector v = new Vector();
1436:
1437: // remove modifies the Vector we are enumerating, so we are extra careful.
1438: for (Enumeration en = getSubnodes(); en.hasMoreElements();) {
1439: v.add(en.nextElement());
1440: }
1441:
1442: int m = v.size();
1443:
1444: for (int i = 0; i < m; i++) {
1445: // getParent() is heuristical/implicit for relational nodes, so we don't base
1446: // a cascading delete on that criterium for relational nodes.
1447: Node n = (Node) v.get(i);
1448:
1449: if (!n.isRelational() && n.getParent() == this ) {
1450: n.deepRemoveNode();
1451: }
1452: }
1453: }
1454:
1455: // mark the node as deleted
1456: setParent(null);
1457: markAs(DELETED);
1458: }
1459:
1460: /**
1461: * Check if the given node is contained in this node's child list.
1462: * If it is contained return its index in the list, otherwise return -1.
1463: *
1464: * @param n a node
1465: *
1466: * @return the node's index position in the child list, or -1
1467: */
1468: public int contains(INode n) {
1469: if (n == null) {
1470: return -1;
1471: }
1472:
1473: loadNodes();
1474:
1475: if (subnodes == null) {
1476: return -1;
1477: }
1478:
1479: // if the node contains relational groupby subnodes, the subnodes vector
1480: // contains the names instead of ids.
1481: if (!(n instanceof Node)) {
1482: return -1;
1483: }
1484:
1485: Node node = (Node) n;
1486:
1487: return subnodes.indexOf(node.getHandle());
1488: }
1489:
1490: /**
1491: * Check if the given node is contained in this node's child list. This
1492: * is similar to <code>contains(INode)</code> but does not load the
1493: * child index for relational nodes.
1494: *
1495: * @param n a node
1496: * @return true if the given node is contained in this node's child list
1497: */
1498: public boolean isParentOf(Node n) {
1499: if (dbmap != null) {
1500: Relation subrel = dbmap.getSubnodeRelation();
1501: // if we're dealing with relational child nodes use
1502: // Relation.checkConstraints to avoid loading the child index.
1503: // Note that we only do that if no filter is set, since
1504: // Relation.checkConstraints() would always return false
1505: // if there was a filter property.
1506: if (subrel != null && subrel.otherType != null
1507: && subrel.otherType.isRelational()
1508: && subrel.filter == null) {
1509: // first check if types are stored in same table
1510: if (!subrel.otherType.isStorageCompatible(n
1511: .getDbMapping())) {
1512: return false;
1513: }
1514: // if they are, check if constraints are met
1515: return subrel.checkConstraints(this , n);
1516: }
1517: }
1518: // just fall back to contains() for non-relational nodes
1519: return contains(n) > -1;
1520: }
1521:
1522: /**
1523: * Count the subnodes of this node. If they're stored in a relational data source, we
1524: * may actually load their IDs in order to do this.
1525: */
1526: public int numberOfNodes() {
1527: // If the subnodes are loaded aggressively, we really just
1528: // do a count statement, otherwise we just return the size of the id index.
1529: // (after loading it, if it's coming from a relational data source).
1530: DbMapping subMap = (dbmap == null) ? null : dbmap
1531: .getSubnodeMapping();
1532:
1533: if ((subMap != null) && subMap.isRelational()) {
1534: // check if subnodes need to be rechecked
1535: Relation subRel = dbmap.getSubnodeRelation();
1536:
1537: // do not fetch subnodes for nodes that haven't been persisted yet or are in
1538: // the process of being persistified - except if "manual" subnoderelation is set.
1539: if (subRel.aggressiveLoading
1540: && subRel.getGroup() == null
1541: && (((state != TRANSIENT) && (state != NEW)) || (subnodeRelation != null))) {
1542: // we don't want to load *all* nodes if we just want to count them
1543: long lastChange = getLastSubnodeChange(subRel);
1544:
1545: if ((lastChange == lastSubnodeFetch)
1546: && (subnodes != null)) {
1547: // we can use the nodes vector to determine number of subnodes
1548: subnodeCount = subnodes.size();
1549: lastSubnodeCount = lastChange;
1550: } else if ((lastChange != lastSubnodeCount)
1551: || (subnodeCount < 0)) {
1552: // count nodes in db without fetching anything
1553: subnodeCount = nmgr.countNodes(this , subRel);
1554: lastSubnodeCount = lastChange;
1555: }
1556: return subnodeCount;
1557: }
1558: }
1559:
1560: loadNodes();
1561:
1562: return (subnodes == null) ? 0 : subnodes.size();
1563: }
1564:
1565: /**
1566: * Make sure the subnode index is loaded for subnodes stored in a relational data source.
1567: * Depending on the subnode.loadmode specified in the type.properties, we'll load just the
1568: * ID index or the actual nodes.
1569: */
1570: public void loadNodes() {
1571: // Don't do this for transient nodes which don't have an explicit subnode relation set
1572: if (((state == TRANSIENT) || (state == NEW))
1573: && (subnodeRelation == null)) {
1574: return;
1575: }
1576:
1577: DbMapping subMap = (dbmap == null) ? null : dbmap
1578: .getSubnodeMapping();
1579:
1580: if ((subMap != null) && subMap.isRelational()) {
1581: // check if subnodes need to be reloaded
1582: Relation subRel = dbmap.getSubnodeRelation();
1583:
1584: synchronized (this ) {
1585: // also reload if the type mapping has changed.
1586: long lastChange = getLastSubnodeChange(subRel);
1587:
1588: if ((lastChange != lastSubnodeFetch && !subRel.autoSorted)
1589: || (subnodes == null)) {
1590: if (subRel.updateCriteria != null) {
1591: // updateSubnodeList is setting the subnodes directly returning an integer
1592: nmgr.updateSubnodeList(this , subRel);
1593: } else if (subRel.aggressiveLoading) {
1594: subnodes = nmgr.getNodes(this , subRel);
1595: } else {
1596: subnodes = nmgr.getNodeIDs(this , subRel);
1597: }
1598:
1599: lastSubnodeFetch = lastChange;
1600: }
1601: }
1602: }
1603: }
1604:
1605: /**
1606: * Retrieve an empty subnodelist. This empty List is an instance of the Class
1607: * used for this Nodes subnode-list
1608: * @return List an empty List of the type used by this Node
1609: */
1610: public SubnodeList createSubnodeList() {
1611: Relation rel = this .dbmap == null ? null : this .dbmap
1612: .getSubnodeRelation();
1613: if (rel != null && rel.updateCriteria != null) {
1614: subnodes = new UpdateableSubnodeList(nmgr, rel);
1615: } else if (rel != null && rel.autoSorted) {
1616: subnodes = new OrderedSubnodeList(nmgr, rel);
1617: } else {
1618: subnodes = new SubnodeList(nmgr, rel);
1619: }
1620: return subnodes;
1621: }
1622:
1623: /**
1624: * Compute a serial number indicating the last change in subnode collection
1625: * @param subRel the subnode relation
1626: * @return a serial number that increases with each subnode change
1627: */
1628: long getLastSubnodeChange(Relation subRel) {
1629: // include dbmap.getLastTypeChange to also reload if the type mapping has changed.
1630: long checkSum = lastSubnodeChange + dbmap.getLastTypeChange();
1631: return subRel.aggressiveCaching ? checkSum : checkSum
1632: + subRel.otherType.getLastDataChange();
1633: }
1634:
1635: /**
1636: *
1637: *
1638: * @param startIndex ...
1639: * @param length ...
1640: *
1641: * @throws Exception ...
1642: */
1643: public void prefetchChildren(int startIndex, int length)
1644: throws Exception {
1645: if (length < 1) {
1646: return;
1647: }
1648:
1649: if (startIndex < 0) {
1650: return;
1651: }
1652:
1653: loadNodes();
1654:
1655: if (subnodes == null) {
1656: return;
1657: }
1658:
1659: if (startIndex >= subnodes.size()) {
1660: return;
1661: }
1662:
1663: int l = Math.min(subnodes.size() - startIndex, length);
1664:
1665: if (l < 1) {
1666: return;
1667: }
1668:
1669: Key[] keys = new Key[l];
1670:
1671: for (int i = 0; i < l; i++) {
1672: keys[i] = ((NodeHandle) subnodes.get(i + startIndex))
1673: .getKey();
1674: }
1675:
1676: prefetchChildren(keys);
1677: }
1678:
1679: public void prefetchChildren(Key[] keys) throws Exception {
1680: nmgr.nmgr.prefetchNodes(this , dbmap.getSubnodeRelation(), keys);
1681: }
1682:
1683: /**
1684: *
1685: *
1686: * @return ...
1687: */
1688: public Enumeration getSubnodes() {
1689: loadNodes();
1690: class Enum implements Enumeration {
1691: int count = 0;
1692:
1693: public boolean hasMoreElements() {
1694: return count < numberOfNodes();
1695: }
1696:
1697: public Object nextElement() {
1698: return getSubnodeAt(count++);
1699: }
1700: }
1701:
1702: return new Enum();
1703: }
1704:
1705: /**
1706: * Return this Node's subnode list
1707: *
1708: * @return the subnode list
1709: */
1710: public SubnodeList getSubnodeList() {
1711: return subnodes;
1712: }
1713:
1714: /**
1715: * Return true if a change in subnodes can be ignored because it is
1716: * stored in the subnodes themselves.
1717: */
1718: private boolean ignoreSubnodeChange() {
1719: Relation rel = (dbmap == null) ? null : dbmap
1720: .getSubnodeRelation();
1721:
1722: return ((rel != null) && (rel.otherType != null) && rel.otherType
1723: .isRelational());
1724: }
1725:
1726: /**
1727: * Get all properties of this node.
1728: */
1729: public Enumeration properties() {
1730: if ((dbmap != null) && dbmap.isRelational()) {
1731: // return the properties defined in type.properties, if there are any
1732: return dbmap.getPropertyEnumeration();
1733: }
1734:
1735: Relation prel = (dbmap == null) ? null : dbmap
1736: .getSubnodeRelation();
1737:
1738: if (state != TRANSIENT && prel != null && prel.hasAccessName()
1739: && prel.otherType != null
1740: && prel.otherType.isRelational()) {
1741: // return names of objects from a relational db table
1742: return nmgr.getPropertyNames(this , prel).elements();
1743: } else if (propMap != null) {
1744: // return the actually explicitly stored properties
1745: return propMap.keys();
1746: }
1747:
1748: // sorry, no properties for this Node
1749: return new EmptyEnumeration();
1750: }
1751:
1752: /**
1753: *
1754: *
1755: * @return ...
1756: */
1757: public Hashtable getPropMap() {
1758: return propMap;
1759: }
1760:
1761: /**
1762: *
1763: *
1764: * @param propname ...
1765: *
1766: * @return ...
1767: */
1768: public IProperty get(String propname) {
1769: return getProperty(propname);
1770: }
1771:
1772: /**
1773: *
1774: *
1775: * @return ...
1776: */
1777: public String getParentInfo() {
1778: return "anonymous:" + anonymous + ",parentHandle"
1779: + parentHandle + ",parent:" + getParent();
1780: }
1781:
1782: /**
1783: *
1784: *
1785: * @param propname ...
1786: *
1787: * @return ...
1788: */
1789: protected Property getProperty(String propname) {
1790: if (propname == null) {
1791: return null;
1792: }
1793:
1794: Relation rel = dbmap == null ? null : dbmap
1795: .getExactPropertyRelation(propname);
1796:
1797: // 1) check if the property is contained in the propMap
1798: Property prop = propMap == null ? null : (Property) propMap
1799: .get(propname.toLowerCase());
1800:
1801: if (prop != null) {
1802: if (rel != null) {
1803: // Is a relational node stored by id but things it's a string or int. Fix it.
1804: if (rel.otherType != null
1805: && prop.getType() != Property.NODE) {
1806: prop.convertToNodeReference(rel);
1807: }
1808: if (rel.isVirtual()) {
1809: // property was found in propMap and is a collection - this is
1810: // a collection holding non-relational objects. set DbMapping and
1811: // NodeManager
1812: Node n = (Node) prop.getNodeValue();
1813: if (n != null) {
1814: // do set DbMapping for embedded db collection nodes
1815: n.setDbMapping(rel.getVirtualMapping());
1816: // also set node manager in case this is a mountpoint node
1817: // that came in through replication
1818: n.nmgr = nmgr;
1819: }
1820: }
1821: }
1822: return prop;
1823: } else if (state == TRANSIENT && rel != null && rel.isVirtual()) {
1824: // When we get a collection from a transient node for the first time, or when
1825: // we get a collection whose content objects are stored in the embedded
1826: // XML data storage, we just want to create and set a generic node without
1827: // consulting the NodeManager about it.
1828: Node n = new Node(propname, rel.getPrototype(), nmgr);
1829: n.setDbMapping(rel.getVirtualMapping());
1830: n.setParent(this );
1831: setNode(propname, n);
1832: return (Property) propMap.get(propname.toLowerCase());
1833: }
1834:
1835: // 2) check if this is a create-on-demand node property
1836: if (rel != null
1837: && (rel.isVirtual() || rel.isComplexReference())) {
1838: if (state != TRANSIENT) {
1839: Node n = nmgr.getNode(this , propname, rel);
1840:
1841: if (n != null) {
1842: if ((n.parentHandle == null) && !nmgr.isRootNode(n)) {
1843: n.setParent(this );
1844: n.name = propname;
1845: n.anonymous = false;
1846: }
1847: return new Property(propname, this , n);
1848: }
1849: }
1850: }
1851:
1852: // 4) nothing to be found - return null
1853: return null;
1854: }
1855:
1856: /**
1857: *
1858: *
1859: * @param propname ...
1860: *
1861: * @return ...
1862: */
1863: public String getString(String propname) {
1864: // propname = propname.toLowerCase ();
1865: Property prop = getProperty(propname);
1866:
1867: try {
1868: return prop.getStringValue();
1869: } catch (Exception ignore) {
1870: }
1871:
1872: return null;
1873: }
1874:
1875: /**
1876: *
1877: *
1878: * @param propname ...
1879: *
1880: * @return ...
1881: */
1882: public long getInteger(String propname) {
1883: // propname = propname.toLowerCase ();
1884: Property prop = getProperty(propname);
1885:
1886: try {
1887: return prop.getIntegerValue();
1888: } catch (Exception ignore) {
1889: }
1890:
1891: return 0;
1892: }
1893:
1894: /**
1895: *
1896: *
1897: * @param propname ...
1898: *
1899: * @return ...
1900: */
1901: public double getFloat(String propname) {
1902: // propname = propname.toLowerCase ();
1903: Property prop = getProperty(propname);
1904:
1905: try {
1906: return prop.getFloatValue();
1907: } catch (Exception ignore) {
1908: }
1909:
1910: return 0.0;
1911: }
1912:
1913: /**
1914: *
1915: *
1916: * @param propname ...
1917: *
1918: * @return ...
1919: */
1920: public Date getDate(String propname) {
1921: // propname = propname.toLowerCase ();
1922: Property prop = getProperty(propname);
1923:
1924: try {
1925: return prop.getDateValue();
1926: } catch (Exception ignore) {
1927: }
1928:
1929: return null;
1930: }
1931:
1932: /**
1933: *
1934: *
1935: * @param propname ...
1936: *
1937: * @return ...
1938: */
1939: public boolean getBoolean(String propname) {
1940: // propname = propname.toLowerCase ();
1941: Property prop = getProperty(propname);
1942:
1943: try {
1944: return prop.getBooleanValue();
1945: } catch (Exception ignore) {
1946: }
1947:
1948: return false;
1949: }
1950:
1951: /**
1952: *
1953: *
1954: * @param propname ...
1955: *
1956: * @return ...
1957: */
1958: public INode getNode(String propname) {
1959: // propname = propname.toLowerCase ();
1960: Property prop = getProperty(propname);
1961:
1962: try {
1963: return prop.getNodeValue();
1964: } catch (Exception ignore) {
1965: }
1966:
1967: return null;
1968: }
1969:
1970: /**
1971: *
1972: *
1973: * @param propname ...
1974: *
1975: * @return ...
1976: */
1977: public Object getJavaObject(String propname) {
1978: // propname = propname.toLowerCase ();
1979: Property prop = getProperty(propname);
1980:
1981: try {
1982: return prop.getJavaObjectValue();
1983: } catch (Exception ignore) {
1984: }
1985:
1986: return null;
1987: }
1988:
1989: /**
1990: * Directly set a property on this node
1991: *
1992: * @param propname ...
1993: * @param value ...
1994: */
1995: protected void set(String propname, Object value, int type) {
1996: checkWriteLock();
1997:
1998: if (propMap == null) {
1999: propMap = new Hashtable();
2000: }
2001:
2002: propname = propname.trim();
2003:
2004: String p2 = propname.toLowerCase();
2005:
2006: Property prop = (Property) propMap.get(p2);
2007:
2008: if (prop != null) {
2009: prop.setValue(value, type);
2010: } else {
2011: prop = new Property(propname, this );
2012: prop.setValue(value, type);
2013: propMap.put(p2, prop);
2014: }
2015:
2016: lastmodified = System.currentTimeMillis();
2017:
2018: if (state == CLEAN && isPersistableProperty(propname)) {
2019: markAs(MODIFIED);
2020: }
2021: }
2022:
2023: /**
2024: *
2025: *
2026: * @param propname ...
2027: * @param value ...
2028: */
2029: public void setString(String propname, String value) {
2030: // nmgr.logEvent ("setting String prop");
2031: checkWriteLock();
2032:
2033: if (propMap == null) {
2034: propMap = new Hashtable();
2035: }
2036:
2037: propname = propname.trim();
2038:
2039: String p2 = propname.toLowerCase();
2040:
2041: Property prop = (Property) propMap.get(p2);
2042: String oldvalue = null;
2043:
2044: if (prop != null) {
2045: oldvalue = prop.getStringValue();
2046:
2047: // check if the value has changed
2048: if ((value != null) && value.equals(oldvalue)) {
2049: return;
2050: }
2051:
2052: prop.setStringValue(value);
2053: } else {
2054: prop = new Property(propname, this );
2055: prop.setStringValue(value);
2056: propMap.put(p2, prop);
2057: }
2058:
2059: if (dbmap != null) {
2060:
2061: // check if this may have an effect on the node's parerent's child collection
2062: // in combination with the accessname or order field.
2063: Node parent = (parentHandle == null) ? null
2064: : (Node) getParent();
2065:
2066: if ((parent != null) && (parent.getDbMapping() != null)) {
2067: DbMapping parentmap = parent.getDbMapping();
2068: Relation subrel = parentmap.getSubnodeRelation();
2069: String dbcolumn = dbmap.propertyToColumnName(propname);
2070:
2071: if (subrel != null && dbcolumn != null) {
2072: // inlined version of notifyPropertyChange();
2073: if (subrel.order != null
2074: && subrel.order.indexOf(dbcolumn) > -1) {
2075: parent.registerSubnodeChange();
2076: }
2077: // check if accessname has changed
2078: if (subrel.accessName != null
2079: && subrel.accessName.equals(dbcolumn)) {
2080: // if any other node is contained with the new value, remove it
2081: INode n = (INode) parent.getChildElement(value);
2082:
2083: if ((n != null) && (n != this )) {
2084: throw new RuntimeException(
2085: this
2086: + " already contains an object named "
2087: + value);
2088: }
2089:
2090: // check if this node is already registered with the old name;
2091: // if so, remove it, then add again with the new acessname
2092: if (oldvalue != null) {
2093: n = (INode) parent
2094: .getChildElement(oldvalue);
2095:
2096: if (n == this ) {
2097: parent.unset(oldvalue);
2098: parent.addNode(this );
2099:
2100: // let the node cache know this key's not for this node anymore.
2101: nmgr.evictKey(new SyntheticKey(parent
2102: .getKey(), oldvalue));
2103: }
2104: }
2105:
2106: setName(value);
2107: }
2108: }
2109: }
2110:
2111: // check if the property we're setting specifies the prototype of this object.
2112: if (state != TRANSIENT
2113: && propname.equals(dbmap.columnNameToProperty(dbmap
2114: .getPrototypeField()))) {
2115: DbMapping newmap = nmgr.getDbMapping(value);
2116:
2117: if (newmap != null) {
2118: // see if old and new prototypes have same storage - otherwise type change is ignored
2119: String oldStorage = dbmap.getStorageTypeName();
2120: String newStorage = newmap.getStorageTypeName();
2121:
2122: if (((oldStorage == null) && (newStorage == null))
2123: || ((oldStorage != null) && oldStorage
2124: .equals(newStorage))) {
2125: // long now = System.currentTimeMillis();
2126: dbmap.setLastDataChange();
2127: newmap.setLastDataChange();
2128: this .dbmap = newmap;
2129: this .prototype = value;
2130: }
2131: }
2132: }
2133: }
2134:
2135: lastmodified = System.currentTimeMillis();
2136:
2137: if (state == CLEAN && isPersistableProperty(propname)) {
2138: markAs(MODIFIED);
2139: }
2140: }
2141:
2142: /**
2143: *
2144: *
2145: * @param propname ...
2146: * @param value ...
2147: */
2148: public void setInteger(String propname, long value) {
2149: // nmgr.logEvent ("setting bool prop");
2150: checkWriteLock();
2151:
2152: if (propMap == null) {
2153: propMap = new Hashtable();
2154: }
2155:
2156: propname = propname.trim();
2157:
2158: String p2 = propname.toLowerCase();
2159:
2160: Property prop = (Property) propMap.get(p2);
2161:
2162: if (prop != null) {
2163: prop.setIntegerValue(value);
2164: } else {
2165: prop = new Property(propname, this );
2166: prop.setIntegerValue(value);
2167: propMap.put(p2, prop);
2168: }
2169:
2170: notifyPropertyChange(propname);
2171:
2172: lastmodified = System.currentTimeMillis();
2173:
2174: if (state == CLEAN && isPersistableProperty(propname)) {
2175: markAs(MODIFIED);
2176: }
2177: }
2178:
2179: /**
2180: *
2181: *
2182: * @param propname ...
2183: * @param value ...
2184: */
2185: public void setFloat(String propname, double value) {
2186: // nmgr.logEvent ("setting bool prop");
2187: checkWriteLock();
2188:
2189: if (propMap == null) {
2190: propMap = new Hashtable();
2191: }
2192:
2193: propname = propname.trim();
2194:
2195: String p2 = propname.toLowerCase();
2196:
2197: Property prop = (Property) propMap.get(p2);
2198:
2199: if (prop != null) {
2200: prop.setFloatValue(value);
2201: } else {
2202: prop = new Property(propname, this );
2203: prop.setFloatValue(value);
2204: propMap.put(p2, prop);
2205: }
2206:
2207: notifyPropertyChange(propname);
2208:
2209: lastmodified = System.currentTimeMillis();
2210:
2211: if (state == CLEAN && isPersistableProperty(propname)) {
2212: markAs(MODIFIED);
2213: }
2214: }
2215:
2216: /**
2217: *
2218: *
2219: * @param propname ...
2220: * @param value ...
2221: */
2222: public void setBoolean(String propname, boolean value) {
2223: // nmgr.logEvent ("setting bool prop");
2224: checkWriteLock();
2225:
2226: if (propMap == null) {
2227: propMap = new Hashtable();
2228: }
2229:
2230: propname = propname.trim();
2231:
2232: String p2 = propname.toLowerCase();
2233:
2234: Property prop = (Property) propMap.get(p2);
2235:
2236: if (prop != null) {
2237: prop.setBooleanValue(value);
2238: } else {
2239: prop = new Property(propname, this );
2240: prop.setBooleanValue(value);
2241: propMap.put(p2, prop);
2242: }
2243:
2244: notifyPropertyChange(propname);
2245:
2246: lastmodified = System.currentTimeMillis();
2247:
2248: if (state == CLEAN && isPersistableProperty(propname)) {
2249: markAs(MODIFIED);
2250: }
2251: }
2252:
2253: /**
2254: *
2255: *
2256: * @param propname ...
2257: * @param value ...
2258: */
2259: public void setDate(String propname, Date value) {
2260: // nmgr.logEvent ("setting date prop");
2261: checkWriteLock();
2262:
2263: if (propMap == null) {
2264: propMap = new Hashtable();
2265: }
2266:
2267: propname = propname.trim();
2268:
2269: String p2 = propname.toLowerCase();
2270:
2271: Property prop = (Property) propMap.get(p2);
2272:
2273: if (prop != null) {
2274: prop.setDateValue(value);
2275: } else {
2276: prop = new Property(propname, this );
2277: prop.setDateValue(value);
2278: propMap.put(p2, prop);
2279: }
2280:
2281: notifyPropertyChange(propname);
2282:
2283: lastmodified = System.currentTimeMillis();
2284:
2285: if (state == CLEAN && isPersistableProperty(propname)) {
2286: markAs(MODIFIED);
2287: }
2288: }
2289:
2290: /**
2291: *
2292: *
2293: * @param propname ...
2294: * @param value ...
2295: */
2296: public void setJavaObject(String propname, Object value) {
2297: // nmgr.logEvent ("setting jobject prop");
2298: checkWriteLock();
2299:
2300: if (propMap == null) {
2301: propMap = new Hashtable();
2302: }
2303:
2304: propname = propname.trim();
2305:
2306: String p2 = propname.toLowerCase();
2307:
2308: Property prop = (Property) propMap.get(p2);
2309:
2310: if (prop != null) {
2311: prop.setJavaObjectValue(value);
2312: } else {
2313: prop = new Property(propname, this );
2314: prop.setJavaObjectValue(value);
2315: propMap.put(p2, prop);
2316: }
2317:
2318: notifyPropertyChange(propname);
2319:
2320: lastmodified = System.currentTimeMillis();
2321:
2322: if (state == CLEAN && isPersistableProperty(propname)) {
2323: markAs(MODIFIED);
2324: }
2325: }
2326:
2327: /**
2328: *
2329: *
2330: * @param propname ...
2331: * @param value ...
2332: */
2333: public void setNode(String propname, INode value) {
2334: // nmgr.logEvent ("setting node prop");
2335: // check if types match, otherwise throw exception
2336: Relation rel = (dbmap == null) ? null : dbmap
2337: .getExactPropertyRelation(propname);
2338: DbMapping nmap = (rel == null) ? null : rel
2339: .getPropertyMapping();
2340: DbMapping vmap = value.getDbMapping();
2341:
2342: if ((nmap != null) && (nmap != vmap)) {
2343: if (vmap == null) {
2344: value.setDbMapping(nmap);
2345: } else if (!nmap.isStorageCompatible(vmap)
2346: && !rel.isComplexReference()) {
2347: throw new RuntimeException("Can't set " + propname
2348: + " to object with prototype "
2349: + value.getPrototype() + ", was expecting "
2350: + nmap.getTypeName());
2351: }
2352: }
2353:
2354: if (state != TRANSIENT) {
2355: checkWriteLock();
2356: }
2357:
2358: Node n = null;
2359:
2360: if (value instanceof Node) {
2361: n = (Node) value;
2362: } else {
2363: throw new RuntimeException(
2364: "Can't add fixed-transient node to a persistent node");
2365: }
2366:
2367: boolean isPersistable = isPersistableProperty(propname);
2368: // if the new node is marked as TRANSIENT and this node is not, mark new node as NEW
2369: if (state != TRANSIENT && n.state == TRANSIENT && isPersistable) {
2370: n.makePersistable();
2371: }
2372:
2373: if (state != TRANSIENT) {
2374: n.checkWriteLock();
2375: }
2376:
2377: // check if the main identity of this node is as a named property
2378: // or as an anonymous node in a collection
2379: if (n != this && !nmgr.isRootNode(n) && isPersistable) {
2380: // avoid calling getParent() because it would return bogus results
2381: // for the not-anymore transient node
2382: Node nparent = (n.parentHandle == null) ? null
2383: : n.parentHandle.getNode(nmgr);
2384:
2385: // if the node doesn't have a parent yet, or it has one but it's
2386: // transient while we are persistent, make this the nodes new parent.
2387: if ((nparent == null)
2388: || ((state != TRANSIENT) && (nparent.getState() == TRANSIENT))) {
2389: n.setParent(this );
2390: n.name = propname;
2391: n.anonymous = false;
2392: }
2393: }
2394:
2395: propname = propname.trim();
2396:
2397: String p2 = propname.toLowerCase();
2398:
2399: if (rel == null && dbmap != null) {
2400: // widen relation to non-exact (collection) mapping
2401: rel = dbmap.getPropertyRelation(propname);
2402: }
2403:
2404: if (rel != null
2405: && (rel.countConstraints() > 1 || rel
2406: .isComplexReference())) {
2407: rel.setConstraints(this , n);
2408: if (rel.isComplexReference()) {
2409: Key key = new MultiKey(n.getDbMapping(), rel
2410: .getKeyParts(this ));
2411: nmgr.nmgr.registerNode(n, key);
2412: return;
2413: }
2414: }
2415:
2416: Property prop = (propMap == null) ? null : (Property) propMap
2417: .get(p2);
2418:
2419: if (prop != null) {
2420: if ((prop.getType() == IProperty.NODE)
2421: && n.getHandle().equals(prop.getNodeHandle())) {
2422: // nothing to do, just clean up locks and return
2423: if (state == CLEAN) {
2424: clearWriteLock();
2425: }
2426:
2427: if (n.state == CLEAN) {
2428: n.clearWriteLock();
2429: }
2430:
2431: return;
2432: }
2433: } else {
2434: prop = new Property(propname, this );
2435: }
2436:
2437: prop.setNodeValue(n);
2438:
2439: if ((rel == null) || rel.isReference() || state == TRANSIENT
2440: || rel.otherType == null
2441: || !rel.otherType.isRelational()) {
2442: // the node must be stored as explicit property
2443: if (propMap == null) {
2444: propMap = new Hashtable();
2445: }
2446:
2447: propMap.put(p2, prop);
2448:
2449: if (state == CLEAN && isPersistable) {
2450: markAs(MODIFIED);
2451: }
2452: }
2453:
2454: // don't check node in transactor cache if node is transient -
2455: // this is done anyway when the node becomes persistent.
2456: if (n.state != TRANSIENT) {
2457: // check node in with transactor cache
2458: Transactor tx = (Transactor) Thread.currentThread();
2459:
2460: // tx.visitCleanNode (new DbKey (dbm, nID), n);
2461: // UPDATE: using n.getKey() instead of manually constructing key. HW 2002/09/13
2462: tx.visitCleanNode(n.getKey(), n);
2463:
2464: // if the field is not the primary key of the property, also register it
2465: if ((rel != null) && (rel.accessName != null)
2466: && (state != TRANSIENT)) {
2467: Key secKey = new SyntheticKey(getKey(), propname);
2468: nmgr.registerNode(n, secKey);
2469: tx.visitCleanNode(secKey, n);
2470: }
2471: }
2472:
2473: lastmodified = System.currentTimeMillis();
2474:
2475: if (n.state == DELETED) {
2476: n.markAs(MODIFIED);
2477: }
2478: }
2479:
2480: private boolean isPersistableProperty(String propname) {
2481: return propname.length() > 0 && propname.charAt(0) != '_';
2482: }
2483:
2484: /**
2485: * Remove a property. Note that this works only for explicitly set properties, not for those
2486: * specified via property relation.
2487: */
2488: public void unset(String propname) {
2489:
2490: try {
2491: // if node is relational, leave a null property so that it is
2492: // updated in the DB. Otherwise, remove the property.
2493: Property p = null;
2494: boolean relational = (dbmap != null)
2495: && dbmap.isRelational();
2496:
2497: if (propMap != null) {
2498: if (relational) {
2499: p = (Property) propMap.get(propname.toLowerCase());
2500: } else {
2501: p = (Property) propMap.remove(propname
2502: .toLowerCase());
2503: }
2504: }
2505:
2506: if (p != null) {
2507: checkWriteLock();
2508:
2509: if (relational) {
2510: p.setStringValue(null);
2511: notifyPropertyChange(propname);
2512: }
2513:
2514: lastmodified = System.currentTimeMillis();
2515:
2516: if (state == CLEAN) {
2517: markAs(MODIFIED);
2518: }
2519: } else if (dbmap != null) {
2520: // check if this is a complex constraint and we have to
2521: // unset constraints.
2522: Relation rel = dbmap.getExactPropertyRelation(propname);
2523:
2524: if (rel != null && (rel.isComplexReference())) {
2525: p = getProperty(propname);
2526: rel.unsetConstraints(this , p.getNodeValue());
2527: }
2528: }
2529: } catch (Exception x) {
2530: getApp().logError("Error unsetting property", x);
2531: }
2532: }
2533:
2534: /**
2535: *
2536: *
2537: * @return ...
2538: */
2539: public long lastModified() {
2540: return lastmodified;
2541: }
2542:
2543: /**
2544: *
2545: *
2546: * @return ...
2547: */
2548: public long created() {
2549: return created;
2550: }
2551:
2552: /**
2553: * Return a string representation for this node. This tries to call the
2554: * javascript implemented toString() if it is defined.
2555: * @return a string representing this node.
2556: */
2557: public String toString() {
2558: try {
2559: // We need to reach deap into helma.framework.core to invoke toString(),
2560: // but the functionality is really worth it.
2561: RequestEvaluator reval = getApp()
2562: .getCurrentRequestEvaluator();
2563: if (reval != null) {
2564: Object str = reval.invokeDirectFunction(this ,
2565: "toString", RequestEvaluator.EMPTY_ARGS);
2566: if (str instanceof String)
2567: return (String) str;
2568: }
2569: } catch (Exception x) {
2570: // fall back to default representation
2571: }
2572: return "HopObject " + name;
2573: }
2574:
2575: /**
2576: * Tell whether this node is stored inside a relational db. This doesn't mean
2577: * it actually is stored in a relational db, just that it would be, if the node was
2578: * persistent
2579: */
2580: public boolean isRelational() {
2581: return (dbmap != null) && dbmap.isRelational();
2582: }
2583:
2584: /**
2585: * Public method to make a node persistent.
2586: */
2587: public void persist() {
2588: if (state == TRANSIENT) {
2589: makePersistable();
2590: } else if (state == CLEAN) {
2591: markAs(MODIFIED);
2592: }
2593:
2594: }
2595:
2596: /**
2597: * Turn node status from TRANSIENT to NEW so that the Transactor will
2598: * know it has to insert this node. Recursively persistifies all child nodes
2599: * and references.
2600: */
2601: private void makePersistable() {
2602: // if this isn't a transient node, do nothing.
2603: if (state != TRANSIENT) {
2604: return;
2605: }
2606:
2607: // mark as new
2608: setState(NEW);
2609:
2610: // generate a real, persistent ID for this object
2611: id = nmgr.generateID(dbmap);
2612: getHandle().becomePersistent();
2613:
2614: // register node with the transactor
2615: Transactor current = (Transactor) Thread.currentThread();
2616: current.visitDirtyNode(this );
2617: current.visitCleanNode(this );
2618:
2619: // recursively make children persistable
2620: makeChildrenPersistable();
2621: }
2622:
2623: /**
2624: * Recursively turn node status from TRANSIENT to NEW on child nodes
2625: * so that the Transactor knows they are to be persistified.
2626: */
2627: private void makeChildrenPersistable() {
2628: for (Enumeration e = getSubnodes(); e.hasMoreElements();) {
2629: Node n = (Node) e.nextElement();
2630:
2631: if (n.state == TRANSIENT) {
2632: n.makePersistable();
2633: }
2634: }
2635:
2636: for (Enumeration e = properties(); e.hasMoreElements();) {
2637: String propname = (String) e.nextElement();
2638: IProperty next = get(propname);
2639:
2640: if ((next != null) && (next.getType() == IProperty.NODE)) {
2641:
2642: // check if this property actually needs to be persisted.
2643: Node n = (Node) next.getNodeValue();
2644:
2645: if (n == null || n == this ) {
2646: continue;
2647: }
2648:
2649: if (dbmap != null) {
2650: Relation rel = dbmap.getExactPropertyRelation(next
2651: .getName());
2652: if (rel != null && rel.isVirtual()
2653: && !rel.needsPersistence()) {
2654: // temporarilly set state to TRANSIENT to avoid loading anything from db
2655: n.setState(TRANSIENT);
2656: n.makeChildrenPersistable();
2657: // make this a virtual node. what we do is basically to
2658: // replay the things done in the constructor for virtual nodes.
2659: // NOTE: setting the primaryKey may not be necessary since this
2660: // isn't managed by the nodemanager but rather an actual property of
2661: // its parent node.
2662: n.setState(VIRTUAL);
2663: n.primaryKey = new SyntheticKey(getKey(),
2664: propname);
2665: n.id = propname;
2666: continue;
2667: }
2668: }
2669:
2670: n.makePersistable();
2671: }
2672: }
2673: }
2674:
2675: /**
2676: * Get the cache node for this node. This can be
2677: * used to store transient cache data per node from Javascript.
2678: */
2679: public synchronized INode getCacheNode() {
2680: if (cacheNode == null) {
2681: cacheNode = new TransientNode();
2682: }
2683:
2684: return cacheNode;
2685: }
2686:
2687: /**
2688: * Reset the cache node for this node.
2689: */
2690: public synchronized void clearCacheNode() {
2691: cacheNode = null;
2692: }
2693:
2694: /**
2695: * This method walks down node path to the first non-virtual node and return it.
2696: * limit max depth to 5, since there shouldn't be more then 2 layers of virtual nodes.
2697: */
2698: public Node getNonVirtualParent() {
2699: Node node = this ;
2700:
2701: for (int i = 0; i < 5; i++) {
2702: if (node == null) {
2703: break;
2704: }
2705:
2706: if (node.getState() == Node.TRANSIENT) {
2707: DbMapping map = node.getDbMapping();
2708: if (map == null || !map.isVirtual())
2709: return node;
2710: } else if (node.getState() != Node.VIRTUAL) {
2711: return node;
2712: }
2713:
2714: node = (Node) node.getParent();
2715: }
2716:
2717: return null;
2718: }
2719:
2720: /**
2721: * Instances of this class may be used to mark an entry in the object cache as null.
2722: * This method tells the caller whether this is the case.
2723: */
2724: public boolean isNullNode() {
2725: return nmgr == null;
2726: }
2727:
2728: /**
2729: * We overwrite hashCode to make it dependant from the prototype. That way, when the prototype
2730: * changes, the node will automatically get a new ESNode wrapper, since they're cached in a hashtable.
2731: * You gotta love these hash code tricks ;-)
2732: */
2733: public int hashCode() {
2734: if (prototype == null) {
2735: return super .hashCode();
2736: } else {
2737: return super .hashCode() + prototype.hashCode();
2738: }
2739: }
2740:
2741: /**
2742: *
2743: */
2744: public void dump() {
2745: System.err.println("subnodes: " + subnodes);
2746: System.err.println("properties: " + propMap);
2747: }
2748:
2749: /**
2750: * This method get's called from the JavaScript environment
2751: * (HopObject.updateSubnodes() or HopObject.collection.updateSubnodes()))
2752: * The subnode-collection will be updated with a selectstatement getting all
2753: * Nodes having a higher id than the highest id currently contained within
2754: * this Node's subnoderelation. If this subnodelist has a special order
2755: * all nodes will be loaded honoring this order.
2756: * Example:
2757: * order by somefield1 asc, somefieled2 desc
2758: * gives a where-clausel like the following:
2759: * (somefiled1 > theHighestKnownValue value and somefield2 < theLowestKnownValue)
2760: * @return the number of loaded nodes within this collection update
2761: */
2762: public int updateSubnodes() {
2763: // TODO: what do we do if dbmap is null
2764: if (dbmap == null) {
2765: throw new RuntimeException(this
2766: + " doesn't have a DbMapping");
2767: }
2768: Relation subRel = dbmap.getSubnodeRelation();
2769: synchronized (this ) {
2770: lastSubnodeFetch = getLastSubnodeChange(subRel);
2771: return nmgr.updateSubnodeList(this , subRel);
2772: }
2773: }
2774:
2775: /**
2776: * Get the application this node belongs to.
2777: * @return the app we belong to
2778: */
2779: private Application getApp() {
2780: return nmgr.nmgr.app;
2781: }
2782: }
|