Source Code Cross Referenced for Node.java in  » Web-Framework » helma » helma » objectmodel » db » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Web Framework » helma » helma.objectmodel.db 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


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:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.