KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > module > core > MMObjectNode


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10 package org.mmbase.module.core;
11
12 import java.util.*;
13 import java.io.*;
14
15 import org.mmbase.cache.*;
16 import org.mmbase.bridge.Field;
17 import org.mmbase.bridge.Node;
18 import org.mmbase.module.corebuilders.InsRel;
19 import org.mmbase.module.builders.DayMarkers;
20 import org.mmbase.security.*;
21 import org.mmbase.storage.search.*;
22 import org.mmbase.util.Casting;
23 import org.mmbase.util.SizeOf;
24 import org.mmbase.util.DynamicDate;
25 import org.mmbase.util.logging.*;
26 import org.mmbase.util.functions.*;
27 import org.w3c.dom.Document JavaDoc;
28
29 /**
30  * MMObjectNode is the core of the MMBase system.
31  * This class is what its all about, because the instances of this class hold the content we are using.
32  * All active Nodes with data and relations are MMObjectNodes and make up the
33  * object world that is MMBase (Creating, searching, removing is done by the node's parent,
34  * which is a class extended from MMObjectBuilder)
35  *
36  * @author Daniel Ockeloen
37  * @author Pierre van Rooden
38  * @author Eduard Witteveen
39  * @author Michiel Meeuwissen
40  * @author Ernst Bunders
41  * @version $Id: MMObjectNode.java,v 1.193.2.3 2006/11/28 13:48:45 johannes Exp $
42  */

43
44 public class MMObjectNode implements org.mmbase.util.SizeMeasurable, java.io.Serializable JavaDoc {
45     private static final Logger log = Logging.getLoggerInstance(MMObjectNode.class);
46
47     /**
48      * @deprecated Simply use <code>null</code>
49      */

50     public final static Object JavaDoc VALUE_NULL = null;
51     /**
52      * Large fields (blobs) are loaded 'lazily', so only on explicit request. Until the first exlicit request this value is stored in such fields.
53      * It can be set back into the field with {@link #storeValue}, to unload the field again.
54      * @since MMBase-1.7.4
55      */

56     public final static String JavaDoc VALUE_SHORTED = "$SHORTED";
57
58     /**
59      * @deprecated use RelationsCache.getCache().getHits()
60      */

61     public static int getRelationCacheHits() {
62         return relationsCache.getHits();
63     }
64
65     /**
66      * @deprecated use RelationsCache.getCache().getMisses()
67      */

68     public static int getRelationCacheMiss() {
69         return relationsCache.getMisses();
70     }
71
72     /**
73      * Results of getRelatedNodes
74      * @since 1.7
75      */

76     protected static final RelatedNodesCache relatedCache = RelatedNodesCache.getCache();
77
78
79     /**
80      * objectNumber -> List of all relation nodes
81      * @since MMBase-1.7
82      */

83     protected static final RelationsCache relationsCache = RelationsCache.getCache();
84     // < MMBase-1.7, every mmobjectnode instance had a cache for relation nodes
85
// private Vector relations=null; // possibly filled with insRels
86

87
88
89     /**
90      * Map which stores the current database value for fields when
91      * then change in the node.
92      * it can be used to optimise cacheing
93      * @since MMBase-1.8
94      */

95     private Map oldValues = new HashMap();
96
97
98     /**
99      * Holds the name - value pairs of this node (the node's fields).
100      * Most nodes will have a 'number' and an 'otype' field, and fields which will differ by builder.
101      * This collection should not be directly queried or changed -
102      * use the SetValue and getXXXValue methods instead.
103      * It should then be made private, and methods that change the map (storeValue) be made synchronized.
104      * Note: To avoid synchronisation conflicts, we can't really change the type until the property is made private.
105      */

106     protected Map values = Collections.synchronizedMap(new HashMap());
107     private Map sizes = Collections.synchronizedMap(new HashMap());
108
109     /**
110      * Determines whether the node is being initialized (typically when it is loaded from the database).
111      * Use {@link #start} to start initializing, use {@link #finish} to end.
112      * @since MMBase-1.7
113      */

114     protected boolean initializing = false;
115
116     /**
117      * Holds the 'extra' name-value pairs (the node's properties)
118      * which are retrieved from the 'properties' table.
119      * @scope private
120      */

121     public Hashtable properties;
122
123     /**
124      * Set which stores the keys of the fields that were changed
125      * since the last commit.
126      */

127     private Set changed = Collections.synchronizedSet(new HashSet());
128
129     /**
130      * Pointer to the parent builder that is responsible for this node.
131      * Note: this may on occasion (due to optimization) duffer for the node's original builder.
132      * Use {@link #getBuilder} instead.
133      * @scope private
134      */

135     protected MMObjectBuilder parent;
136
137     /**
138      * Pointer to the actual builder to which this node belongs.
139      * This value is initialised through the first call to {@link #getBuilder}
140      */

141     private MMObjectBuilder builder = null;
142
143     /**
144      * If <code>true</code>, the node is a new node, which is not (yet) stored in storage.
145      */

146     protected boolean isNew = false;
147
148     /**
149      * New aliases of the node
150      * @scope private
151      */

152     private Set aliases = null;
153
154     // object to sync access to properties
155
private final Object JavaDoc properties_sync = new Object JavaDoc();
156
157     /**
158      * temporarily holds a new context for a node
159      * @since MMBase-1.7
160      */

161
162     private String JavaDoc newContext = null;
163
164    /**
165     * Default Main constructor, creates a node that is new and not (yet) in storage.
166     * @param parent the node's parent, an instance of the node's builder.
167     * @throws IllegalArgumentException If parent is <code>null</code>
168     */

169     public MMObjectNode(MMObjectBuilder parent) {
170         this(parent, true);
171     }
172
173     /**
174      * Main constructor.
175      * @param parent the node's parent, an instance of the node's builder.
176      * @param isNew if the node is a newly created node
177      * @throws IllegalArgumentException If parent is <code>null</code>
178      */

179     public MMObjectNode(MMObjectBuilder parent, boolean isNew) {
180         this.isNew = isNew;
181         if (parent != null) {
182             this.parent = parent;
183         } else {
184             throw new IllegalArgumentException JavaDoc("Constructor called with parent=null");
185         }
186     }
187
188     /**
189      * @since MMBase-1.8
190      */

191     public MMObjectNode(MMObjectNode node) {
192         parent = node.parent;
193         isNew = node.isNew();
194         values.putAll(node.getValues());
195         values.putAll(node.getOldValues());
196     }
197
198     /**
199      * Creates an MMObject based on a given Map. This can e.g. be used to make an MMObjectNode of a bridge node (use {@link org.mmbase.bridge.util.NodeMap}).
200      *
201      * @since MMBase-1.8
202      */

203     public MMObjectNode(MMObjectBuilder parent, Map map) {
204         isNew = false;
205         this.parent = parent;
206         values = map;
207     }
208
209     /**
210      * Returns the actual builder of the node.
211      * Note that it is possible that, due to optimization, a node is currently associated with
212      * another (parent) builder, i.e. a posrel node may be associated with a insrel builder.
213      * This method returns the actual builder.
214      * The node may miss vital information (not retrieved from the database) to act as a node of such
215      * a builder - if you need actual status you need to reload it.
216      * @since MMBase-1.6
217      * @return the builder of this node
218      */

219     public MMObjectBuilder getBuilder() {
220         if (builder == null) {
221             int oType = getOType();
222             if (oType == -1 || parent.getNumber() == oType) {
223                 builder = parent;
224             } else {
225                 String JavaDoc builderName = parent.mmb.getTypeDef().getValue(oType);
226                 if (builderName != null) { // avoid NPE from mmb.getBuilder.
227
builder = parent.mmb.getBuilder(builderName);
228                 }
229             }
230             if (builder == null) {
231                 log.warn("Builder of node " + getNumber() + " not found, taking 'object'");
232                 builder = parent.mmb.getBuilder("object");
233             }
234         }
235         return builder;
236     }
237
238     /**
239      * Start the loading of a node
240      * @since MMBase-1.7
241      */

242     public void start() {
243         initializing = true;
244     }
245
246     /**
247      * Finish the loading of a node
248      * @since MMBase-1.7
249      */

250     public void finish() {
251         initializing = false;
252     }
253
254     /**
255      * Tests whether the data in a node is valid (throws an exception if this is not the case).
256      * @throws org.mmbase.module.core.InvalidDataException
257      * If the data was unrecoverably invalid (the references did not point to existing objects)
258      */

259     public void testValidData() throws InvalidDataException {
260         parent.testValidData(this);
261     };
262
263     /**
264      * Commit the node to the database or other storage system.
265      * This can only be done on a existing (inserted) node. It will use the
266      * changed Vector as its base of what to commit/change.
267      * @return <code>true</code> if the commit was succesfull, <code>false</code> is it failed
268      */

269     public boolean commit() {
270         boolean success = parent.commit(this);
271         if (success) {
272             isNew = false; // perhaps it is always already false (otherwise insert is called, I think), but no matter, now it certainly isn't new!
273
} else {
274             values.putAll(oldValues);
275         }
276         oldValues.clear();
277         changed.clear();
278         return success;
279     }
280
281
282     /**
283      * Undo changes made to the node.
284      *
285      * @since MMBase-1.8
286      */

287     public void cancel() {
288         values.putAll(oldValues);
289         oldValues.clear();
290         changed.clear();
291     }
292     /**
293      * Insert this node into the storage
294      * @param userName the name of the user who inserts the node. This value is ignored
295      * @return the new node key (number field), or -1 if the insert failed
296      */

297     public int insert(String JavaDoc userName) {
298         return parent.insert(userName, this);
299     }
300
301     /**
302      * Insert this node into the database or other storage system.
303      * @param user the user who inserts the node.
304      * Used to set security-related information
305      * @return the new node key (number field), or -1 if the insert failed
306      * @since MMBase-1.7
307      */

308     public int insert(UserContext user) {
309         int nodeID = parent.safeInsert(this, user.getIdentifier());
310         if (nodeID != -1) {
311             MMBaseCop mmbaseCop = parent.getMMBase().getMMBaseCop();
312             mmbaseCop.getAuthorization().create(user, nodeID);
313             if (newContext != null) {
314                 mmbaseCop.getAuthorization().setContext(user, nodeID, newContext);
315                 newContext = null;
316             }
317         }
318         return nodeID;
319     }
320
321     /**
322      * Commit this node to the storage
323      * @param user the user who commits the node.
324      * Used to set security-related information
325      * @return <code>true</code> if succesful
326      * @since MMBase-1.7
327      */

328     public boolean commit(UserContext user) {
329         boolean success = parent.safeCommit(this);
330         if (success) {
331             MMBaseCop mmbaseCop = parent.getMMBase().getMMBaseCop();
332             mmbaseCop.getAuthorization().update(user, getNumber());
333             if (newContext != null) {
334                 mmbaseCop.getAuthorization().setContext(user,getNumber(), newContext);
335                 newContext = null;
336             }
337         }
338         return success;
339     }
340
341     /**
342      * Remove this node from the storage
343      * @param user the user who removes the node.
344      * Used to set security-related information
345      * @since MMBase-1.7
346      */

347     public void remove(UserContext user) {
348         if (log.isDebugEnabled()) {
349             log.debug("Deleting node " + getNumber() + " because " + Logging.stackTrace(5));
350         }
351         parent.removeNode(this);
352         parent.getMMBase().getMMBaseCop().getAuthorization().remove(user, getNumber());
353     }
354
355     /**
356      * Sets the security context for this node
357      * @param user the user who changes the context of the node.
358      * @param context the new context
359      * @param now if <code>true</code>, the context is changed instantly, otherwise it is changed
360      * after the node is send to storage.
361      * @since MMBase-1.7
362      */

363     public void setContext(UserContext user, String JavaDoc context, boolean now) {
364        if (now) {
365            parent.getMMBase().getMMBaseCop().getAuthorization().setContext(user, getNumber(), context);
366        } else {
367            newContext = context;
368        }
369     }
370
371     /**
372      * Returns the security context for this node
373      * @param user the user who requests the context of the node.
374      * @since MMBase-1.7.1
375      */

376     public String JavaDoc getContext(UserContext user) {
377         if (newContext != null) return newContext;
378         if (getNumber() < 0) return user.getOwnerField();
379         return parent.getMMBase().getMMBaseCop().getAuthorization().getContext(user, getNumber());
380     }
381
382     /**
383      * Returns the possible new security contexts for this node
384      * @param user the user who requests the context of the node.
385      * @since MMBase-1.7.1
386      */

387     public Set getPossibleContexts(UserContext user) {
388         if (getNumber() < 0) {
389             // a new node has yet no context (except the default).
390
// instead of searching the database for data, return a
391
// standard set of values existing of the current context
392
// and the contexts "system" and "admin".
393
// A better way involves rewriting the security layer to accept
394
// MMObjectNodes instead of node numbers
395
Set contexts = new HashSet();
396             contexts.add(getContext(user));
397             contexts.add("admin");
398             contexts.add("system");
399             return contexts;
400 /*
401             NodeSearchQuery query = new NodeSearchQuery(parent);
402             CoreField fieldDefs = parent.getField("owner");
403             StepField field = query.getField(fieldDefs);
404             BasicFieldValueConstraint cons = new BasicFieldValueConstraint(field, getContext(user));
405             query.setMaxNumber(1);
406             try {
407                 Iterator resultList = parent.getNodes(query).iterator();
408                 if (resultList.hasNext()) {
409                     return ((MMObjectNode) resultList.next()).getPossibleContexts(user);
410                 }
411             } catch (SearchQueryException sqe) {
412                 log.error(sqe.toString());
413             }
414             return new HashSet();
415 */

416         }
417         return parent.getMMBase().getMMBaseCop().getAuthorization().getPossibleContexts(user, getNumber());
418     }
419
420     /**
421      * Returns the core of this node in a string.
422      * Used for debugging.
423      * For data exchange use toXML() and getDTD().
424      * @return the contents of the node as a string.
425      */

426     public String JavaDoc toString() {
427         if (parent != null) {
428             return parent.toString(this);
429         } else {
430             return defaultToString();
431         }
432     }
433
434     /**
435      * @since MMBase-1.6.2
436      */

437     String JavaDoc defaultToString() {
438         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
439         try {
440             Set entrySet = values.entrySet();
441             synchronized(values) {
442                 Iterator i = entrySet.iterator();
443                 while (i.hasNext()) {
444                     Map.Entry entry = (Map.Entry) i.next();
445                     String JavaDoc key = (String JavaDoc) entry.getKey();
446                     int dbtype = getDBType(key);
447                     String JavaDoc value = "" + entry.getValue(); // XXX:should be retrieveValue ?
448
if ("".equals(result.toString())) {
449                         result = new StringBuffer JavaDoc(key+"='"+value+"'"); // can this occur?
450
} else {
451                         result.append(","+key+"='");
452                         Casting.toStringBuffer(result, value).append("'");
453                     }
454                 }
455             }
456         } catch(Throwable JavaDoc e) {
457             result.append("" + values); // simpler version...
458
}
459         result.append(super.toString());
460         return result.toString();
461     }
462
463
464     /**
465      * @return <code>true</code> if field exists and may be used.
466      * @since MMBase-1.8
467      */

468     protected boolean checkFieldExistance(String JavaDoc fieldName) {
469         if (fieldName.charAt(0) == '_') {
470             // don't complain then, a lot of hackery (apps1 import/export) is based on this.
471
// This is just a hack to make app1 import/export working, withough exposing the values map.
472
return true;
473         }
474         if (fieldName.indexOf('(') > 0) {
475             return true;
476         }
477         if (!getBuilder().hasField(fieldName)) {
478             if (MMBase.getMMBase().inDevelopment()) {
479                 throw new IllegalArgumentException JavaDoc("You cannot use non-existing field '" + fieldName + "' of node '" + getNumber() + "' existing fields of '" + getBuilder().getTableName() + "' are " + getBuilder().getFieldNames());
480             } else {
481                 log.warn("Tried to use non-existing field '" + fieldName + "' of node '" + getNumber() + "' from " + getBuilder().getTableName());
482                 return false;
483             }
484         }
485         return true;
486     }
487
488     /**
489      * Stores a value in the values hashtable.
490      * This is a low-level method that circumvents typechecking and the triggers of extended classes.
491      * You should normally call {@link #setValue} to change fields.
492      * @todo This should become a synchronized method, once values becomes a private HashMap instead of a
493      * public Hashtable.
494      *
495      *@param fieldName the name of the field to change
496      *@param fieldValue the value to assign
497      */

498     public void storeValue(String JavaDoc fieldName, Object JavaDoc fieldValue) {
499         if (fieldName.startsWith("_") && fieldValue == null) {
500             // This is just a hack to make app1 import/export working, withough exposing the values map.
501
values.remove(fieldName);
502         }
503         if (checkFieldExistance(fieldName)) {
504             values.put(fieldName, fieldValue);
505         }
506     }
507
508     /**
509      * this method stores a fieldvalue only once. the purpose is to
510      * store the value only the first time a field changes, so it reflects
511      * the value in the database.
512      * @param fieldName
513      * @param object
514      * @since MMBase-1.8
515      */

516     private void storeOldValue(String JavaDoc fieldName, Object JavaDoc object) {
517         if (! oldValues.containsKey(fieldName)) {
518             oldValues.put(fieldName, object);
519         }
520     }
521
522
523     /**
524      * Retrieves a value from the values hashtable.
525      * This is a low-level method that circumvents typechecking and the triggers of extended classes.
526      * You should normally call {@link #getValue} to load fields.
527      *
528      * @param fieldName the name of the field to change
529      * @return the value of the field
530      */

531     public Object JavaDoc retrieveValue(String JavaDoc fieldName) {
532         return values.get(fieldName);
533     }
534
535     /**
536      * Determines whether the node is virtual.
537      * A virtual node is not persistent (that is, stored in a database table).
538      */

539     public boolean isVirtual() {
540         return false;
541     }
542
543
544     /**
545      * If a node is still 'new' you must persistify it with {@link #insert}, and otherwise with {@link #commit}.
546      * @since MMBase-1.8
547      */

548     public boolean isNew() {
549         return isNew;
550     }
551     /*
552      *
553      * @since MMBase-1.6
554      */

555
556     protected Document JavaDoc toXML(Object JavaDoc value, String JavaDoc fieldName) {
557         Document JavaDoc doc = Casting.toXML(value);
558         if (doc == null && parent.getField(fieldName).isRequired()) {
559             doc = Casting.toXML("<p/>");
560         }
561         return doc;
562     }
563
564     /**
565      * Sets a key/value pair in the main values of this node.
566      * Note that if this node is a node in cache, the changes are immediately visible to
567      * everyone, even if the changes are not committed.
568      * The fieldName is added to the (public) 'changed' vector to track changes.
569      * @param fieldName the name of the field to change
570      * @param fieldValue the value to assign
571      * @return <code>true</code> When the field was changed, false otherwise.
572      */

573     public boolean setValue(String JavaDoc fieldName, Object JavaDoc fieldValue) {
574         // check the value also when the parent thing is null
575
Object JavaDoc originalValue = values.get(fieldName);
576
577         if (fieldValue != VALUE_SHORTED) {
578             // make sure this value remains not in the blob-cache.
579
BlobCache blobs = parent.getBlobCache(fieldName);
580             blobs.remove(blobs.getKey(getNumber(), fieldName));
581         }
582
583         if (fieldValue instanceof DynamicDate) {
584             // 'dynamic' values can of course not be stored in database, and that is not the intentention too, so
585
// store a static version
586
fieldValue = new Date(((Date) fieldValue).getTime());
587         }
588
589         if (log.isDebugEnabled()) {
590             String JavaDoc string;
591             if (fieldValue instanceof byte[]) {
592                 string = "byte array of size " + ((byte[])fieldValue).length;
593             } else {
594                 string = Casting.toString(fieldValue);
595                 if (string.length()>200) string = string.substring(0, 200);
596             }
597             log.debug("Setting " + fieldName + " to " + string);
598         }
599
600         boolean changed =
601             (! values.containsKey(fieldName)) ||
602             (originalValue == null ? fieldValue != null : ! originalValue.equals(fieldValue));
603         if (! changed) return false;
604
605         if (log.isDebugEnabled()) {
606             log.debug("" + fieldName + ":" + originalValue + " --> " + fieldValue);
607         }
608
609         //store the old value
610
storeOldValue(fieldName, originalValue);
611
612
613         // put the key/value in the value hashtable
614
storeValue(fieldName, fieldValue);
615
616         // process the changed value (?)
617
if (parent != null) {
618             if(!parent.setValue(this, fieldName, originalValue)) {
619                 // setValue of parent returned false, no update needed...
620
return false;
621             }
622         } else {
623             log.error("parent was null for node with number" + getNumber());
624         }
625         setUpdate(fieldName);
626         return true;
627     }
628
629     /**
630      * Sets the size (in byte) of the given field. This is meant for byte-array fields, which you
631      * fill using an InputStream.
632      * @see #getSize(String)
633      * @since MMBase-1.8
634      */

635     public void setSize(String JavaDoc fieldName, long size) {
636         sizes.put(fieldName, new Long JavaDoc(size));
637     }
638     /**
639      * Returns the size (in byte) of the given field. This is mainly targeted at fields of the type
640      * byte array. For other fields this method will return something reasonable, but it is as yet
641      * not well defined what...
642      *
643      * @since MMBase-1.8
644      */

645     public long getSize(String JavaDoc fieldName) {
646         Long JavaDoc l = (Long JavaDoc) sizes.get(fieldName);
647         if (l != null) return l.intValue();
648         Object JavaDoc value = values.get(fieldName);
649         // Value is null so it does not occupy any space.
650
if (value == null) return 0;
651         // Value is not yet loaded from the database?
652
if (VALUE_SHORTED.equals(value)) return -1;
653         return SizeOf.getByteSize(value);
654     }
655
656     /**
657      * Sets a key/value pair in the main values of this node. The value to set is of type <code>boolean</code>.
658      * Note that if this node is a node in cache, the changes are immediately visible to
659      * everyone, even if the changes are not committed.
660      * The fieldName is added to the (public) 'changed' vector to track changes.
661      * @param fieldName the name of the field to change
662      * @param fieldValue the value to assign
663      * @return always <code>true</code>
664      */

665     public boolean setValue(String JavaDoc fieldName, boolean fieldValue) {
666         return setValue(fieldName, Boolean.valueOf(fieldValue));
667     }
668
669     /**
670      * Sets a key/value pair in the main values of this node. The value to set is of type <code>int</code>.
671      * Note that if this node is a node in cache, the changes are immediately visible to
672      * everyone, even if the changes are not committed.
673      * The fieldName is added to the (public) 'changed' vector to track changes.
674      * @param fieldName the name of the field to change
675      * @param fieldValue the value to assign
676      * @return always <code>true</code>
677      */

678     public boolean setValue(String JavaDoc fieldName, int fieldValue) {
679         return setValue(fieldName, new Integer JavaDoc(fieldValue));
680     }
681
682     /**
683      * Sets a key/value pair in the main values of this node. The value to set is of type <code>int</code>.
684      * Note that if this node is a node in cache, the changes are immediately visible to
685      * everyone, even if the changes are not committed.
686      * The fieldName is added to the (public) 'changed' vector to track changes.
687      * @param fieldName the name of the field to change
688      * @param fieldValue the value to assign
689      * @return always <code>true</code>
690      */

691     public boolean setValue(String JavaDoc fieldName, long fieldValue) {
692         return setValue(fieldName, new Long JavaDoc(fieldValue));
693     }
694
695     /**
696      * Sets a key/value pair in the main values of this node. The value to set is of type <code>double</code>.
697      * Note that if this node is a node in cache, the changes are immediately visible to
698      * everyone, even if the changes are not committed.
699      * The fieldName is added to the (public) 'changed' vector to track changes.
700      * @param fieldName the name of the field to change
701      * @param fieldValue the value to assign
702      * @return always <code>true</code>
703      */

704     public boolean setValue(String JavaDoc fieldName, double fieldValue) {
705         return setValue(fieldName, new Double JavaDoc(fieldValue));
706     }
707
708
709     // Add the field to update to the changed Vector
710
//
711
private void setUpdate(String JavaDoc fieldName) {
712         // obtain the type of field this is
713
int state = getDBState(fieldName);
714
715         // add it to the changed vector so we know that we have to update it
716
// on the next commit
717
if (! initializing && state != Field.STATE_VIRTUAL) {
718             changed.add(fieldName);
719         }
720         // is it a memory only field ? then send a fieldchange
721
if (state == 0) sendFieldChangeSignal(fieldName);
722     }
723
724     /**
725      * Retrieve an object's number.
726      * In case of a new node that is not committed, this will return -1.
727      * @return the number of the node
728      */

729     public int getNumber() {
730         return Casting.toInt(values.get(MMObjectBuilder.FIELD_NUMBER));
731     }
732
733     /**
734      * Retrieve an object's object type.
735      * This is a number (an index in the typedef builer), rather than a name.
736      * @return the object type number of the node
737      */

738     public int getOType() {
739         return Casting.toInt(values.get(MMObjectBuilder.FIELD_OBJECT_TYPE));
740     }
741
742
743     /**
744      * @since MMBase-1.8
745      */

746     public boolean isNull(String JavaDoc fieldName) {
747         if (checkFieldExistance(fieldName)) {
748             Field field = getBuilder().getField(fieldName);
749             if (field != null && field.getType() == Field.TYPE_NODE) {
750                 return getIntValue(fieldName) <= -1;
751             }
752             Object JavaDoc value = values.get(fieldName);
753             if (VALUE_SHORTED.equals(value)) {
754                 // value is not loaded from the database. We have to check the database to be sure.
755
value = getValue(fieldName);
756             }
757             return value == null;
758         } else {
759             return true;
760         }
761     }
762
763     /**
764      * Get a value of a certain field.
765      * @performance do not store byte values directly in node (?)
766      * @param fieldName the name of the field who's data to return
767      * @return the field's value as an <code>Object</code>
768      */

769     public Object JavaDoc getValue(String JavaDoc fieldName) {
770         // get the value from the values table
771
Object JavaDoc value = values.get(fieldName);
772
773         // explicitly load byte values if they are 'shortened'
774
if (VALUE_SHORTED.equals(value)) { // could use == if we are sure that everybody uses the constant
775

776             BlobCache blobs = parent.getBlobCache(fieldName);
777             String JavaDoc key = blobs.getKey(getNumber(), fieldName);
778             value = blobs.get(key);
779             if (value == null) {
780                 int type = getDBType(fieldName);
781                 switch (type) {
782                 case Field.TYPE_BINARY:
783                     value = parent.getShortedByte(fieldName, this);
784                     break;
785                 case Field.TYPE_STRING:
786                     value = parent.getShortedText(fieldName, this);
787                     break;
788                 default:
789                     throw new UnsupportedOperationException JavaDoc("Found shorted value for type " + type);
790                 }
791                 blobs.put(key, value);
792             }
793         }
794
795         // if we have an XML-dbtype field, we always have to return a Document (or null).
796
// note that if the value is null we store it as a null value
797
if (parent != null && value != null && !(value instanceof Document JavaDoc) &&
798             getDBType(fieldName) == Field.TYPE_XML) {
799             String JavaDoc string = Casting.toString(value).trim();
800             Document JavaDoc doc = toXML(string, fieldName);
801             if(doc != null) {
802                 // store the document inside the field.. much faster...
803
value = doc;
804                 values.put(fieldName, value);
805             } else {
806                 values.put(fieldName, null);
807             }
808         }
809
810         // routine to check for indirect values
811
// this are used for functions for example
812
// its implemented per builder so lets give this
813
// request to our builder
814
if (value == null) {
815             value = parent.getValue(this, fieldName);
816         }
817         // still null!
818
if (value == null) {
819             if (!checkFieldExistance(fieldName)) return null;
820         }
821
822         if (value instanceof InputStream) {
823             value = useInputStream(fieldName, (InputStream) value);
824         }
825
826         // return the found object
827
return value;
828     }
829
830
831     /**
832      * Get a value of a certain field. The value is returned as a
833      * String. Non-string values are automatically converted to
834      * String. 'null' is converted to an empty string.
835      * @param fieldName the name of the field who's data to return
836      * @return the field's value as a <code>String</code>
837      */

838     public String JavaDoc getStringValue(String JavaDoc fieldName) {
839         Object JavaDoc value = getValue(fieldName);
840         if (value instanceof MMObjectNode) return "" + ((MMObjectNode)value).getNumber();
841         String JavaDoc s = Casting.toString(value);
842         return s;
843     }
844
845     /**
846      * XXX: return type of this method make it impossible to make MMObjectNode implements Node, perhaps it needs change
847      * @javadoc
848      * @since MMBase-1.6
849      */

850     public Object JavaDoc getFunctionValue(String JavaDoc functionName, List parameters) {
851         return parent.getFunctionValue(this, functionName, parameters);
852     }
853
854     /**
855      * @javadoc
856      * @since MMBase-1.8
857      */

858     public Parameters createParameters(String JavaDoc functionName) {
859         return parent.createParameters(functionName);
860     }
861
862     /**
863      * @javadoc
864      * @since MMBase-1.8
865      */

866     public Function getFunction(String JavaDoc functionName) {
867         return parent.getFunction(this, functionName);
868     }
869
870     /**
871      * @javadoc
872      * @since MMBase-1.8
873      */

874     public Collection getFunctions() {
875         return parent.getFunctions(this);
876     }
877
878     /**
879      * Returns the value of the specified field as a <code>dom.Document</code>
880      * If the node value is not itself a Document, the method attempts to
881      * attempts to convert the String value into an XML.
882      * If the value cannot be converted, this method returns <code>null</code>
883      *
884      * @param fieldName the name of the field to be returned
885      * @return the value of the specified field as a DOM Element or <code>null</code>
886      * @throws IllegalArgumentException if the value cannot be converted.
887      * @since MMBase-1.6
888      */

889     public Document JavaDoc getXMLValue(String JavaDoc fieldName) {
890         Document JavaDoc o = toXML(getValue(fieldName), fieldName);
891         if(o != null && getDBType(fieldName) == Field.TYPE_XML) {
892             storeValue(fieldName, o);
893         }
894         return o;
895     }
896
897
898     /**
899      * If the values map contains an InputStream, care must be taken because often an InputStream can be used only once.
900      * @since MMBase-1.8
901      */

902     private byte[] useInputStream(String JavaDoc fieldName, InputStream stream) { // first, convert to byte-array
903
ByteArrayOutputStream bos = new ByteArrayOutputStream();
904         int c;
905         try {
906             byte[] buf = new byte[1024];
907             int n;
908             while ((n = stream.read(buf)) > -1) {
909                 bos.write(buf, 0, n);
910             }
911         } catch (IOException ioe) {
912             log.error(ioe);
913         }
914         byte[] b = bos.toByteArray();
915         // check if we can cache it.
916
BlobCache blobs = parent.getBlobCache(fieldName);
917         String JavaDoc key = blobs.getKey(getNumber(), fieldName);
918         if (b.length < blobs.getMaxEntrySize()) {
919             blobs.put(key, b);
920         }
921
922         values.put(fieldName, b);
923         return b;
924     }
925
926     /**
927      * Get a binary value of a certain field.
928      * @performance do not store byte values directly in node (?)
929      * @param fieldName the name of the field who's data to return
930      * @return the field's value as an <code>byte []</code> (binary/blob field)
931      */

932     public byte[] getByteValue(String JavaDoc fieldName) {
933         Object JavaDoc obj = getValue(fieldName);
934         if (obj == null) {
935             return new byte[0];
936         } else if (obj instanceof byte[]) {
937             // was already unmapped so return the value
938
return (byte[]) obj;
939         } else {
940             byte[] b;
941             if (getDBType(fieldName) == Field.TYPE_STRING) {
942                 String JavaDoc s = getStringValue(fieldName);
943                 try {
944                     b = s.getBytes(parent.getMMBase().getEncoding());
945                 } catch (UnsupportedEncodingException uee) {
946                     log.error(uee.getMessage());
947                     b = s.getBytes();
948                 }
949             } else {
950                 b = new byte[0];
951             }
952             return b;
953         }
954     }
955
956
957     public InputStream getInputStreamValue(String JavaDoc fieldName) {
958         Object JavaDoc value = values.get(fieldName);
959         if (value == null) {
960             log.debug("NUL on " + fieldName + " " + this, new Exception JavaDoc());
961             return new ByteArrayInputStream(new byte[0]);
962         }
963
964         if (value instanceof InputStream) {
965             // cannot return it directly, it would kill the inputstream, and perhaps it cannot be saved in db anymore then.
966
// Sad, we have a buffer always now.
967
// XXX think of something that the buffer is only needed if actually used a second time
968
// help-file, i think
969
return new ByteArrayInputStream(useInputStream(fieldName, (InputStream) value));
970         }
971
972         if (VALUE_SHORTED.equals(value)) {
973             BlobCache blobs = parent.getBlobCache(fieldName);
974             String JavaDoc key = blobs.getKey(getNumber(), fieldName);
975             byte[] v = (byte[]) blobs.get(key);
976             if (v == null) {
977                 if (getSize(fieldName) < blobs.getMaxEntrySize()) {
978                     v = parent.mmb.getStorageManager().getBinaryValue(this, parent.getField(fieldName));
979                     if (log.isDebugEnabled()) {
980                         log.debug("Putting in blob cache " + key);
981                     }
982                     blobs.put(key, v);
983                 } else {
984                     log.debug("Too big for cache, requesting InputStream directly from storage");
985                     return parent.mmb.getStorageManager().getInputStreamValue(this, parent.getField(fieldName));
986                 }
987             } else {
988                 log.debug("Found in blob cache " + fieldName);
989             }
990             return new ByteArrayInputStream(v);
991         } else {
992             if (value instanceof byte[]) {
993                 return new ByteArrayInputStream((byte[]) value);
994             } else {
995                 // probably not a byte-array field, do something.
996
// this behavior is undefined!, don't depend on it.
997
return new ByteArrayInputStream(("" + value).getBytes());
998             }
999         }
1000    }
1001
1002
1003    /**
1004     * Get a value of a certain field.
1005     * The value is returned as an MMObjectNode.
1006     * If the field contains an Numeric value, the method
1007     * tries to obtrain the object with that number.
1008     * If it is a String, the method tries to obtain the object with
1009     * that alias. The only other possible values are those created by
1010     * certain virtual fields.
1011     * All remaining situations return <code>null</code>.
1012     * @param fieldName the name of the field who's data to return
1013     * @return the field's value as an <code>int</code>
1014     */

1015    public MMObjectNode getNodeValue(String JavaDoc fieldName) {
1016        if (fieldName == null || fieldName.equals(MMObjectBuilder.FIELD_NUMBER)) return this;
1017        Object JavaDoc value = getValue(fieldName);
1018        MMObjectNode res = null;
1019         if (value instanceof MMObjectNode) {
1020             res = (MMObjectNode) value;
1021         } else if (value instanceof Node) {
1022             Node node = (Node) value;
1023             if (node.isNew()) {
1024                 throw new UnsupportedOperationException JavaDoc("dropped tmpnodemanager...");
1025             } else if (value instanceof org.mmbase.bridge.implementation.VirtualNode) {
1026                 res = new VirtualNode(new org.mmbase.bridge.util.NodeMap(node));
1027             } else {
1028                 res = parent.getNode(node.getNumber());
1029              }
1030         } else if (value instanceof Number JavaDoc) {
1031             int nodenumber = ((Number JavaDoc)value).intValue();
1032             if (nodenumber != -1) {
1033                 res = parent.getNode(nodenumber);
1034             }
1035         } else if (value != null && !value.equals("")) {
1036             res = parent.getNode(value.toString());
1037         }
1038         return res;
1039    }
1040
1041    /**
1042     * Get a value of a certain field.
1043     * The value is returned as an int value. Values of non-int, numeric fields are converted if possible.
1044     * Booelan fields return 0 for false, 1 for true.
1045     * String fields are parsed to a number, if possible.
1046     * If a value is an MMObjectNode, its numberfield is returned.
1047     * All remaining field values return -1.
1048     * @param fieldName the name of the field who's data to return
1049     * @return the field's value as an <code>int</code>
1050     */

1051    public int getIntValue(String JavaDoc fieldName) {
1052        Object JavaDoc value = getValue(fieldName);
1053        if (value instanceof MMObjectNode) return ((MMObjectNode)value).getNumber();
1054        return Casting.toInt(value);
1055    }
1056
1057    /**
1058     * Get a value of a certain field.
1059     * The value is returned as an boolean value.
1060     * If the actual value is numeric, this call returns <code>true</code>
1061     * if the value is a positive, non-zero, value. In other words, values '0'
1062     * and '-1' are concidered <code>false</code>.
1063     * If the value is a string, this call returns <code>true</code> if
1064     * the value is "true" or "yes" (case-insensitive).
1065     * In all other cases (including calling byte fields), <code>false</code>
1066     * is returned.
1067     * Note that there is currently no basic MMBase boolean type, but some
1068     * <code>excecuteFunction</code> calls may return a Boolean result.
1069     *
1070     * @param fieldName the name of the field who's data to return
1071     * @return the field's value as an <code>int</code>
1072     */

1073    public boolean getBooleanValue(String JavaDoc fieldName) {
1074        return Casting.toBoolean(getValue(fieldName));
1075    }
1076
1077    /**
1078     * Get a value of a certain field.
1079     * The value is returned as an Integer value. Values of non-Integer, numeric fields are converted if possible.
1080     * Boolean fields return 0 for false, 1 for true.
1081     * String fields are parsed to a number, if possible.
1082     * All remaining field values return -1.
1083     * @param fieldName the name of the field who's data to return
1084     * @return the field's value as an <code>Integer</code>
1085     */

1086    public Integer JavaDoc getIntegerValue(String JavaDoc fieldName) {
1087        Object JavaDoc value = getValue(fieldName);
1088        if (value instanceof MMObjectNode) return new Integer JavaDoc(((MMObjectNode)value).getNumber());
1089        return Casting.toInteger(value);
1090    }
1091
1092    /**
1093     * Get a value of a certain field.
1094     * @see #getValue
1095     * @see Casting#toLong
1096     * @param fieldName the name of the field who's data to return
1097     * @return the field's value as a <code>long</code>
1098     */

1099    public long getLongValue(String JavaDoc fieldName) {
1100        Object JavaDoc value = getValue(fieldName);
1101        if (value instanceof MMObjectNode) return (long) ((MMObjectNode)value).getNumber();
1102        return Casting.toLong(value);
1103    }
1104
1105
1106    /**
1107     * Get a value of a certain field.
1108     * The value is returned as a float value. Values of non-float, numeric fields are converted if possible.
1109     * Boolean fields return 0 for false, 1 for true.
1110     * String fields are parsed to a number, if possible.
1111     * All remaining field values return -1.
1112     * @param fieldName the name of the field who's data to return
1113     * @return the field's value as a <code>float</code>
1114     */

1115    public float getFloatValue(String JavaDoc fieldName) {
1116        Object JavaDoc value = getValue(fieldName);
1117        if (value instanceof MMObjectNode) return (float) ((MMObjectNode)value).getNumber();
1118        return Casting.toFloat(value);
1119    }
1120
1121
1122    /**
1123     * Get a value of a certain field.
1124     * The value is returned as a double value. Values of non-double, numeric fields are converted if possible.
1125     * Boolean fields return 0 for false, 1 for true.
1126     * String fields are parsed to a number, if possible.
1127     * All remaining field values return -1.
1128     * @param fieldName the name of the field who's data to return
1129     * @return the field's value as a <code>double</code>
1130     */

1131    public double getDoubleValue(String JavaDoc fieldName) {
1132        Object JavaDoc value = getValue(fieldName);
1133        if (value instanceof MMObjectNode) return (double) ((MMObjectNode)value).getNumber();
1134        return Casting.toDouble(value);
1135    }
1136
1137    /**
1138     * Get a value of a certain field.
1139     * The value is returned as a Date value. Values of numeric fields are converted as if they were
1140     * time in seconds since 1/1/1970.
1141     * String fields are parsed to a date, if possible.
1142     * All remaining field values return -1.
1143     * @since MMBase-1.8
1144     * @param fieldName the name of the field who's data to return
1145     * @return the field's value as a <code>Date</code>
1146     */

1147    public Date getDateValue(String JavaDoc fieldName) {
1148        Object JavaDoc value = getValue(fieldName);
1149        org.mmbase.core.CoreField cf = getBuilder().getField(fieldName);
1150        if (cf != null && cf.getType() == Field.TYPE_NODE) {
1151            // cannot be handled by casting, because it would receive object-number and cannot make distinction with Nodes.
1152
return new Date(-1);
1153        }
1154        return Casting.toDate(value);
1155    }
1156
1157    /**
1158     * Get a value of a certain field.
1159     * The value is returned as a List value.
1160     * Strings are treated as comma-seperated value lists, and split into their component parts.
1161     * Values of other fields are returned as Lists of one object.
1162     * @since MMBase-1.8
1163     * @param fieldName the name of the field who's data to return
1164     * @return the field's value as a <code>List</code>
1165     */

1166    public List getListValue(String JavaDoc fieldName) {
1167        return Casting.toList(getValue(fieldName));
1168    }
1169
1170    /**
1171     * Returns the DBType of a field.
1172     * @param fieldName the name of the field which' type to return
1173     * @return the field's DBType
1174     */

1175    public int getDBType(String JavaDoc fieldName) {
1176        return parent.getDBType(fieldName);
1177    }
1178
1179    /**
1180     * Returns the DBState of a field.
1181     * @param fieldName the name of the field who's state to return
1182     * @return the field's DBState
1183     */

1184    public int getDBState(String JavaDoc fieldName) {
1185        if (parent != null) {
1186            return parent.getDBState(fieldName);
1187        } else {
1188            return Field.STATE_UNKNOWN;
1189        }
1190    }
1191
1192    /**
1193     * Return the names of all persistent fields that were changed.
1194     * Note that this is a direct reference. Changes (i.e. clearing the vector) will affect the node's status.
1195     * @return A Set containing Strings. The set is modifiable, and synchronized. Don't modify it though.
1196     */

1197    public Set getChanged() {
1198        return changed;
1199    }
1200
1201    /**
1202     * Tests whether one of the values of this node was changed since the last commit/insert.
1203     * @return <code>true</code> if changes have been made, <code>false</code> otherwise
1204     */

1205    public boolean isChanged() {
1206        return newContext != null || changed.size() > 0;
1207    }
1208
1209    /**
1210     * Clear the 'signal' Vector with the changed keys since last commit/insert.
1211     * Marks the node as 'unchanged'.
1212     * Does not affect the values of the fields, nor does it commit the node.
1213     * @return always <code>true</code>
1214     */

1215    public boolean clearChanged() {
1216        changed.clear();
1217        oldValues.clear();
1218        return true;
1219    }
1220
1221
1222    /**
1223     * Deletes the propertie cache for this node.
1224     * Forces a reload of the properties on next use.
1225     */

1226    public void delPropertiesCache() {
1227        synchronized(properties_sync) {
1228            properties = null;
1229        }
1230    }
1231
1232    public Map getValues() {
1233        return Collections.unmodifiableMap(values);
1234    }
1235    /**
1236     * @since MMBase-1.8
1237     */

1238    public Map getOldValues() {
1239        return Collections.unmodifiableMap(oldValues);
1240    }
1241
1242
1243
1244    /**
1245     * Return a the properties for this node.
1246     * @return the properties as a <code>Hashtable</code>
1247     */

1248    public Hashtable getProperties() {
1249        synchronized(properties_sync) {
1250            if (properties == null) {
1251                properties = new Hashtable();
1252                MMObjectBuilder bul = parent.mmb.getMMObject("properties");
1253                Enumeration e = bul.search("parent=="+getNumber());
1254                while (e.hasMoreElements()) {
1255                    MMObjectNode pnode = (MMObjectNode)e.nextElement();
1256                    String JavaDoc key = pnode.getStringValue("key");
1257                    properties.put(key, pnode);
1258                }
1259            }
1260        }
1261        return properties;
1262    }
1263
1264
1265    /**
1266     * Returns a specified property of this node.
1267     * @param key the name of the property to retrieve
1268     * @return the property object as a <code>MMObjectNode</code>
1269     */

1270    public MMObjectNode getProperty(String JavaDoc key) {
1271        MMObjectNode n;
1272        synchronized(properties_sync) {
1273            if (properties == null) {
1274                getProperties();
1275            }
1276            n=(MMObjectNode)properties.get(key);
1277        }
1278        if (n!=null) {
1279            return n;
1280        } else {
1281            return null;
1282        }
1283    }
1284
1285
1286    /**
1287     * Sets a specified property for this node.
1288     * This method does not commit anything - it merely updates the node's propertylist.
1289     * @param node the property object as a <code>MMObjectNode</code>
1290     */

1291    public void putProperty(MMObjectNode node) {
1292        synchronized(properties_sync) {
1293            if (properties==null) {
1294                getProperties();
1295            }
1296            properties.put(node.getStringValue("key"),node);
1297        }
1298    }
1299
1300    /**
1301     * Return the GUI indicator for this node.
1302     * The GUI indicator is a string that represents the contents of this node.
1303     * By default it is the string-representation of the first non-system field of the node.
1304     * Individual builders can alter this behavior.
1305     * @return the GUI iddicator as a <code>String</code>
1306     */

1307    public String JavaDoc getGUIIndicator() {
1308        if (parent!=null) {
1309            return parent.getGUIIndicator(this);
1310        } else {
1311            log.error("MMObjectNode -> can't get parent");
1312            return "problem";
1313        }
1314    }
1315
1316    /**
1317     * Return the buildername of this node
1318     * @return the builder table name
1319     */

1320    public String JavaDoc getName() {
1321        return parent.getTableName();
1322    }
1323
1324    /**
1325     * Delete the relation cache for this node.
1326     * This means it will be reloaded from the database/storage on next use.
1327     */

1328    public void delRelationsCache() {
1329        delRelationsCache(new Integer JavaDoc(getNumber()));
1330    }
1331
1332    /**
1333     * Delete the relation cache for this node.
1334     * This means it will be reloaded from the database/storage on next use.
1335     * @param number nodenumber
1336     */

1337    public static void delRelationsCache(Integer JavaDoc number) {
1338        relationsCache.remove(number);
1339    }
1340
1341    /**
1342     * Returns whether this node has relations.
1343     * This includes unidirection relations which would otherwise not be counted.
1344     * @return <code>true</code> if any relations exist, <code>false</code> otherwise.
1345     */

1346    public boolean hasRelations() {
1347        // return getRelationCount()>0;
1348
return parent.mmb.getInsRel().hasRelations(getNumber());
1349    }
1350
1351    /**
1352     * Return all the relations of this node.
1353     * Use only to delete the relations of a node.
1354     * Note that this returns the nodes describing the relation - not the nodes 'related to'.
1355     * @return An <code>Enumeration</code> containing the nodes
1356     */

1357    public Enumeration getAllRelations() {
1358        Vector allrelations=parent.mmb.getInsRel().getAllRelationsVector(getNumber());
1359        if (allrelations!=null) {
1360            return allrelations.elements();
1361        } else {
1362            return null;
1363        }
1364    }
1365
1366    /**
1367     * Return the relations of this node.
1368     * Note that this returns the nodes describing the relation - not the nodes 'related to'.
1369     *
1370     *
1371     * XXX: return type of this method makes it impossible to make MMObjectNode implements Node, perhaps it needs change
1372     *
1373     * @return An <code>Enumeration</code> containing the nodes
1374     */

1375    public Enumeration getRelations() {
1376        List relations = getRelationNodes();
1377        if (relations != null) {
1378            return Collections.enumeration(relations);
1379        } else {
1380            return null;
1381        }
1382    }
1383
1384    /**
1385     * @since MMBase-1.7
1386     * @scope public?
1387     */

1388    protected List getRelationNodes() {
1389        Integer JavaDoc number = new Integer JavaDoc(getNumber());
1390        List relations;
1391        if (! relationsCache.contains(number)) {
1392            relations = parent.getRelations_main(getNumber());
1393            relationsCache.put(number, relations);
1394
1395        } else {
1396            relations = (List) relationsCache.get(number);
1397        }
1398        return relations;
1399    }
1400
1401    /**
1402     * Remove the relations of the node.
1403     */

1404    public void removeRelations() {
1405        parent.removeRelations(this);
1406    }
1407
1408    /**
1409     * Returns the number of relations of this node.
1410     * @return An <code>int</code> indicating the number of nodes found
1411     */

1412    public int getRelationCount() {
1413        List relations = getRelationNodes();
1414        if (relations!=null) {
1415            return relations.size();
1416        } else {
1417            return 0;
1418        }
1419    }
1420
1421
1422    /**
1423     * Return the relations of this node, filtered on a specified type.
1424     * Note that this returns the nodes describing the relation - not the nodes 'related to'.
1425     * @param otype the 'type' of relations to return. The type identifies a relation (InsRel-derived) builder, not a reldef object.
1426     * @return An <code>Enumeration</code> containing the nodes
1427     */

1428    public Enumeration getRelations(int otype) {
1429        Enumeration e = getRelations();
1430        Vector result=new Vector();
1431        if (e!=null) {
1432            while (e.hasMoreElements()) {
1433                MMObjectNode tnode=(MMObjectNode)e.nextElement();
1434                if (tnode.getOType()==otype) {
1435                    result.addElement(tnode);
1436                }
1437            }
1438        }
1439        return result.elements();
1440    }
1441
1442    /**
1443     * Return the relations of this node, filtered on a specified type.
1444     * Note that this returns the nodes describing the relation - not the nodes 'related to'.
1445     * @param wantedtype the 'type' of relations to return. The type identifies a relation (InsRel-derived) builder, not a reldef object.
1446     * @return An <code>Enumeration</code> containing the nodes
1447     */

1448    public Enumeration getRelations(String JavaDoc wantedtype) {
1449        int otype=parent.mmb.getTypeDef().getIntValue(wantedtype);
1450        if (otype!=-1) {
1451            return getRelations(otype);
1452        }
1453        return null;
1454    }
1455
1456    /**
1457     * Return the number of relations of this node, filtered on a specified type.
1458     * @param wt the 'type' of related nodes (NOT the relations!).
1459     * @return An <code>int</code> indicating the number of nodes found
1460     */

1461    public int getRelationCount(String JavaDoc wt) {
1462        int count = 0;
1463        MMObjectBuilder wantedType = parent.mmb.getBuilder(wt);
1464        if (wantedType != null) {
1465            List relations = getRelationNodes();
1466            if (relations != null) {
1467                for(Enumeration e= Collections.enumeration(relations); e.hasMoreElements();) {
1468                    MMObjectNode tnode=(MMObjectNode)e.nextElement();
1469                    int relation_number =tnode.getIntValue("snumber");
1470                    int nodetype =0;
1471
1472                    // bugfix #6432: marcel: determine source of relation, get type, display
1473
// error when nodetype is determined to be -1, which is a possible wrongly inserted relation
1474

1475                    if (relation_number==getNumber()) {
1476                        relation_number = tnode.getIntValue("dnumber");
1477                        nodetype = parent.getNodeType(relation_number);
1478                    } else {
1479                        nodetype = parent.getNodeType(relation_number);
1480                    }
1481
1482                    // Display situation where snumber or dnumber from a relation-node does not seem to
1483
// exsist in the database. This can be fixed by mannually removing the node out of the insrel-table
1484
if(nodetype==-1) {
1485                        log.warn("Warning: relation_node("+tnode.getNumber()+") has a possible removed relation_number("+relation_number+"), manually check its consistency!");
1486                    }
1487
1488                    MMObjectBuilder nodeType = parent.mmb.getBuilder(parent.mmb.getTypeDef().getValue(nodetype));
1489                    if (nodeType!=null && (nodeType.equals(wantedType) || nodeType.isExtensionOf(wantedType))) {
1490                        count++;
1491                    }
1492                }
1493            }
1494        } else {
1495            log.warn("getRelationCount is requested with an invalid Builder name (otype "+wt+" does not exist)");
1496        }
1497        return count;
1498    }
1499
1500
1501    /**
1502     * Return the age of the node, determined using the daymarks builder.
1503     * @return the age in days, or 0 if unknown (daymarks builder not present)
1504     */

1505    public int getAge() {
1506        DayMarkers dayMarkers = ((DayMarkers) parent.mmb.getBuilder("daymarks"));
1507        if (dayMarkers == null) return 0;
1508        return dayMarkers.getAge(getNumber());
1509    }
1510
1511    /**
1512     * Sends a field-changed signal.
1513     * @param fieldName the name of the changed field.
1514     * @return always <code>true</code>
1515     */

1516    public boolean sendFieldChangeSignal(String JavaDoc fieldName) {
1517        return parent.sendFieldChangeSignal(this,fieldName);
1518    }
1519
1520    /**
1521     * Sets the node's alias.
1522     * The code only sets a (memory) property, it does not actually add the alias to the database.
1523     * Only works for uninserted Nodes. So this is actually only used for application import.
1524     * No need to use this. Use {@link MMObjectBuilder#createAlias}.
1525     */

1526    public void setAlias(String JavaDoc alias) {
1527        if (aliases == null) aliases = new HashSet();
1528        synchronized(aliases) {
1529            aliases.add(alias);
1530        }
1531    }
1532
1533    /**
1534     * Returns the node's alias.
1535     * Does not support multiple aliases.
1536     * @return the new aliases as a <code>Set</code>
1537     */

1538    void useAliases() {
1539        if (aliases != null) {
1540            synchronized(aliases) {
1541                if (getNumber() <= 0) {
1542                    log.error("Trying to set aliases for uncommited node!!");
1543                    return;
1544                }
1545                Iterator it = aliases.iterator();
1546                while(it.hasNext()) {
1547                    try {
1548                        String JavaDoc alias = (String JavaDoc) it.next();
1549                        parent.createAlias(getNumber(), alias, getStringValue("owner"));
1550                    } catch (org.mmbase.storage.StorageException se) {
1551                        log.error(se);
1552                    }
1553                }
1554                aliases.clear();
1555            }
1556        }
1557    }
1558
1559
1560    /**
1561     * Get all related nodes. The returned nodes are not the
1562     * nodes directly attached to this node (the relation nodes) but the nodes
1563     * attached to the relation nodes of this node.
1564     *
1565     * XXX: return type of this method make it impossible to make MMObjectNode implements Node, perhaps it needs change
1566     *
1567     * @return a <code>Vector</code> containing <code>MMObjectNode</code>s
1568     */

1569    public Vector getRelatedNodes() {
1570        return getRelatedNodes("object", null, RelationStep.DIRECTIONS_EITHER);
1571    }
1572
1573    /**
1574     * Makes number -> MMObjectNode of a vector of MMObjectNodes.
1575     * @since MMBase-1.6.2
1576     */

1577    private Map makeMap(List v) {
1578        Map result = new HashMap();
1579        Iterator i = v.iterator();
1580        while(i.hasNext()) {
1581            MMObjectNode node = (MMObjectNode) i.next();
1582            result.put(node.getStringValue(MMObjectBuilder.FIELD_NUMBER), node);
1583        }
1584        return result;
1585    }
1586
1587
1588    /**
1589     * Get the related nodes of a certain type. The returned nodes are not the
1590     * nodes directly attached to this node (the relation nodes) but the nodes
1591     * attached to the relation nodes of this
1592     *
1593     * XXX: return type of this method make it impossible to make MMObjectNode implements Node, perhaps it needs change
1594     *
1595     * @param type the type of objects to be returned
1596     * @return a <code>Vector</code> containing <code>MMObjectNode</code>s
1597     */

1598    public Vector getRelatedNodes(String JavaDoc type) {
1599        if (log.isDebugEnabled()) {
1600            log.debug("Getting related nodes of " + this + " of type " + type);
1601        }
1602
1603        if(InsRel.usesdir) {
1604            return getRelatedNodes(type, RelationStep.DIRECTIONS_BOTH);
1605        } else {
1606            //
1607
// determine related nodes
1608
Map source = makeMap(getRelatedNodes(type, RelationStep.DIRECTIONS_SOURCE));
1609            Map destin = makeMap(getRelatedNodes(type, RelationStep.DIRECTIONS_DESTINATION));
1610
1611            if (log.isDebugEnabled()) {
1612                log.debug("source("+source.size()+") - destin("+destin.size()+")");
1613            }
1614            // remove duplicates (can happen if multirel is being used when no dir on insrel exists)
1615
destin.putAll(source);
1616            return new Vector(destin.values());
1617        }
1618    }
1619
1620    /**
1621     * If you query from this_node_type(type) (typex, insrel, typey where typex == typey) {
1622     * if the insrel table is directional, use the multirelations.SEARCH_BOTH
1623     * if the insrel table is not directional, use the multirelations.SEARCH_SOURCE + multirelations.SEARCH_DESTINATION
1624     * }
1625     * Otherwise the SEARCH_BOTH will result in an OR on insrel which will never return in
1626     * (huge) databases.
1627     * @param type the type of teh realted node to return
1628     * @param search_type the type of directionality to use
1629     * @since MMBase-1.6.3
1630     */

1631    public Vector getRelatedNodes(String JavaDoc type, int search_type) {
1632        return getRelatedNodes(type, "insrel", search_type);
1633    }
1634
1635    /**
1636     * If you query from this_node_type(type) (typex, insrel, typey where typex == typey) {
1637     * if the insrel table is directional, use the multirelations.SEARCH_BOTH
1638     * if the insrel table is not directional, use the multirelations.SEARCH_SOURCE + multirelations.SEARCH_DESTINATION
1639     * }
1640     * Otherwise the SEARCH_BOTH will result in an OR on insrel which will never return in
1641     * (huge) databases.
1642     * @param type the type of teh realted node to return
1643     * @param role the role of the relation (null if no role specified)
1644     * @param search_type the type of directionality to use
1645     * @since MMBase-1.6.3
1646     */

1647    public Vector getRelatedNodes(String JavaDoc type, String JavaDoc role, int search_type) {
1648        Vector result = null;
1649
1650        MMObjectBuilder builder = parent.mmb.getBuilder(type);
1651
1652        // example: we want a thisnode.relatedNodes(mediaparts) where mediaparts are of type
1653
// audioparts and videoparts. This method will return the real nodes (thus of type audio/videoparts)
1654
// when asked to get nodes of type mediaparts.
1655
//
1656
// - get a list of virtual nodes from a multilevel("this.parent.name, type") ordered on otype
1657
// (this will return virtual audio- and/or videoparts ordered on their *real* parent)
1658
// - construct a list of nodes for each parentbuilder seperately
1659
// - ask the parentbuilder for each list of virtual nodes to get a list of the real nodes
1660

1661
1662        if( builder != null ) {
1663
1664            ClusterBuilder clusterBuilder = parent.mmb.getClusterBuilder();
1665
1666
1667            // multilevel from table this.parent.name -> type
1668
List tables = new ArrayList();
1669            tables.add(parent.getTableName() + "1");
1670            if (role != null) {
1671                tables.add(role);
1672            }
1673            tables.add(type + "2");
1674
1675            // return type.number (and otype for sorting)
1676
List fields = new ArrayList();
1677            fields.add(type + "2.number");
1678            fields.add(type + "2.otype");
1679
1680            // order list UP
1681
List directions = new ArrayList();
1682            directions.add("UP");
1683
1684            // and order on otype
1685
List ordered = new ArrayList();
1686            ordered.add(type + "2.otype");
1687
1688            List snodes = new ArrayList();
1689            snodes.add("" + getNumber());
1690
1691            SearchQuery query = clusterBuilder.getMultiLevelSearchQuery(snodes, fields, "NO", tables, null, ordered, directions, search_type);
1692            List v = (List) relatedCache.get(query);
1693            if (v == null) {
1694                try {
1695                    v = clusterBuilder.getClusterNodes(query);
1696                    relatedCache.put(query, v);
1697                } catch (SearchQueryException sqe) {
1698                    log.error(sqe.toString());
1699                    v = null;
1700                }
1701            }
1702            if(v == null) {
1703                result = new Vector();
1704            } else {
1705                result = new Vector(getRealNodes(v, type + "2"));
1706            }
1707        } else {
1708            log.error("This type(" + type + ") is not a valid buildername!");
1709            result = new Vector(); // return empty vector
1710
}
1711
1712        if (log.isDebugEnabled()) {
1713            log.debug("related("+parent.getTableName()+"("+getNumber()+")) -> "+type+" = size("+result.size()+")");
1714        }
1715
1716        return result;
1717    }
1718
1719    /**
1720     * Loop through the virtuals vector, group all same nodes based on parent and fetch the real nodes from those parents
1721     *
1722     * @param List of virtual nodes (only type.number and type.otype fields are set)
1723     * @param type, needed to retreive the otype, which is set in node as type + ".otype"
1724     * @returns List of real nodes
1725     *
1726     * @see getRelatedNodes(String type)
1727     * @since MMBase-1.6.2
1728     */

1729    private List getRealNodes(List virtuals, String JavaDoc type) {
1730
1731        log.debug("Getting real nodes");
1732        List result = new ArrayList();
1733
1734        MMObjectNode node = null;
1735        MMObjectNode convert = null;
1736        Iterator i = virtuals.iterator();
1737        List list = new ArrayList();
1738        int otype = -1;
1739        int ootype = -1;
1740
1741        List virtualNumbers = new ArrayList();
1742
1743        // fill the list
1744
while(i.hasNext()) {
1745            node = (MMObjectNode)i.next();
1746            Integer JavaDoc number = node.getIntegerValue(type + ".number");
1747            if (!virtualNumbers.contains(number)) {
1748                virtualNumbers.add(number);
1749
1750                otype = node.getIntValue(type + ".otype");
1751
1752                // convert the nodes of type ootype to real numbers
1753
if(otype != ootype) {
1754                    // if we have nodes return real values
1755
if(ootype != -1) {
1756                        result.addAll(getRealNodesFromBuilder(list, ootype));
1757                        list = new ArrayList();
1758                    }
1759                    ootype = otype;
1760                }
1761                // convert current node type.number and type.otype to number and otype
1762
String JavaDoc builderName = parent.mmb.getTypeDef().getValue(otype);
1763                if (builderName == null) {
1764                    log.warn("Could not find builder of node " + node.getNumber() + " taking 'object'");
1765                    builderName = "object";
1766                    otype = parent.mmb.getBuilder(builderName).getObjectType();
1767                }
1768
1769                convert = new MMObjectNode(parent.mmb.getBuilder(builderName), false);
1770                // parent needs to be set or else mmbase does nag nag nag on a setValue()
1771
convert.setValue(MMObjectBuilder.FIELD_NUMBER, node.getValue(type + ".number"));
1772                convert.setValue(MMObjectBuilder.FIELD_OBJECT_TYPE, ootype);
1773                list.add(convert);
1774            }
1775            // first and only list or last list, return real values
1776
if(!i.hasNext()) {
1777                // log.debug("subconverting last "+list.size()+" nodes of type("+otype+")");
1778
result.addAll(getRealNodesFromBuilder(list, otype));
1779            }
1780        }
1781
1782        // check that we didnt loose any nodes
1783
if(virtualNumbers.size() != result.size()) {
1784            log.error("We lost a few nodes during conversion from virtualnodes(" + virtuals.size() + ") to realnodes(" + result.size() + ")");
1785            StringBuffer JavaDoc vNumbers = new StringBuffer JavaDoc();
1786            for (int j = 0; j < virtualNumbers.size(); j++) {
1787                vNumbers.append(virtualNumbers.get(j)).append(" ");
1788            }
1789            log.error("Virtual node numbers: " + vNumbers.toString());
1790            StringBuffer JavaDoc rNumbers = new StringBuffer JavaDoc();
1791            for (int j = 0; j < result.size(); j++) {
1792                int resultNumber = ((MMObjectNode) result.get(j)).getIntValue("number");
1793                rNumbers.append(resultNumber).append(" ");
1794            }
1795            log.error("Real node numbers: " + rNumbers.toString());
1796        }
1797
1798        return result;
1799    }
1800
1801    /**
1802     * Upgrade a certain list of MMObectNodes to the right type.
1803     * @since MMBase-1.6.2
1804     */

1805    private List getRealNodesFromBuilder(List list, int otype) {
1806        List result = new ArrayList();
1807        String JavaDoc name = parent.mmb.getTypeDef().getValue(otype);
1808        if(name != null) {
1809            MMObjectBuilder rparent = parent.mmb.getBuilder(name);
1810            if(rparent != null) {
1811                result.addAll(rparent.getNodes(list));
1812            } else {
1813                log.error("This otype(" + otype + ") does not denote a valid typedef-name(" + name + ")!");
1814            }
1815        } else {
1816            log.error("This otype(" + otype + ") gives no name(" + name + ") from typedef!");
1817        }
1818        return result;
1819    }
1820
1821
1822    public int getByteSize() {
1823        return getByteSize(new SizeOf());
1824    }
1825
1826    public int getByteSize(SizeOf sizeof) {
1827        return
1828            sizeof.sizeof(values) +
1829            sizeof.sizeof(oldValues) +
1830            sizeof.sizeof(sizes) +
1831            sizeof.sizeof(properties) +
1832            sizeof.sizeof(changed) +
1833            12 * SizeOf.SZ_REF;
1834    }
1835
1836
1837    /**
1838     * @since MMBase-1.6.2
1839     */

1840    public int hashCode() {
1841        if (parent != null) {
1842            return parent.hashCode(this);
1843        } else {
1844            return super.hashCode();
1845        }
1846    }
1847
1848    /**
1849     * @since MMBase-1.6.2
1850     */

1851    public boolean equals(Object JavaDoc o) {
1852        if (o instanceof MMObjectNode) {
1853            MMObjectNode n = (MMObjectNode) o;
1854            if (parent != null) {
1855                return parent.equals(this, n);
1856            } else {
1857                return defaultEquals(n);
1858            }
1859        }
1860        return false;
1861    }
1862    /**
1863     * @since MMBase-1.6.2
1864     */

1865    public boolean defaultEquals(MMObjectNode n) {
1866        /*
1867          if (getNumber() >= 0) { // we know when real nodes are equal
1868          return n.getNumber() == getNumber();
1869          } else { // I don't know about others
1870          return super.equals(n); // compare as objects.
1871          }
1872        */

1873        return super.equals(n); // compare as objects.
1874
}
1875
1876    /**
1877     * Custom serialize method for MMObjectNode. The main reason this method exists is
1878     * that the builder for an object will not be serialized, but the tablename for
1879     * the object will be saved instead. During deserialization the builder will
1880     * be recovered using that name.
1881     * @since MMBase-1.8.0
1882     */

1883    private void writeObject(java.io.ObjectOutputStream JavaDoc out) throws IOException {
1884        out.writeObject(oldValues);
1885        out.writeObject(values);
1886        out.writeObject(sizes);
1887        out.writeBoolean(initializing);
1888        out.writeObject(properties);
1889        out.writeObject(changed);
1890
1891        // Save parent and builder by name, not by object
1892
if (parent == null) {
1893            out.writeObject(null);
1894        } else {
1895            out.writeObject(parent.getTableName());
1896        }
1897        if (builder == null) {
1898            out.writeObject(null);
1899        } else {
1900            out.writeObject(builder.getTableName());
1901        }
1902        out.writeBoolean(isNew);
1903        out.writeObject(aliases);
1904        out.writeObject(newContext);
1905    }
1906
1907    /**
1908     * Custom deserialize method for MMObjectNode. The main reason this method exists is
1909     * that the builder for an object will not be serialized, but the tablename for
1910     * the object will be saved instead. During deserialization the builder will
1911     * be recovered using that name.
1912     * @since MMBase-1.8.0
1913     */

1914    private void readObject(java.io.ObjectInputStream JavaDoc in) throws IOException, ClassNotFoundException JavaDoc {
1915        oldValues = (Map)in.readObject();
1916        values = (Map)in.readObject();
1917        sizes = (Map)in.readObject();
1918        initializing = in.readBoolean();
1919        properties = (Hashtable)in.readObject();
1920        changed = (Set)in.readObject();
1921
1922        // Retrieve parent and builder by name, not by object
1923
String JavaDoc parentName = (String JavaDoc)in.readObject();
1924        if (parentName != null) {
1925            parent = MMBase.getMMBase().getBuilder(parentName);
1926        }
1927        String JavaDoc builderName = (String JavaDoc)in.readObject();
1928        if (builderName != null) {
1929            builder = MMBase.getMMBase().getBuilder(builderName);
1930        }
1931        isNew = in.readBoolean();
1932        aliases = (Set)in.readObject();
1933        newContext = (String JavaDoc)in.readObject();
1934    }
1935}
1936
Popular Tags