KickJava   Java API By Example, From Geeks To Geeks.

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


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.io.File JavaDoc;
13 import java.net.URLEncoder JavaDoc;
14 import java.text.DateFormat JavaDoc;
15 import java.text.NumberFormat JavaDoc;
16 import java.util.*;
17
18 import org.mmbase.bridge.*;
19
20 import org.mmbase.cache.*;
21
22 import org.mmbase.datatypes.DataTypeCollector;
23
24 import org.mmbase.module.corebuilders.*;
25
26 import org.mmbase.core.*;
27 import org.mmbase.core.event.*;
28 import org.mmbase.core.util.Fields;
29 import org.mmbase.core.util.StorageConnector;
30
31 import org.mmbase.datatypes.DataType;
32
33 import org.mmbase.storage.StorageException;
34 import org.mmbase.storage.StorageNotFoundException;
35 import org.mmbase.storage.search.*;
36 import org.mmbase.storage.search.implementation.*;
37
38 import org.mmbase.util.*;
39 import org.mmbase.util.functions.*;
40 import org.mmbase.util.logging.Logger;
41 import org.mmbase.util.logging.Logging;
42
43 /**
44  * This class is the base class for all builders.
45  * It offers a list of routines which are useful in maintaining the nodes in the MMBase
46  * object cloud.
47  * <br />
48  * Builders are the core of the MMBase system. They create, delete and search the MMObjectNodes.
49  * Most manipulations concern nodes of that builders type. However, a number of retrieval routines extend
50  * beyond a builders scope and work on the cloud in general, allowing some ease in retrieval of nodes.
51  * The basic routines in this class can be extended to handle more specific demands for nodes.
52  * Most of these 'extended builders' will be stored in mmbase.org.builders or mmbase.org.corebuilders.
53  * Examples include relation builders or builders for handling binary data such as images.
54  * The various builders are registered by the 'TypeDef' builder class (one of the core builders, itself
55  * an extension of this class).
56  *
57  * @author Daniel Ockeloen
58  * @author Rob Vermeulen
59  * @author Pierre van Rooden
60  * @author Eduard Witteveen
61  * @author Johannes Verelst
62  * @author Rob van Maris
63  * @author Michiel Meeuwissen
64  * @author Ernst Bunders
65  * @version $Id: MMObjectBuilder.java,v 1.391.2.2 2006/10/17 12:08:47 nklasens Exp $
66  */

67 public class MMObjectBuilder extends MMTable implements NodeEventListener, RelationEventListener {
68
69     /**
70      * Name of the field containing the object number, which uniquely identifies the node.
71      * @since MMBase-1.8
72      */

73     public static final String JavaDoc FIELD_NUMBER = "number";
74
75     /**
76      * Name of the field containing the owner. The owner field is used for security implementations.
77      * @since MMBase-1.8
78      */

79     public static final String JavaDoc FIELD_OWNER = "owner";
80
81     /**
82      * Name of the field containing the object type number. This refers to an entry in the 'typedef' builder table.
83      * @since MMBase-1.8
84      */

85     public static final String JavaDoc FIELD_OBJECT_TYPE = "otype";
86
87     /**
88      * @since MMBase-1.8
89      */

90     public static final String JavaDoc TMP_FIELD_NUMBER = "_number";
91     public static final String JavaDoc TMP_FIELD_EXISTS = "_exists";
92
93     /**
94      * Default (system) owner name for the owner field.
95      * @since MMBase-1.8
96      */

97     public static final String JavaDoc SYSTEM_OWNER = "system";
98
99     /** Default size of the temporary node cache */
100     public final static int TEMPNODE_DEFAULT_SIZE = 1024;
101
102     /** Default replacements for method getHTML() */
103     public final static String JavaDoc DEFAULT_ALINEA = "<br />&#160;<br />";
104     public final static String JavaDoc DEFAULT_EOL = "<br />";
105
106     public final static int EVENT_TYPE_LOCAL = 0;
107     public final static int EVENT_TYPE_REMOTE = 1;
108
109     /**
110      * Parameters for the age function
111      * @since MMBase-1.7
112      */

113     public final static Parameter[] AGE_PARAMETERS = {};
114
115     /**
116      * Collection for temporary nodes,
117      * Used by the Temporarynodemanager when working with transactions
118      * The default size is 1024.
119      * @duplicate use Cache object instead
120      * @scope protected
121      */

122     public static Map temporaryNodes = new Hashtable(TEMPNODE_DEFAULT_SIZE);
123
124     /**
125      * Default output when no data is available to determine a node's GUI description
126      */

127     public static final String JavaDoc GUI_INDICATOR = "no info";
128
129     /**
130      * The cache for all blobs.
131      * @since 1.8.0
132      */

133     protected static BlobCache genericBlobCache = new BlobCache(200) {
134             public String JavaDoc getName() { return "GenericBlobCache"; }
135         };
136
137     static {
138         genericBlobCache.putCache();
139     }
140
141     /**
142      * The cache that contains the X last requested nodes
143      */

144     protected static org.mmbase.cache.NodeCache nodeCache = org.mmbase.cache.NodeCache.getCache();
145
146     /**
147      * Determines whether the cache is locked.
148      * A locked cache can be read, and nodes can be removed from it (allowing it to
149      * clean invalid nodes), but nodes cannot be added.
150      * Needed for committing nodes from transactions.
151      */

152     private static int cacheLocked = 0;
153
154     private static final Logger log = Logging.getLoggerInstance(MMObjectBuilder.class);
155
156     /**
157      * The string that can be used inside the builder.xml as property,
158      * to define the maximum number of nodes to return.
159      */

160     private static String JavaDoc MAX_NODES_FROM_QUERY_PROPERTY = "max-nodes-from-query";
161
162     /**
163      * The string that can be used inside the builder.xml as property,
164      * to set whether the builder broadcasts changes to nodes to eventlisteners.
165      */

166     private static String JavaDoc BROADCAST_CHANGES_PROPERTY = "broadcast-changes";
167
168     /**
169      * Description of the builder in the currently selected language
170      * Not that the first time the builder is created, this value is what is stored in the TypeDef table.
171      * @scope protected
172      */

173     public String JavaDoc description="Base Object";
174
175     /**
176      * Descriptions of the builder per language
177      * Can be set with the &lt;descriptions&gt; tag in the xml builder file.
178      * @scope protected
179      */

180     public Hashtable descriptions;
181
182     /**
183      * The default search age for this builder.
184      * Used for intializing editor search forms (see HtmlBase)
185      * Default value is 31. Can be changed with the &lt;searchage&gt; tag in the xml builder file.
186      * @scope protected
187      */

188     public String JavaDoc searchAge="31";
189
190     /**
191      * Determines whether changes to this builder need be broadcast to other known mmbase servers.
192      */

193     protected boolean broadCastChanges = true;
194
195     /**
196      * Internal (instance) version number of this builder.
197      */

198     protected long internalVersion = -1;
199
200     /**
201      * The current builder's object type
202      * Retrieved from the TypeDef builder.
203      */

204     protected int oType = -1;
205
206     /**
207      * Maintainer information for builder registration
208      * Set with &lt;builder maintainer="mmbase.org" version="0"&gt; in the xml builder file
209      */

210     String JavaDoc maintainer = "mmbase.org";
211
212     /** Collections of (GUI) names (singular) for the builder's objects, divided by language
213      */

214     Hashtable singularNames;
215
216     /** Collections of (GUI) names (plural) for the builder's objects, divided by language
217      */

218     Hashtable pluralNames;
219
220     /**
221      * Full filename (path + buildername + ".xml") where we loaded the builder from
222      * It is relative from the '/builders/' subdir
223      */

224     String JavaDoc xmlPath = "";
225
226     /**
227      * Parameters for the GUI function
228      * @since MMBase-1.7
229      */

230     public final static Parameter[] GUI_PARAMETERS = {
231         Parameter.FIELD,
232         Parameter.LANGUAGE,
233         new Parameter("session", String JavaDoc.class),
234         Parameter.RESPONSE,
235         Parameter.REQUEST,
236         Parameter.LOCALE,
237         new Parameter("stringvalue", String JavaDoc.class)
238         //new Parameter("length", Integer.class),
239
// field, language, session, response, request) Returns a (XHTML) gui representation of the node (if field is '') or of a certain field. It can take into consideration a http session variable name with loging information and a language");
240

241     };
242     /**
243      * The famous GUI function as a function object.
244      * @since MMBase-1.8
245      */

246     protected Function guiFunction = new NodeFunction("gui", GUI_PARAMETERS, ReturnType.STRING) {
247             protected Object JavaDoc getFunctionValue(Node node, Parameters parameters) {
248                 if (log.isDebugEnabled()) {
249                     log.debug("GUI of builder with " + parameters);
250                 }
251                 String JavaDoc fieldName = (String JavaDoc) parameters.get(Parameter.FIELD);
252                 if (fieldName != null && (! fieldName.equals("")) && parameters.get("stringvalue") == null) {
253                     if (node.getSize(fieldName) < 2000) {
254                         parameters.set("stringvalue", node.getStringValue(fieldName));
255                     }
256                 }
257                 MMObjectNode n = (MMObjectNode) parameters.get(Parameter.CORENODE);
258                 return MMObjectBuilder.this.getGUIIndicator(n, parameters);
259             }
260         };
261     {
262         addFunction(guiFunction);
263     }
264     /**
265      * Parameters constants for the NodeFunction {@link #wrapFunction}.
266      * @since MMBase-1.8
267      */

268     protected final static Parameter[] WRAP_PARAMETERS = {
269         new Parameter(Parameter.FIELD, true),
270         new Parameter("length", Number JavaDoc.class, new Integer JavaDoc(20))
271     };
272
273     /**
274      * This function wraps the text of a node's field and returns the result as a String.
275      * It takes as parameters a fieldname, the line length to wrap, and the Node containing the data.
276      * This function can be called through the function framework.
277      * @since MMBase-1.8
278      */

279     protected Function wrapFunction = new NodeFunction("wrap", WRAP_PARAMETERS, ReturnType.STRING) {
280             {
281                 setDescription("This function wraps a field, word-by-word. You can use this, e.g. in <pre>-tags. This functionality should be available as an 'escaper', and this version should now be considered an example.");
282             }
283             public Object JavaDoc getFunctionValue(Node node, Parameters parameters) {
284                 String JavaDoc val = node.getStringValue(parameters.getString(Parameter.FIELD));
285                 Number JavaDoc wrappos = (Number JavaDoc) parameters.get("length");
286                 return wrap(val, wrappos.intValue());
287             }
288         };
289     {
290         addFunction(wrapFunction);
291     }
292
293     /**
294      * Every Function Provider provides least the 'getFunctions' function, which returns a Set of all functions which it provides.
295      * This is overridden from FunctionProvider, because this one needs to be (also) a NodeFunction
296      * @since MMBase-1.8
297      */

298     protected Function getFunctions = new NodeFunction("getFunctions", Parameter.EMPTY, ReturnType.SET) {
299             {
300                 setDescription("The 'getFunctions' returns a Map of al Function object which are available on this FunctionProvider");
301             }
302             public Object JavaDoc getFunctionValue(Node node, Parameters parameters) {
303                 return MMObjectBuilder.this.getFunctions(getCoreNode(MMObjectBuilder.this, node));
304             }
305             public Object JavaDoc getFunctionValue(Parameters parameters) {
306                 Node node = (Node) parameters.get(Parameter.NODE);
307                 if (node == null) {
308                     return MMObjectBuilder.this.getFunctions();
309                 } else {
310                     return MMObjectBuilder.this.getFunctions(getCoreNode(MMObjectBuilder.this, node));
311                 }
312             }
313         };
314     {
315         addFunction(getFunctions);
316     }
317
318     /**
319      * The info-function is a node-function and a builder-function. Therefore it is defined as a node-function, but also overidesd getFunctionValue(Parameters).
320      * @since MMBase-1.8
321      */

322     protected Function infoFunction = new NodeFunction("info", new Parameter[] { new Parameter("function", String JavaDoc.class) }, ReturnType.UNKNOWN) {
323             {
324                 setDescription("Returns information about available functions");
325             }
326             protected Object JavaDoc getFunctionValue(Collection functions, Parameters parameters) {
327                 String JavaDoc function = (String JavaDoc) parameters.get("function");
328                 if (function == null || function.equals("")) {
329                     Map info = new HashMap();
330                     Iterator i = functions.iterator();
331                     while (i.hasNext()) {
332                         Function f = (Function) i.next();
333                         info.put(f.getName(), f.getDescription());
334                     }
335                     return info;
336                 } else {
337                     Function func = getFunction(function);
338                     if (func == null) return "No such function " + function;
339                     return func.getDescription();
340                 }
341             }
342             public Object JavaDoc getFunctionValue(Node node, Parameters parameters) {
343                 return getFunctionValue(MMObjectBuilder.this.getFunctions(getCoreNode(MMObjectBuilder.this, node)), parameters);
344             }
345             public Object JavaDoc getFunctionValue(Parameters parameters) {
346                 MMObjectNode node = (MMObjectNode) parameters.get(Parameter.NODE);
347                 if (node == null) {
348                     return getFunctionValue(MMObjectBuilder.this.getFunctions(), parameters);
349                 } else {
350                     return getFunctionValue(MMObjectBuilder.this.getFunctions(node), parameters);
351                 }
352             }
353         };
354     {
355         addFunction(infoFunction);
356     }
357
358     // contains the builder's field definitions
359
protected final Map fields = new HashMap();
360
361     /**
362      * Determines whether a builder is virtual (data is not stored in the storage layer).
363      */

364     protected boolean virtual=false;
365
366     /**
367      * Set of remote observers, which are notified when a node of this type changes
368      */

369     private Set remoteObservers = Collections.synchronizedSet(new HashSet());
370
371     /**
372      * Set of local observers, which are notified when a node of this type changes
373      */

374     private Set localObservers = Collections.synchronizedSet(new HashSet());
375
376     /**
377      * Reference to the builders that this builder extends.
378      * @since MMBase-1.6.2 (parentBuilder in 1.6.0)
379      */

380     private Stack ancestors = new Stack();
381
382     /**
383      * Version information for builder registration
384      * Set with &lt;builder maintainer="mmbase.org" version="0"&gt; in the xml
385      * builder file
386      */

387     private int version=0;
388
389     /**
390      * Contains lists of builder fields in specified order
391      * (ORDER_CREATE, ORDER_EDIT, ORDER_LIST, ORDER_SEARCH)
392      */

393     private Map sortedFieldLists = new HashMap();
394
395     /** Properties of a specific Builder.
396      * Specified in the xml builder file with the <properties> tag.
397      * The use of properties is determined by builder
398      */

399     private Hashtable properties = null;
400
401     /**
402      * The datatype collector for this builder
403      */

404     private DataTypeCollector dataTypeCollector = null;
405
406     /**
407      * Constructor.
408      */

409     public MMObjectBuilder() {
410         storageConnector = new StorageConnector(this);
411     }
412
413     private void initAncestors() {
414         if (! ancestors.empty()) {
415             ((MMObjectBuilder) ancestors.peek()).init();
416         }
417     }
418
419     /**
420      * Initializes this builder
421      * The property 'mmb' needs to be set for the builder before this method can be called.
422      * The method retrieves data from the TypeDef builder, or adds data to that builder if the
423      * current builder is not yet registered.
424      * @return true if init was completed, false if uncompleted.
425      * @see #create
426      */

427     public boolean init() {
428         synchronized(mmb) { // synchronized on mmb because can only init builder if mmb is inited completely
429

430             // skip initialisation if oType has been set (happend at end of init)
431
// note that init can be called twice
432
if (oType != -1) return true;
433
434             log.debug("Init of builder " + getTableName());
435
436             // first make sure parent builder is initalized
437
initAncestors();
438
439             String JavaDoc broadCastChangesProperty = getInitParameter(BROADCAST_CHANGES_PROPERTY);
440             if (broadCastChangesProperty != null) {
441                 broadCastChanges = broadCastChangesProperty.equals("true");
442             }
443
444             if (!created()) {
445                 log.info("Creating table for builder " + tableName);
446                 if (!create() ) {
447                     // can't create buildertable.
448
// Throw an exception
449
throw new BuilderConfigurationException("Cannot create table for "+getTableName()+".");
450                 };
451             }
452             TypeDef typeDef = mmb.getTypeDef();
453             // only deteremine otype if typedef is available,
454
// or this is typedef itself (have to start somewhere)
455
if (((typeDef != null) && (typeDef.getObjectType()!=-1)) || (this == typeDef)) {
456                 oType = typeDef.getIntValue(tableName);
457                 if (oType == -1) { // no object type number defined yet
458
if (log.isDebugEnabled()) log.debug("Creating typedef entry for " + tableName);
459                     MMObjectNode node = typeDef.getNewNode(SYSTEM_OWNER);
460                     node.storeValue("name", tableName);
461
462                     // This sucks:
463
if (description == null) description = "not defined in this language";
464
465                     node.storeValue("description", description);
466
467                     try {
468                         oType = mmb.getStorageManager().createKey();
469                     } catch (StorageException se) {
470                         log.error(se.getMessage() + Logging.stackTrace(se));
471                         return false;
472                     }
473
474                     log.debug("Got key " + oType);
475                     node.storeValue(FIELD_NUMBER, new Integer JavaDoc(oType));
476                     // for typedef, set otype explictly, as it wasn't set in getNewNode()
477
if (this == typeDef) {
478                         node.storeValue(FIELD_OBJECT_TYPE, new Integer JavaDoc(oType));
479                     }
480                     typeDef.insert(SYSTEM_OWNER, node, false);
481                     // for typedef, call it's parents init again, as otype is only now set
482
if (this == typeDef) {
483                         initAncestors();
484                     }
485                 }
486             } else {
487                 // warn if typedef was not created
488
// except for the 'object' and 'typedef' basic builders
489
if(!tableName.equals("typedef") && !tableName.equals("object")) {
490                     log.warn("init(): for tablename(" + tableName + ") -> can't get to typeDef");
491                     return false;
492                 }
493             }
494             // XXX: wtf
495
// add temporary fields
496
checkAddTmpField(TMP_FIELD_NUMBER);
497             checkAddTmpField(TMP_FIELD_EXISTS);
498
499             // get property of maximum number of queries..
500
String JavaDoc property = getInitParameter(MAX_NODES_FROM_QUERY_PROPERTY);
501             if(property != null) {
502                 try {
503                     maxNodesFromQuery = Integer.parseInt(property);
504                     log.debug(getTableName() + " returns no more than " + maxNodesFromQuery + " records from a query.");
505                 } catch(NumberFormatException JavaDoc nfe) {
506                     log.warn("property:" + MAX_NODES_FROM_QUERY_PROPERTY + " contained an invalid integer value:'" + property +"'(" + nfe + ")");
507                 }
508             }
509         }
510         update();
511
512         //now register it as a listener for events of it's own type
513
//this is only for backwards compatibility, to notify the MMBaseObserver's
514
MMBase.getMMBase().addNodeRelatedEventsListener(getTableName(), this);
515
516
517         return true;
518     }
519
520     /**
521      * Returns the builder object number, which also functions as the objecttype.
522      * This is the same value as the value of the 'otype' field of objects created by this builder
523      * (rather than created by its descendants).
524      * @return the builder number
525      * @since MMBase-1.8
526      */

527     public int getNumber() {
528         return oType;
529     }
530
531     /**
532      * Returns the objecttype (otype).
533      * By preference, use {@link #getNumber()} for future compatibility with the bridge NodeManager methods.
534      * @return the objecttype
535      */

536     public int getObjectType() {
537         return getNumber();
538     }
539
540     /**
541      * Updates the internal version number of this buidler;
542      */

543     protected void update() {
544         internalVersion = System.currentTimeMillis();
545     }
546
547     /**
548      * Returns the builder's internal version number.
549      * This number can be used to sync wrapper classes. I.e. to make sure that a
550      * nodemanager's fieldlist is the same as that of the wrapped builder.
551      */

552     public long getInternalVersion() {
553         return internalVersion;
554     }
555
556     /**
557      * Creates a new builder table in the storage layer.
558      */

559     public boolean create() {
560         log.debug(tableName);
561         try {
562             mmb.getStorageManager().create(this);
563             return true;
564         } catch (StorageException se) {
565             log.error(se.getMessage() + Logging.stackTrace(se));
566             return false;
567         }
568     }
569
570     /**
571      * Removes the builder from the storage.
572      * @since MMBase-1.7
573      */

574     public void delete() {
575         log.service("trying to drop table of builder: '"+tableName+"'");
576         mmb.getStorageManager().delete(this);
577     }
578
579     /**
580      * Tests whether the data in a node is valid (throws an exception if this is not the case).
581      * @param node The node whose data to check
582      * @throws org.mmbase.module.core.InvalidDataException
583      * If the data was unrecoverably invalid (the references did not point to existing objects)
584      */

585     public void testValidData(MMObjectNode node) throws InvalidDataException {
586         return;
587     };
588
589     /**
590      * Insert a new, empty, object of a certain type.
591      * @param oType The type of object to create
592      * @param owner The administrator creating the node
593      * @return An <code>int</code> value which is the new object's unique number, -1 if the insert failed.
594      * The basic routine does not create any nodes this way and always fails.
595      */

596     public int insert(int oType, String JavaDoc owner) {
597         return -1;
598     }
599
600     /**
601      * Insert a new object (content provided) in the cloud, including an entry for the object alias (if provided).
602      * This method indirectly calls {@link #preCommit}.
603      * @param owner The administrator creating the node
604      * @param node The object to insert. The object need be of the same type as the current builder.
605      * @return An <code>int</code> value which is the new object's unique number, -1 if the insert failed.
606      */

607     public int insert(String JavaDoc owner, MMObjectNode node) {
608         int n = mmb.getStorageManager().create(node);
609         if (n >= 0) {
610             node.isNew = false;
611         }
612
613         node.useAliases();
614
615         // it is in the storage now, all caches can allready be invalidated, this makes sure
616
// that imediate 'select' after 'insert' will be correct'.
617
//xxx: this is bad.let's kill it!
618
//QueryResultCache.invalidateAll(node, NodeEvent.TYPE_NEW);
619
if (n <= 0) {
620             log.warn("Did not get valid nodeNumber of storage " + n);
621         }
622         Integer JavaDoc nodeNumber = new Integer JavaDoc(n);
623         if (isNodeCached(nodeNumber)) {
624             // it seems that something put the node in the cache already.
625
// This is usually because the ChangeManager indirectly called 'getNode'
626
// This should in the new event-mechanism not be needed, because the NodeEvent
627
// contains the node.
628
//log.warn("New node '" + n + "' of type " + node.parent.getTableName() + " is already in node-cache!" + Logging.stackTrace());
629
} else {
630             safeCache(nodeNumber, node);
631         }
632         return n;
633     }
634
635     /**
636      * This method is called before an actual write to the storage layer is performed.
637      * @param node The node to be committed.
638      * @return the node to be committed (possibly after changes have been made).
639      */

640     public MMObjectNode preCommit(MMObjectNode node) {
641         return node;
642     }
643
644     /**
645      * Commit changes to this node to the storage layer. This method indirectly calls {@link #preCommit}.
646      * Use only to commit changes - for adding node, use {@link #insert}.
647      * @param node The node to be committed
648      * @return true if commit successful
649      */

650     public boolean commit(MMObjectNode node) {
651         mmb.getStorageManager().change(node);
652         return true;
653     }
654
655     /**
656      * Determines whether changes to this builder need be broadcast to other known mmbase servers.
657      * This setting also governs whether the cache for relation builders is emptied when a relation changes.
658      * Actual broadcasting (and cache emptying) is initiated in the storage layer, when
659      * changes are commited.
660      * By default, all builders broadcast their changes, with the exception of the TypeDef builder.
661      *
662      * MM: Can somebody please explain _why_ typedef node changes, like e.g. creating a new node type are _not_ broadcast.
663      * @since MMBase-1.8
664      */

665     public boolean broadcastChanges() {
666         return broadCastChanges;
667     }
668
669     /**
670      * Creates an alias for a node, provided the OAlias builder is loaded.
671      * @param number the to-be-aliased node's unique number
672      * @param alias the aliasname to associate with the object
673      * @param owner the owner of the alias
674      * @since MMBase-1.8
675      * @return if the alias could be created
676      */

677     public boolean createAlias(int number, String JavaDoc alias, String JavaDoc owner) {
678         if (mmb.getOAlias() != null) {
679             if (getNode(alias) != null ) { // this alias already exists! Don't add a new one!
680
return false;
681             }
682             mmb.getOAlias().createAlias(alias, number, owner);
683             return true;
684         } else {
685             return false;
686         }
687     }
688
689     /**
690      * Creates an alias for a node, provided the OAlias builder is loaded.
691      * @param number the to-be-aliased node's unique number
692      * @param alias the aliasname to associate with the object
693      * @return if the alias could be created
694      */

695     public boolean createAlias(int number, String JavaDoc alias) {
696         return createAlias(number, alias, "system");
697     }
698
699     /**
700      * Returns the builder that this builder extends.
701      *
702      * @since MMBase-1.6
703      * @return the extended (parent) builder, or null if not available
704      */

705     public MMObjectBuilder getParentBuilder() {
706         if (ancestors.empty()) return null;
707         return (MMObjectBuilder) ancestors.peek();
708     }
709     /**
710      * Gives the list of parent-builders.
711      *
712      * @since MMBase-1.6.2
713
714      */

715     public List getAncestors() {
716         return Collections.unmodifiableList(ancestors);
717     }
718
719     /**
720      * Creates list of descendant-builders.
721      *
722      * @since MMBase-1.6.2
723      */

724     public List getDescendants() {
725         ArrayList result = new ArrayList();
726         for (Iterator i = mmb.getBuilders().iterator(); i.hasNext(); ) {
727             MMObjectBuilder builder = (MMObjectBuilder) i.next();
728             if (builder.isExtensionOf(this)) {
729                 result.add(builder);
730             }
731         }
732         return result;
733     }
734
735
736     /**
737      * Sets the builder that this builder extends, and registers it in the storage layer.
738      * @param parent the extended (parent) builder, or null if not available
739      *
740      * @since MMBase-1.6
741      */

742     public void setParentBuilder(MMObjectBuilder parent) {
743         ancestors.addAll(parent.getAncestors());
744         ancestors.push(parent);
745         getDataTypeCollector().addCollector(parent.getDataTypeCollector());
746     }
747
748     /**
749      * Returns the datatype collector belonging to this buidler.
750      * A datatype collector contains the datatypes that are local to this builder.
751      * @since MMBase-1.8
752      */

753     public DataTypeCollector getDataTypeCollector() {
754         if (dataTypeCollector == null) {
755             Object JavaDoc signature = new String JavaDoc(getTableName()+ "_" + System.currentTimeMillis());
756             dataTypeCollector = new DataTypeCollector(signature);
757         }
758         return dataTypeCollector;
759     }
760
761     /**
762      * Checks wether this builder is an extension of the argument builder
763      *
764      * @since MMBase-1.6.2
765      */

766     public boolean isExtensionOf(MMObjectBuilder o) {
767         return ancestors.contains(o);
768     }
769
770     /**
771      * Get a new node, using this builder as its parent. The new node is not a part of the cloud
772      * yet, and thus has the value -1 as a number. (Call {@link #insert} to add the node to the
773      * cloud).
774      * @param owner The administrator creating the new node.
775      * @return A newly initialized <code>MMObjectNode</code>.
776      */

777     public MMObjectNode getNewNode(String JavaDoc owner) {
778         MMObjectNode node = getEmptyNode(owner);
779         node.isNew = true;
780         return node;
781     }
782
783     /**
784      * Returns a new empty node object. This is used by Storage to create a non-new node object (isNew is false), which is then
785      * be filled with actual values from storage.
786      * @since MMBase-1.8.
787      */

788     public MMObjectNode getEmptyNode(String JavaDoc owner) {
789         MMObjectNode node = new MMObjectNode(this, false);
790         node.setValue(FIELD_NUMBER, -1);
791         node.setValue(FIELD_OWNER, owner);
792         node.setValue(FIELD_OBJECT_TYPE, oType);
793         setDefaults(node);
794         return node;
795     }
796     /**
797      * Sets defaults for a node. Fields "number", "owner" and "otype" are not set by this method.
798      * @param node The node to set the defaults of.
799      */

800     public void setDefaults(MMObjectNode node) {
801         for (Iterator i = getFields().iterator(); i.hasNext(); ) {
802             CoreField field = (CoreField) i.next();
803             if (field.getName().equals(FIELD_NUMBER)) continue;
804             if (field.getName().equals(FIELD_OWNER)) continue;
805             if (field.getName().equals(FIELD_OBJECT_TYPE)) continue;
806             if (field.getType() == Field.TYPE_NODE) continue;
807
808             Object JavaDoc defaultValue = field.getDataType().getDefaultValue();
809             if ((defaultValue == null) && field.isNotNull()) {
810                 Class JavaDoc clazz = Fields.typeToClass(field.getType());
811                 if (clazz != null) {
812                     defaultValue = Casting.toType(clazz, null, "");
813                 } else {
814                     log.warn("No class found for type of " + field);
815                 }
816             }
817             node.setValue(field.getName(), defaultValue);
818         }
819     }
820
821     /**
822      * In setDefault you could want to generate unique values for fields (if the field is 'unique').
823      * @since MMBase-1.7
824      */

825     protected String JavaDoc setUniqueValue(MMObjectNode node, String JavaDoc field, String JavaDoc baseValue) {
826         int seq = 0;
827         boolean found = false;
828         String JavaDoc value = baseValue;
829         try {
830             while (! found) {
831                 NodeSearchQuery query = new NodeSearchQuery(this);
832                 value = baseValue + seq;
833                 BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(query.getField(getField(field)), value);
834                 query.setConstraint(constraint);
835                 if (getNodes(query).size() == 0) {
836                     found = true;
837                     break;
838                 }
839                 seq++;
840             }
841         } catch (SearchQueryException e) {
842             value = baseValue + System.currentTimeMillis();
843         }
844         node.setValue(field, value);
845         return value;
846     }
847
848     /**
849      * In setDefault you could want to generate unique values for fields (if the field is 'unique').
850      * @since MMBase-1.7
851      */

852     protected int setUniqueValue(MMObjectNode node, String JavaDoc field, int offset) {
853         int seq = offset;
854         boolean found = false;
855         try {
856             while (! found) {
857                 NodeSearchQuery query = new NodeSearchQuery(this);
858                 BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(query.getField(getField(field)), new Integer JavaDoc(seq));
859                 query.setConstraint(constraint);
860                 if (getNodes(query).size() == 0) {
861                     found = true;
862                     break;
863                 }
864                 seq++;
865             }
866         } catch (SearchQueryException e) {
867             seq = (int) System.currentTimeMillis() / 1000;
868         }
869         node.setValue(field, seq);
870         return seq;
871     }
872
873     /**
874      * Remove a node from the cloud.
875      * @param node The node to remove.
876      */

877     public void removeNode(MMObjectNode node) {
878         if (oType != node.getOType()) {
879             // fixed comment's below..??
880
// prevent from making storage inconsistent(say remove nodes from inactive builder)
881
// the builder we are in is not the actual builder!!
882
// ? why not an node.remove()
883
throw new RuntimeException JavaDoc("Builder with name: " + getTableName() + "(otype " + oType + ") is not the actual builder of the node that is to be deleted: " +
884                                        node.getNumber() + " (otype: " + node.getOType() + ")");
885         }
886
887         removeSyncNodes(node);
888
889         clearBlobCache(node.getNumber());
890
891         // removes the node FROM THIS BUILDER
892
// seems not a very logical call, as node.parent is the node's actual builder,
893
// which may - possibly - be very different from the current builder
894
mmb.getStorageManager().delete(node);
895
896         // change is in storage, caches can be invalidated immediately
897
//really bad!!!
898
//QueryResultCache.invalidateAll(node, NodeEvent.EVENT_TYPE_DELETE);
899
}
900
901     /**
902      * Removes the syncnodes to this node. This is logical, but also needed to maintain storage
903      * integrety.
904      *
905      * @since MMBase-1.7
906      */

907     protected void removeSyncNodes(MMObjectNode node) {
908         try {
909             MMObjectBuilder syncnodes = mmb.getBuilder("syncnodes");
910             NodeSearchQuery query = new NodeSearchQuery(syncnodes);
911             Object JavaDoc numericalValue = new Integer JavaDoc(node.getNumber());
912             BasicStepField field = query.getField(syncnodes.getField("localnumber"));
913             BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(field,
914                                                                                  numericalValue);
915             query.setConstraint(constraint);
916             Iterator syncs = syncnodes.getNodes(query).iterator();
917             while (syncs.hasNext()) {
918                 MMObjectNode syncnode = (MMObjectNode) syncs.next();
919                 syncnode.parent.removeNode(syncnode);
920                 if (log.isDebugEnabled()) {
921                     log.debug("Removed syncnode " + syncnode);
922                 }
923             }
924         } catch (SearchQueryException e) {
925             throw new RuntimeException JavaDoc(e);
926         }
927     }
928
929     /**
930      * Remove the relations of a node.
931      * @param node The node whose relations to remove.
932      */

933     public void removeRelations(MMObjectNode node) {
934         List relsv = getRelations_main(node.getNumber());
935         if (relsv != null) {
936             for(Iterator rels = relsv.iterator(); rels.hasNext(); ) {
937                 // get the relation node
938
MMObjectNode relnode = (MMObjectNode)rels.next();
939                 // determine the true builder for this node
940
// (node.parent is always InsRel, but otype
941
// indicates any derived builders, such as AuthRel)
942
MMObjectBuilder bul = mmb.getMMObject(mmb.getTypeDef().getValue(relnode.getOType()));
943                 // remove the node using this builder
944
// circumvent problem in storage layer (?)
945
bul.removeNode(relnode);
946             }
947         }
948     }
949
950     /**
951      * Is this node cached at this moment?
952      * @param number The number of the node to check.
953      * @return <code>true</code> if the node is in the cache, <code>false</code> otherwise
954      */

955     public boolean isNodeCached(Integer JavaDoc number) {
956         return nodeCache.containsKey(number);
957     }
958
959     /**
960      * Retrieves a node from the cache, or <code>null</code> if it doesn't exist.
961      * @param number The number of the node to retrieve.
962      * @return an MMObjectNode or <code>null</code> if the node is not in the cache
963      * @todo This is a simple wrapper around node cache, why not expose node cache in stead?
964      * @since MMBase-1.8
965      */

966     public MMObjectNode getNodeFromCache(Integer JavaDoc number) {
967         return (MMObjectNode) nodeCache.get(number);
968     }
969
970     /**
971      * Stores a node in the cache provided the cache is not write locked.
972      * @return a valid node. If the node already was in the cache, the cached node is returned.
973      * In that case the node given as parameter should become invalid
974      */

975     public MMObjectNode safeCache(Integer JavaDoc n, MMObjectNode node) {
976         MMObjectNode retval = getNodeFromCache(n);
977         if (retval != null) {
978             return retval;
979         } else {
980             synchronized (nodeCache) {
981                 if (cacheLocked == 0) {
982                     nodeCache.put(n, node);
983                 }
984             }
985             return node;
986         }
987     }
988
989     /**
990      * Locks the node cache during the commit of a node. This prevents the cache from gaining an
991      * invalid state during the commit.
992      *
993      * Basicly the goals is to ensure that nothing is put into the cache during a commit of a node,
994      * because that may be the wrong node then.
995      */

996     boolean safeCommit(MMObjectNode node) {
997         boolean res = false;
998         try {
999             synchronized(nodeCache) {
1000                cacheLocked++;
1001            }
1002            if (node.getNumber() > 0 ) {
1003                Integer JavaDoc number = new Integer JavaDoc(node.getNumber());
1004                nodeCache.remove(number);
1005            }
1006
1007            res = node.commit();
1008        } finally {
1009            synchronized(nodeCache) {
1010                cacheLocked--;
1011            }
1012        }
1013        return res;
1014    }
1015
1016    /**
1017     * Locks the node cache during the insert of a node.
1018     * This prevents the cache from adding the node, which
1019     * means that the next time the node is read it is 'refreshed'
1020     * from the storage
1021     */

1022    int safeInsert(MMObjectNode node, String JavaDoc userName) {
1023        int res = -1;
1024        try {
1025            synchronized(nodeCache) {
1026                cacheLocked++;
1027            }
1028            // determine valid username
1029
if ((userName == null) || (userName.length() <= 1 )) { // may not have owner of 1 char??
1030
userName = node.getStringValue(FIELD_OWNER);
1031                if (log.isDebugEnabled()) {
1032                    log.debug("Found username " + (userName == null ? "NULL" : userName));
1033                }
1034            }
1035            res = node.insert(userName);
1036            if (res > -1) {
1037                nodeCache.put(new Integer JavaDoc(res), node);
1038            }
1039        } finally {
1040            synchronized(nodeCache) {
1041                cacheLocked--;
1042            }
1043        }
1044        return res;
1045    }
1046
1047    /**
1048     * Determine whether this builder is virtual.
1049     * A virtual builder represents nodes that are not stored or retrieved directly
1050     * from storage, but are created as needed.
1051     * @return <code>true</code> if the builder is virtual.
1052     */

1053    public boolean isVirtual() {
1054        return virtual;
1055    }
1056
1057    /**
1058     * Retrieves a node based on a unique key. The key is either an entry from the OAlias table
1059     * or the string-form of an integer value (the number field of an object node).
1060     * Note that the OAlias builder needs to be active for the alias to be used
1061     * (otherwise using an alias is concidered invalid).
1062     * @param key The value to search for
1063     * @param useCache If true, the node is retrieved from the node cache if possible.
1064     * @return <code>null</code> if the node does not exist or the key is invalid, or a
1065     * <code>MMObjectNode</code> containing the contents of the requested node.
1066     */

1067    public MMObjectNode getNode(String JavaDoc key, boolean useCache) {
1068        if( key == null ) {
1069            log.error("getNode(null) for builder '" + tableName + "': key is null!");
1070            // who is doing that?
1071
log.info(Logging.stackTrace(6));
1072            return null;
1073        }
1074        int nr =-1;
1075        // first look if we have a number...
1076
try {
1077            nr = Integer.parseInt(key);
1078        } catch (Exception JavaDoc e) {}
1079        if (nr != -1) {
1080            // key passed was a number.
1081
// return node with this number
1082
return getNode(nr, useCache);
1083        } else {
1084            // key passed was an alias
1085
// return node with this alias
1086
log.debug("Getting node by alias");
1087            if (mmb.getOAlias() != null) {
1088                return mmb.getOAlias().getAliasedNode(key);
1089            } else {
1090                return null;
1091            }
1092        }
1093    }
1094
1095    /**
1096     * Retrieves a node based on a unique key. The key is either an entry from the OAlias table
1097     * or the string-form of an integer value (the number field of an object node).
1098     * Retrieves a node from the node cache if possible.
1099     * @param key The value to search for
1100     * @return <code>null</code> if the node does not exist or the key is invalid, or a
1101     * <code>MMObjectNode</code> containing the contents of the requested node.
1102     */

1103    public MMObjectNode getNode(String JavaDoc key) {
1104        return getNode(key, true);
1105    }
1106
1107    /**
1108     * Retrieves a node based on a unique key. The key is either an entry from the OAlias table
1109     * or the string-form of an integer value (the number field of an object node).
1110     * Retrieves the node from directly the storage, not using the node cache.
1111     * @param key The value to search for
1112     * @return <code>null</code> if the node does not exist or the key is invalid, or a
1113     * <code>MMObjectNode</code> containing the contents of the requested node.
1114     */

1115    public MMObjectNode getHardNode(String JavaDoc key) {
1116        return getNode(key, false);
1117    }
1118
1119    /**
1120     * Retrieves a node based on it's number (a unique key), retrieving the node
1121     * from the node cache if possible.
1122     * @param number The number of the node to search for
1123     * @return <code>null</code> if the node does not exist or the key is invalid, or a
1124     * <code>MMObjectNode</code> containign the contents of the requested node.
1125     */

1126    public MMObjectNode getNode(int number) {
1127        return getNode(number, true);
1128    }
1129
1130    /**
1131     * Retrieves a node based on it's number (a unique key), directly from
1132     * the storage, not using the node cache.
1133     * @param number The number of the node to search for
1134     * @return <code>null</code> if the node does not exist or the key is invalid, or a
1135     * <code>MMObjectNode</code> containign the contents of the requested node.
1136     */

1137    public MMObjectNode getHardNode(int number) {
1138        return getNode(number, false);
1139    }
1140
1141    /**
1142     * Create a new temporary node and put it in the temporary _exist
1143     * node space
1144     */

1145    public MMObjectNode getNewTmpNode(String JavaDoc owner,String JavaDoc key) {
1146        MMObjectNode node = null;
1147        node = getNewNode(owner);
1148        node.setValue(TMP_FIELD_NUMBER, key);
1149        temporaryNodes.put(key, node);
1150        return node;
1151    }
1152
1153    /**
1154     * Put a Node in the temporary node list
1155     * @param key The (temporary) key under which to store the node
1156     * @param node The node to store
1157     */

1158    public void putTmpNode(String JavaDoc key, MMObjectNode node) {
1159        node.setValue(TMP_FIELD_NUMBER, key);
1160        temporaryNodes.put(key, node);
1161    }
1162
1163    /**
1164     * Defines a virtual field to use for temporary nodes. If the given field-name does not start
1165     * with underscore ('_'), wich it usually does, then the field does also get a 'dbpos' (1000) as if it
1166     * was actually present in the builder's XML as a virtual field (this is accompanied with a log
1167     * message).
1168     *
1169     * Normally this is used to add 'tmp' fields like _number, _exists and _snumber which are system
1170     * fields which are normally invisible.
1171     *
1172     * @param field the name of the temporary field
1173     * @return true if the field was added, false if it already existed.
1174     */

1175    public boolean checkAddTmpField(String JavaDoc field) {
1176        if (getDBState(field) == Field.STATE_UNKNOWN) { // means that field is not yet defined.
1177
CoreField fd = Fields.createField(field, Field.TYPE_STRING, Field.TYPE_UNKNOWN, Field.STATE_VIRTUAL, null);
1178            if (! fd.isTemporary()) {
1179                fd.setStoragePosition(1000);
1180                log.service("Added a virtual field '" + field + "' to builder '" + getTableName() + "' because it was not defined in the builder's XML, but the implementation requires it to exist.");
1181            } else {
1182                log.debug("Adding tmp (virtual) field '" + field + "' to builder '" + getTableName() + "'");
1183            }
1184
1185            fd.setParent(this);
1186            fd.finish();
1187
1188            addField(fd);
1189            // added field, so update version
1190
update();
1191            return true;
1192        } else {
1193            return false;
1194        }
1195    }
1196
1197    /**
1198     * Get nodes from the temporary node space
1199     * @param key The (temporary) key to use under which the node is stored
1200     */

1201    public MMObjectNode getTmpNode(String JavaDoc key) {
1202        MMObjectNode node = null;
1203        node = (MMObjectNode) temporaryNodes.get(key);
1204        if (node == null && log.isDebugEnabled()) {
1205            log.trace("getTmpNode(): node not found " + key);
1206        }
1207        return node;
1208    }
1209
1210    /**
1211     * Remove a node from the temporary node space
1212     * @param key The (temporary) key under which the node is stored
1213     */

1214    public void removeTmpNode(String JavaDoc key) {
1215        MMObjectNode node;
1216        node=(MMObjectNode) temporaryNodes.remove(key);
1217        if (node == null) {
1218        log.debug("removeTmpNode: node with "+key+" didn't exists");
1219    }
1220    }
1221
1222    /**
1223     * Return a copy of the list of field definitions of this table.
1224     * @return An unmodifiable <code>Collection</code> with the tables fields
1225     */

1226    public Collection getFields() {
1227        return Collections.unmodifiableCollection(fields.values());
1228    }
1229
1230
1231    /**
1232     * Return a list of field names of this table.
1233     * @return a unmodifiable <code>Set</code> with the tables field names
1234     * @todo return an unmodifiable Set.
1235     */

1236    public Set getFieldNames() {
1237        return Collections.unmodifiableSet(fields.keySet());
1238    }
1239
1240    /**
1241     * Return a field's definition
1242     * @param fieldName the requested field's name
1243     * @return a <code>FieldDefs</code> belonging with the indicated field
1244     * @todo Should return CoreField
1245     */

1246    public FieldDefs getField(String JavaDoc fieldName) {
1247        return (FieldDefs) fields.get(fieldName.toLowerCase());
1248    }
1249
1250
1251    /**
1252     * @since MMBase-1.8
1253     */

1254    public boolean hasField(String JavaDoc fieldName) {
1255        return fields.containsKey(fieldName.toLowerCase());
1256    }
1257
1258    /**
1259     * Clears all field list caches, and recalculates the field list.
1260     */

1261    protected void updateFields() {
1262        sortedFieldLists = new HashMap();
1263        update();
1264    }
1265
1266    /**
1267     * Add a field to this builder.
1268     * This does not affect the builder config file, nor the table used.
1269     * @param def the field definiton to add
1270     */

1271    public void addField(CoreField def) {
1272        Object JavaDoc oldField = fields.put(def.getName().toLowerCase(), def);
1273        if (oldField != null) {
1274            log.warn("Replaced " + oldField + " !!");
1275        }
1276        updateFields();
1277    }
1278
1279
1280    /**
1281     * Remove a field from this builder.
1282     * This does not affect the builder config file, nor the table used.
1283     * @param fieldName the name of the field to remove
1284     */

1285    public void removeField(String JavaDoc fieldName) {
1286        CoreField def = getField(fieldName);
1287        int dbpos = def.getStoragePosition();
1288        fields.remove(fieldName);
1289        // move them all up one place
1290
for (Iterator e = fields.values().iterator(); e.hasNext();) {
1291            def = (CoreField) e.next();
1292            int curpos = def.getStoragePosition();
1293            if (curpos >= dbpos) def.setStoragePosition(curpos - 1);
1294        }
1295        updateFields();
1296    }
1297
1298
1299    /**
1300     * Return a field's storage type. The returned value is one of the following values
1301     * declared in Field:
1302     * TYPE_STRING,
1303     * TYPE_INTEGER,
1304     * TYPE_BINARY,
1305     * TYPE_FLOAT,
1306     * TYPE_DOUBLE,
1307     * TYPE_LONG,
1308     * TYPE_NODE,
1309     * TYPE_UNKNOWN
1310     * @param fieldName the requested field's name
1311     * @return the field's type.
1312     */

1313    public int getDBType(String JavaDoc fieldName) {
1314        if (fields == null) {
1315            log.error("getDBType(): fields are null on object : "+tableName);
1316            return Field.TYPE_UNKNOWN;
1317        }
1318        Field field = getField(fieldName);
1319        if (field == null) {
1320            //perhaps prefixed with own tableName[0-9]? (allowed since MMBase-1.7)
1321
int dot = fieldName.indexOf('.');
1322            if (dot > 0) {
1323                if (fieldName.startsWith(tableName)) {
1324                    if (tableName.length() <= dot ||
1325                        Character.isDigit(fieldName.charAt(dot - 1))) {
1326                        fieldName = fieldName.substring(dot + 1);
1327                        field = getField(fieldName);
1328                    }
1329                }
1330            }
1331        }
1332
1333        if (field == null) {
1334
1335            // log warning, except for virtual builders
1336
if (!virtual) { // should getDBType not be overridden in Virtual Builder then?
1337
log.warn("getDBType(): Can't find definition on field '" + fieldName + "' of builder " + tableName);
1338                log.debug(Logging.stackTrace());
1339            }
1340            return Field.TYPE_UNKNOWN;
1341        }
1342        return field.getType();
1343    }
1344
1345    /**
1346     * Return a field's storage state. The returned value is one of the following values
1347     * declared in Field:
1348     * STATE_VIRTUAL,
1349     * STATE_PERSISTENT,
1350     * STATE_SYSTEM,
1351     * STATE_UNKNOWN
1352     * @param fieldName the requested field's name
1353     * @return the field's state.
1354     */

1355    public int getDBState(String JavaDoc fieldName) {
1356        if (fields == null) return Field.STATE_UNKNOWN;
1357        Field field = getField(fieldName);
1358        if (field == null) return Field.STATE_UNKNOWN;
1359        return field.getState();
1360    }
1361
1362    /**
1363     * A complicated default implementation for GUI.
1364     * @since MMBase-1.8
1365     */

1366    protected String JavaDoc getGUIIndicator(MMObjectNode node, Parameters pars) {
1367        Locale locale = (Locale) pars.get(Parameter.LOCALE);
1368        String JavaDoc language = (String JavaDoc) pars.get(Parameter.LANGUAGE);
1369        if (locale == null) {
1370            if (language != null) {
1371                locale = new Locale(language, "");
1372            }
1373        } else {
1374            if (language != null && (! locale.getLanguage().equals(language))) { // odd, but well,
1375
locale = new Locale(language, locale.getCountry());
1376            }
1377        }
1378        if (locale == null) locale = mmb.getLocale();
1379
1380        if (log.isDebugEnabled()) {
1381            log.debug("language " + locale.getLanguage() + " country " + locale.getCountry());
1382        }
1383
1384        String JavaDoc rtn;
1385        String JavaDoc field = pars.getString("field");
1386
1387        if (locale == null) {
1388            if (field == null || "".equals(field)) {
1389                rtn = getGUIIndicator(node);
1390                if (rtn == GUI_INDICATOR) { // not overridden
1391
rtn = getNodeGUIIndicator(node, pars);
1392                }
1393            } else {
1394                rtn = getGUIIndicator(field, node);
1395            }
1396        } else {
1397            if (field == null || "".equals(field)) {
1398                rtn = getLocaleGUIIndicator(locale, node);
1399                if (rtn == GUI_INDICATOR) { // not overridden
1400
rtn = getNodeGUIIndicator(node, pars);
1401                }
1402            } else {
1403                rtn = getLocaleGUIIndicator(locale, field, node);
1404            }
1405        }
1406
1407        if (rtn == null) {
1408            CoreField fdef = getField(field);
1409
1410            Object JavaDoc returnValue;
1411            if (fdef != null) {
1412                // test if the value can be derived from the enumerationlist of a datatype
1413
DataType dataType = fdef.getDataType();
1414                if (dataType instanceof org.mmbase.datatypes.BinaryDataType) {
1415                    returnValue = node.isNull(field) ? "" : "" + node.getSize(field) + " byte";
1416                } else {
1417                    returnValue = dataType.getEnumerationValue(locale, (Cloud) pars.get(Parameter.CLOUD), (Node) pars.get(Parameter.NODE), fdef, node.getStringValue(field));
1418                }
1419            } else {
1420                returnValue = null;
1421            }
1422            if (returnValue != null) {
1423                rtn = returnValue.toString();
1424            } else {
1425                if (fdef != null && ("eventtime".equals(fdef.getGUIType()) ||
1426                                     fdef.getDataType() instanceof org.mmbase.datatypes.DateTimeDataType)) { // do something reasonable for this
1427
Date date;
1428                    if (fdef.getType() == Field.TYPE_DATETIME) {
1429                        date = node.getDateValue(field);
1430                    } else {
1431                        date = new Date(node.getLongValue(field) * 1000);
1432                    }
1433                    rtn = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, locale).format(date);
1434                    Calendar calendar = new GregorianCalendar(locale);
1435                    calendar.setTime(date);
1436                    if (calendar.get(Calendar.ERA) == GregorianCalendar.BC) {
1437                        java.text.DateFormat JavaDoc df = new java.text.SimpleDateFormat JavaDoc(" G", locale);
1438                        rtn += df.format(date);
1439                    }
1440                } else {
1441                    rtn = (String JavaDoc) pars.get("stringvalue");
1442                    if (rtn == null) {
1443                        rtn = node.getStringValue(field);
1444                    }
1445                }
1446            }
1447            rtn = org.mmbase.util.transformers.Xml.XMLEscape(rtn);
1448        }
1449        return rtn;
1450    }
1451
1452    /**
1453     * Returns a GUI-indicator for the node itself.
1454     * @since MMBase-1.8.2
1455     */

1456    protected String JavaDoc getNodeGUIIndicator(MMObjectNode node, Parameters params) {
1457        // do the best we can because this method was not implemented
1458
// we get the first field in the object and try to make it
1459
// to a string we can return
1460
List list = getFields(NodeManager.ORDER_LIST);
1461        if (list.size() > 0) {
1462            String JavaDoc fname = ((CoreField) list.get(0)).getName();
1463            String JavaDoc str = node.getStringValue( fname );
1464            if (str.length() > 128) {
1465                str = str.substring(0, 128) + "...";
1466            }
1467            if (params == null) {
1468                // Needed for getGuiIndicator calls for NODE fields
1469
// Temporary fix, should perhaps be solved in getGuiIndicator(node,params)
1470
String JavaDoc result = getGUIIndicator(fname, node);
1471                if (result == null) {
1472                    result = str;
1473                }
1474                return result;
1475            } else {
1476                params.set("field", fname);
1477                params.set("stringvalue", str);
1478                return getGUIIndicator(node, params);
1479            }
1480        } else {
1481            return GUI_INDICATOR;
1482        }
1483    }
1484
1485    /**
1486     * What should a GUI display for this node.
1487     * Default the value returned is GUI_INDICATOR ('no info').
1488     * Override this to display your own choice (see Images.java).
1489     * You may want to override {@link #getNodeGUIIndicator} for more flexibility.
1490     * @param node The node to display
1491     * @return the display of the node as a <code>String</code>
1492     */

1493    public String JavaDoc getGUIIndicator(MMObjectNode node) {
1494        return GUI_INDICATOR;
1495    }
1496
1497    /**
1498     * What should a GUI display for this node/field combo.
1499     * Default is null (indicating to display the field as is)
1500     * Override this to display your own choice.
1501     * @param node The node to display
1502     * @param fieldName the name field of the field to display
1503     * @return the display of the node's field as a <code>String</code>, null if not specified
1504     */

1505    public String JavaDoc getGUIIndicator(String JavaDoc fieldName, MMObjectNode node) {
1506        CoreField field = getField(fieldName);
1507
1508        if (field != null && field.getType() == Field.TYPE_NODE && ! fieldName.equals(FIELD_NUMBER)) {
1509            try {
1510                MMObjectNode otherNode = node.getNodeValue(fieldName);
1511                if (otherNode == null) {
1512                    return "";
1513                } else {
1514                    // may return GUI_INDICATOR
1515
String JavaDoc rtn = otherNode.parent.getGUIIndicator(otherNode);
1516                    if (rtn == GUI_INDICATOR) {
1517                        rtn = otherNode.parent.getNodeGUIIndicator(otherNode, null);
1518                    }
1519                    return rtn;
1520                }
1521            } catch (RuntimeException JavaDoc rte) {
1522                log.warn("Cannot load node from field " + fieldName +" in node " + node.getNumber() + ":" +rte);
1523                return "invalid";
1524            }
1525        } else {
1526            return null;
1527        }
1528    }
1529
1530
1531    /**
1532     * The GUIIndicator can depend on the locale. Override this function
1533     * @since MMBase-1.6
1534     */

1535    protected String JavaDoc getLocaleGUIIndicator(Locale locale, String JavaDoc field, MMObjectNode node) {
1536        return getGUIIndicator(field, node);
1537    }
1538
1539    /**
1540     * The GUIIndicator can depend on the locale. Override this function
1541     * You may want to override {@link #getNodeGUIIndicator} for more flexibility.
1542     * @since MMBase-1.6
1543     */

1544    protected String JavaDoc getLocaleGUIIndicator(Locale locale, MMObjectNode node) {
1545        return getGUIIndicator(node);
1546    }
1547
1548
1549    /**
1550     * Gets the field definitions for the editor, sorted according
1551     * to the specified order, and excluding the fields that have
1552     * not been assigned a valid position (valid is >= 0).
1553     * This method makes an explicit sort (it does not use a cached list).
1554     *
1555     * @param sortOrder One of the sortorders defined in
1556     * {@link org.mmbase.core.CoreField CoreField}
1557     * @return The ordered list of field definitions.
1558     */

1559    public List getFields(int sortOrder) {
1560        List orderedFields = (List)sortedFieldLists.get(new Integer JavaDoc(sortOrder));
1561        if (orderedFields == null) {
1562            orderedFields = new ArrayList();
1563            for (Iterator i = fields.values().iterator(); i.hasNext();) {
1564                CoreField field = (CoreField)i.next();
1565                if (field.isTemporary()) {
1566                    continue;
1567                }
1568
1569                // include only fields which have been assigned a valid position, and are
1570
if ((sortOrder == NodeManager.ORDER_NONE) ||
1571                    ((sortOrder == NodeManager.ORDER_CREATE) && (field.getStoragePosition()>-1)) ||
1572                    ((sortOrder == NodeManager.ORDER_EDIT) && (field.getEditPosition()>-1)) ||
1573                    ((sortOrder == NodeManager.ORDER_SEARCH) && (field.getSearchPosition()>-1)) ||
1574                    ((sortOrder == NodeManager.ORDER_LIST) && (field.getListPosition()>-1))
1575                    ) {
1576                    orderedFields.add(field);
1577                }
1578            }
1579            Fields.sort(orderedFields, sortOrder);
1580            sortedFieldLists.put(new Integer JavaDoc(sortOrder), Collections.unmodifiableList(orderedFields));
1581        } else {
1582            //log.info("From cache!");
1583
}
1584        return orderedFields;
1585    }
1586
1587    /**
1588     * Get the field definitions for the editor, sorted according to it's GUISearch property (as set in the builder xml file).
1589     * Used for creating search-forms.
1590     * @deprecated use getFields() with sortorder ORDER_SEARCH
1591     * @return a vector with CoreField objects
1592     */

1593    public Vector getEditFields() {
1594        return (Vector)getFields(NodeManager.ORDER_SEARCH);
1595    }
1596
1597    /**
1598     * Get the field definitions for the editor, sorted accoring to it's GUIList property (as set in the builder xml file).
1599     * Used for creating list-forms (tables).
1600     * @deprecated use getFields() with sortorder ORDER_LIST
1601     * @return a vector with CoreField objects
1602     */

1603    public Vector getSortedListFields() {
1604        return (Vector)getFields(NodeManager.ORDER_LIST);
1605    }
1606
1607    /**
1608     * Get the field definitions for the editor, sorted according to it's GUIPos property (as set in the builder xml file).
1609     * Used for creating edit-forms.
1610     * @deprecated use getFields() with sortorder ORDER_EDIT
1611     * @return a vector with CoreField objects
1612     */

1613    public Vector getSortedFields() {
1614        return (Vector)getFields(NodeManager.ORDER_EDIT);
1615    }
1616
1617    /**
1618     * Returns the next field as defined by its sortorder, according to the specified order.
1619     */

1620    public FieldDefs getNextField(String JavaDoc currentfield, int sortorder) {
1621        CoreField cdef = getField(currentfield);
1622        List sortedFields = getFields(sortorder);
1623        int pos = sortedFields.indexOf(cdef);
1624        if (pos != -1 && (pos+1) < sortedFields.size()) {
1625            return (FieldDefs) sortedFields.get(pos+1);
1626        }
1627        return null;
1628    }
1629
1630    /**
1631     * Returns the next field as defined by its sortorder, according to it's GUIPos property (as set in the builder xml file).
1632     * Used for moving between fields in an edit-form.
1633     * @deprecated use getNextField() with sortorder ORDER_EDIT
1634     */

1635    public FieldDefs getNextField(String JavaDoc currentfield) {
1636        return getNextField(currentfield,NodeManager.ORDER_EDIT);
1637    }
1638
1639    /**
1640     * Returns
1641     * @since MMBase-1.7.4
1642     */

1643    protected BlobCache getBlobCache(String JavaDoc fieldName) {
1644        return genericBlobCache;
1645    }
1646
1647    /**
1648     * @since MMBase-1.8
1649     */

1650    public int clearBlobCache(int nodeNumber) {
1651        Iterator i = getFields().iterator();
1652        int result = 0;
1653        while (i.hasNext()) {
1654            CoreField field = (CoreField) i.next();
1655            String JavaDoc fieldName = field.getName();
1656            BlobCache cache = getBlobCache(fieldName);
1657            String JavaDoc key = cache.getKey(nodeNumber,fieldName);
1658            if (cache.remove(key) != null) result++;
1659        }
1660        return result;
1661    }
1662
1663    /**
1664     * Provides additional functionality when obtaining field values.
1665     * This method is called whenever a Node of the builder's type fails at evaluating a getValue() request
1666     * (generally when a fieldname is supplied that doesn't exist).
1667     * It allows the system to add 'functions' to be included with a field name, such as 'html(body)' or 'time(lastmodified)'.
1668     * This method will parse the fieldname, determining functions and calling the {@link #executeFunction} method to handle it.
1669     * Functions in fieldnames can be given in the format 'functionname(fieldname)'. An old format allows 'functionname_fieldname' instead,
1670     * though this only applies to the text functions 'short', 'html', and 'wap'.
1671     * Functions can be nested, i.e. 'html(shorted(body))'.
1672     * Derived builders should override this method only if they want to provide virtual fieldnames. To provide addiitonal functions,
1673     * call {@link #addFunction} instead. See also the source code for {@link org.mmbase.util.functions.ExampleBuilder}.
1674     * @param node the node whos efields are queries
1675     * @param field the fieldname that is requested
1676     * @return the result of the 'function', or null if no valid functions could be determined.
1677     */

1678    public Object JavaDoc getValue(MMObjectNode node, String JavaDoc field) {
1679        Object JavaDoc rtn = getObjectValue(node, field);
1680
1681        // Old code
1682
if (field.indexOf("short_") == 0) {
1683            String JavaDoc val = node.getStringValue(field.substring(6));
1684            val = getShort(val,34);
1685            rtn = val;
1686        } else if (field.indexOf("html_") == 0) {
1687            String JavaDoc val = node.getStringValue(field.substring(5));
1688            val = getHTML(val);
1689            rtn = val;
1690        } else if (field.indexOf("wap_") == 0) {
1691            String JavaDoc val = node.getStringValue(field.substring(4));
1692            val = getWAP(val);
1693            rtn = val;
1694        }
1695        // end old
1696
return rtn;
1697    }
1698    /**
1699     * Like getValue, but without the 'old' code (short_ html_ etc). This is for
1700     * protected use, when you are sure this is not used, and you can
1701     * avoid the overhead.
1702     *
1703     * @since MMBase-1.6
1704     * @see #getValue
1705     */

1706
1707    protected Object JavaDoc getObjectValue(MMObjectNode node, String JavaDoc field) {
1708        Object JavaDoc rtn = null;
1709        int pos1 = field.indexOf('(');
1710        if (pos1 != -1) {
1711            int pos2 = field.lastIndexOf(')');
1712            if (pos2 != -1) {
1713                String JavaDoc name = field.substring(pos1 + 1, pos2);
1714                String JavaDoc function = field.substring(0, pos1);
1715                if (log.isDebugEnabled()) {
1716                    log.debug("function = '" + function + "', fieldname = '" + name + "'");
1717                }
1718                List a = new ArrayList();
1719                a.add(name);
1720                rtn = getFunctionValue(node, function, a);
1721
1722            }
1723        }
1724        return rtn;
1725    }
1726
1727    /**
1728     * Parses string containing function parameters.
1729     * The parameters must be separated by ',' or ';' and may be functions
1730     * themselves (i.e. a functionname, followed by a parameter list between
1731     * parenthesis).
1732     *
1733     * @param fields The string, containing function parameters.
1734     * @return List of function parameters (may be functions themselves).
1735     * @deprecated use executeFunction(node, function, list)
1736     */

1737    protected Vector getFunctionParameters(String JavaDoc fields) {
1738        int commapos = 0;
1739        int nested = 0;
1740        Vector v = new Vector();
1741        int i;
1742        if (log.isDebugEnabled()) log.debug("Fields=" + fields);
1743        for(i = 0; i<fields.length(); i++) {
1744            if ((fields.charAt(i)==',') || (fields.charAt(i)==';')){
1745                if(nested==0) {
1746                    v.add(fields.substring(commapos,i).trim());
1747                    commapos=i+1;
1748                }
1749            }
1750            if (fields.charAt(i)=='(') {
1751                nested++;
1752            }
1753            if (fields.charAt(i)==')') {
1754                nested--;
1755            }
1756        }
1757        if (i>0) {
1758            v.add(fields.substring(commapos).trim());
1759        }
1760        return v;
1761    }
1762
1763    /**
1764     * Executes a 'function' on a MMObjectNode. The function is
1765     * identified by a string, and its arguments are passed by a List.
1766     *
1767     * The function 'info' should exist, and this will return a Map
1768     * with descriptions of the possible functions.
1769     *
1770     * Call {@link #addFunction} in your extension if you want to add functions.
1771     *
1772     * @param node The node on which the function must be executed
1773     * @param functionName The string identifying the funcion
1774     * @param parameters The list with function argument or null (which means 'no arguments')
1775     *
1776     * @see #executeFunction
1777     * @since MMBase-1.6
1778     */

1779    // package because called from MMObjectNode
1780
final Object JavaDoc getFunctionValue(MMObjectNode node, String JavaDoc functionName, List parameters) {
1781        if (parameters == null) parameters = new ArrayList();
1782        // for backwards compatibility (calling with string function with more than one argument)
1783
if (parameters.size() == 1 && parameters.get(0) instanceof String JavaDoc) {
1784            String JavaDoc arg = (String JavaDoc) parameters.get(0);
1785            Object JavaDoc result = executeFunction(node, functionName, arg);
1786            if (result != null) {
1787                return result;
1788            }
1789            parameters = StringSplitter.splitFunctions(arg);
1790        }
1791        Function function = getFunction(node, functionName);
1792        if (function != null) {
1793            return function.getFunctionValueWithList(parameters);
1794        } else {
1795            // fallback
1796
return executeFunction(node, functionName, parameters);
1797        }
1798    }
1799
1800    /**
1801     * Instantiates a Function object for a certain function on a certain node of this type.
1802     * @param node The Node for on which the function must work
1803     * @param functionName Name of the request function.
1804     * @return a Function object or <code>null</code> if no such function.
1805     * @since MMBase-1.8
1806     */

1807    protected Function getFunction(MMObjectNode node, String JavaDoc functionName) {
1808        Function function = getFunction(functionName);
1809        if (function instanceof NodeFunction) {
1810            return ((NodeFunction) function).newInstance(node);
1811        } else {
1812            return null;
1813        }
1814    }
1815
1816    /**
1817     * Returns all Functions which are available (or at least known to be available) on a Node.
1818     * @since MMBase-1.8
1819     */

1820    protected Collection getFunctions(MMObjectNode node) {
1821        Collection builderFunctions = getFunctions();
1822        Collection nodeFunctions = new HashSet();
1823        for (Iterator i = builderFunctions.iterator(); i.hasNext();) {
1824            Object JavaDoc function = i.next();
1825            if (function instanceof NodeFunction) {
1826                nodeFunctions.add(((NodeFunction) function).newInstance(node));
1827            }
1828        }
1829        return nodeFunctions;
1830    }
1831
1832    /**
1833     *
1834     * @inheritDoc
1835     * @since MMBase-1.8
1836     */

1837    protected Function newFunctionInstance(String JavaDoc name, Parameter[] parameters, ReturnType returnType) {
1838        return new NodeFunction(name, parameters, returnType) {
1839                public Object JavaDoc getFunctionValue(Node node, Parameters parameters) {
1840                    return MMObjectBuilder.this.executeFunction(getCoreNode(MMObjectBuilder.this, node),
1841                                                                name,
1842                                                                parameters.subList(0, parameters.size() - 1) // removes the node-argument, some legacy impl. get confused
1843
);
1844                }
1845            };
1846    }
1847
1848    /**
1849     * Executes a function on the field of a node, and returns the result.
1850     * This method is called by the builder's {@link #getValue} method.
1851     * Derived builders should override this method to provide additional functions.
1852     *
1853     * @since MMBase-1.6
1854     * @throws IllegalArgumentException if the argument List does not
1855     * fit the function
1856     * @see #executeFunction
1857     */

1858    protected Object JavaDoc executeFunction(MMObjectNode node, String JavaDoc function, List arguments) {
1859        if (log.isDebugEnabled()) {
1860            log.debug("Executing function " + function + " on node " + node.getNumber() + " with argument " + arguments);
1861        }
1862
1863        if (function.equals("info")) {
1864            Map info = new HashMap();
1865            Iterator i = getFunctions(node).iterator();
1866            while (i.hasNext()) {
1867                Function f = (Function) i.next();
1868                info.put(f.getName(), f.getDescription());
1869            }
1870            info.put("info", "(functionname) Returns information about a certain 'function'. Or a map of all function if no arguments.");
1871            if (arguments == null || arguments.size() == 0 || arguments.get(0) == null) {
1872                log.info("returing " + info);
1873                return info;
1874            } else {
1875                return info.get(arguments.get(0));
1876            }
1877        } else if (function.equals("wrap")) {
1878            if (arguments.size() < 2) throw new IllegalArgumentException JavaDoc("wrap function needs 2 arguments (currently:" + arguments.size() + " : " + arguments + ")");
1879            try {
1880                String JavaDoc val = node.getStringValue((String JavaDoc)arguments.get(0));
1881                int wrappos = Integer.parseInt((String JavaDoc)arguments.get(1));
1882                return wrap(val, wrappos);
1883            } catch(Exception JavaDoc e) {}
1884
1885        } else if (function.equals("substring")) {
1886            if (arguments.size() < 2) throw new IllegalArgumentException JavaDoc("substring function needs 2 or 3 arguments (currently:" + arguments.size() + " : " + arguments + ")");
1887            try {
1888                String JavaDoc val = node.getStringValue((String JavaDoc)arguments.get(0));
1889                int len = Integer.parseInt((String JavaDoc)arguments.get(1));
1890                if (arguments.size() > 2) {
1891                    String JavaDoc filler = (String JavaDoc)arguments.get(2);
1892                    return substring(val, len, filler);
1893                } else {
1894                    return substring(val, len, null);
1895                }
1896            } catch(Exception JavaDoc e) {
1897                log.debug(Logging.stackTrace(e));
1898                return e.toString();
1899            }
1900        } else if (function.equals("smartpath")) {
1901            try {
1902                String JavaDoc documentRoot = (String JavaDoc) arguments.get(0);
1903                String JavaDoc path = (String JavaDoc) arguments.get(1);
1904                String JavaDoc version = (String JavaDoc) arguments.get(2);
1905                if (version != null) {
1906                    if (version.equals("")) {
1907                        version = null;
1908                    }
1909                }
1910                return getSmartPath(documentRoot, path, "" + node.getNumber(), version);
1911            } catch(Exception JavaDoc e) {
1912                log.error("Evaluating smartpath for "+node.getNumber()+" went wrong " + e.toString());
1913            }
1914        }
1915
1916        String JavaDoc field = "";
1917        if (arguments != null && arguments.size() > 0) {
1918            Object JavaDoc o = arguments.get(0);
1919            if (o instanceof String JavaDoc) {
1920                field = (String JavaDoc) o;
1921            }
1922        }
1923
1924        // time functions
1925
if(function.equals("date")) { // date
1926
int v = node.getIntValue(field);
1927            return DateSupport.date2string(v);
1928        } else if (function.equals("time")) { // time hh:mm
1929
int v = node.getIntValue(field);
1930            return DateSupport.getTime(v);
1931        } else if (function.equals("timesec")) { // timesec hh:mm:ss
1932
int v = node.getIntValue(field);
1933            return DateSupport.getTimeSec(v);
1934        } else if (function.equals("longmonth")) { // longmonth September
1935
int v = node.getIntValue(field);
1936            return DateStrings.ENGLISH_DATESTRINGS.getMonth(DateSupport.getMonthInt(v));
1937        } else if (function.equals("monthnumber")) {
1938            int v = node.getIntValue(field);
1939            return "" + (DateSupport.getMonthInt(v)+1);
1940        } else if (function.equals("month")) { // month Sep
1941
int v = node.getIntValue(field);
1942            return DateStrings.DUTCH_DATESTRINGS.getShortMonth(DateSupport.getMonthInt(v));
1943        } else if (function.equals("weekday")) { // weekday Sunday
1944
int v = node.getIntValue(field);
1945            return DateStrings.DUTCH_DATESTRINGS.getDay(DateSupport.getWeekDayInt(v));
1946        } else if (function.equals("shortday")) { // shortday Sun
1947
int v = node.getIntValue(field);
1948            return DateStrings.DUTCH_DATESTRINGS.getShortDay(DateSupport.getWeekDayInt(v));
1949        } else if (function.equals("day")) { // day 4
1950
int v = node.getIntValue(field);
1951            return ""+DateSupport.getDayInt(v);
1952        } else if (function.equals("shortyear")) { // year 01
1953
int v = node.getIntValue(field);
1954            return (DateSupport.getYear(v)).substring(2);
1955        } else if (function.equals("year")) { // year 2001
1956
int v = node.getIntValue(field);
1957            return DateSupport.getYear(v);
1958        } else if (function.equals("thisdaycurtime")) { //
1959
int curtime=node.getIntValue(field);
1960            // gives us the next full day based on time (00:00)
1961
int days = curtime/(3600*24);
1962            return "" + ((days*(3600*24))-3600);
1963        } else if (function.equals("age")) {
1964            Integer JavaDoc val = new Integer JavaDoc(node.getAge());
1965            return val.toString();
1966        } else if (function.equals("wap")) {
1967            String JavaDoc val = node.getStringValue(field);
1968            return getWAP(val);
1969        } else if (function.equals("html")) {
1970            String JavaDoc val = node.getStringValue(field);
1971            return getHTML(val);
1972        } else if (function.equals("shorted")) {
1973            String JavaDoc val = node.getStringValue(field);
1974            return getShort(val,32);
1975        } else if (function.equals("uppercase")) {
1976            String JavaDoc val = node.getStringValue(field);
1977            return val.toUpperCase();
1978        } else if (function.equals("lowercase")) {
1979            String JavaDoc val = node.getStringValue(field);
1980            return val.toLowerCase();
1981        } else if (function.equals("hostname")) {
1982            String JavaDoc val = node.getStringValue(field);
1983            return hostname_function(val);
1984        } else if (function.equals("urlencode")) {
1985            String JavaDoc val = node.getStringValue(field);
1986            return getURLEncode(val);
1987        } else if (function.startsWith("wrap_")) {
1988            String JavaDoc val = node.getStringValue(field);
1989            try {
1990                int wrappos = Integer.parseInt(function.substring(5));
1991                return wrap(val, wrappos);
1992            } catch(Exception JavaDoc e) {}
1993        } else if (function.equals("currency_euro")) {
1994             double val = node.getDoubleValue(field);
1995             NumberFormat JavaDoc nf = NumberFormat.getNumberInstance (Locale.GERMANY);
1996             return "" + nf.format(val);
1997        } else {
1998            StringBuffer JavaDoc arg = new StringBuffer JavaDoc(field);
1999             if (arguments != null) {
2000                 for (int i = 1; i < arguments.size(); i++) {
2001                     if (arg.length() > 0) arg.append(',');
2002                     arg.append(arguments.get(i));
2003                 }
2004             }
2005             return executeFunction(node, function, arg.toString());
2006        }
2007        return null;
2008    }
2009
2010    /**
2011     * Executes a function on the field of a node, and returns the result.
2012     * This method is called by the builder's {@link #getValue} method.
2013     *
2014     * current functions are:<br />
2015     * on dates: date, time, timesec, longmonth, month, monthnumber, weekday, shortday, day, yearhort year<br />
2016     * on text: wap, html, shorted, uppercase, lowercase <br />
2017     * on node: age() <br />
2018     * on numbers: wrap_&lt;int&gt;, currency_euro <br />
2019     *
2020     * @param node the node whose fields are queries
2021     * @param field the fieldname that is requested
2022     * @return the result of the 'function', or null if no valid functions could be determined.
2023     * @deprecated use {@link #getFunction(MMObjectNode, String)}
2024     */

2025    protected Object JavaDoc executeFunction(MMObjectNode node, String JavaDoc function, String JavaDoc field) {
2026        if (log.isDebugEnabled()) {
2027            log.debug("Executing function " + function + " on node " + node.getNumber() + " with argument " + field);
2028        }
2029        return null;
2030    }
2031
2032    /**
2033     * Returns all relations of a node.
2034     * This returns the relation objects, not the objects related to.
2035     * Note that the relations returned are always of builder type 'InsRel', even if they are really from a derived builser such as AuthRel.
2036     * @param src the number of the node to obtain the relations from
2037     * @return a <code>Vector</code> with InsRel nodes
2038     * @todo Return-type and name of this function are not sound.
2039     */

2040    public Vector getRelations_main(int src) {
2041        InsRel bul = mmb.getInsRel();
2042        if (bul == null) {
2043            log.error("getMMObject(): InsRel not yet loaded");
2044            return null;
2045        }
2046        return bul.getRelationsVector(src);
2047    }
2048
2049    /**
2050     * Return the default url of this object.
2051     * The basic value returned is <code>null</code>.
2052     * @param src the number of the node to obtain the url from
2053     * @return the basic url as a <code>String</code>, or <code>null</code> if unknown.
2054     */

2055    public String JavaDoc getDefaultUrl(int src) {
2056        return null;
2057    }
2058
2059    /**
2060     * Returns the path to use for TREEPART, TREEFILE, LEAFPART and LEAFFILE.
2061     * The system searches in a provided base path for a filename that matches the supplied number/alias of
2062     * a node (possibly extended with a version number). See the documentation on the TREEPART SCAN command for more info.
2063     * @move maybe to a different SmartPathFunction class?
2064     * @param documentRoot the root of the path to search
2065     * @param path the subpath of the path to search
2066     * @param nodeNumber the number or alias of the node to filter on
2067     * @param version the version number (or <code>null</code> if not applicable) to filter on
2068     * @return the found path as a <code>String</code>, or <code>null</code> if not found
2069     * This method should be added to the bridge so jsp can make use of it.
2070     * This method can be overriden to make an even smarter search possible.
2071     */

2072    public String JavaDoc getSmartPath(String JavaDoc documentRoot, String JavaDoc path, String JavaDoc nodeNumber, String JavaDoc version) {
2073        File JavaDoc dir = new File JavaDoc(documentRoot+path);
2074        if (version != null) nodeNumber += "." + version;
2075        String JavaDoc[] matches = dir.list( new SPartFileFilter( nodeNumber ));
2076        if ((matches == null) || (matches.length == 0)) {
2077            return null;
2078        }
2079        return path + matches[0] + File.separator;
2080    }
2081
2082    /**
2083     * Get the next object key (unique index for an object).
2084     * @return an <code>int</code> value that is the next available key for an object.
2085     * @deprecated use MMBase.getStorageManager().createKey()
2086     */

2087    public int getDBKey() {
2088        return mmb.getStorageManager().createKey();
2089    }
2090
2091    /**
2092     * Get the name of this mmserver from the MMBase Root
2093     * @return a <code>String</code> which is the server's name
2094     */

2095    public String JavaDoc getMachineName() {
2096        return mmb.getMachineName();
2097    }
2098
2099    /**
2100     * Called when a remote node is changed.
2101     * Should be called by subclasses if they override it.
2102     * @param machine Name of the machine that changed the node.
2103     * @param number Number of the changed node as a <code>String</code>
2104     * @param builder type of the changed node
2105     * @param ctype command type, 'c'=changed, 'd'=deleted', 'r'=relations changed, 'n'=new
2106     * @return always <code>true</code>
2107     * @deprecated use notify(NodeEvent) in stead
2108     */

2109    public boolean nodeRemoteChanged(String JavaDoc machine, String JavaDoc number, String JavaDoc builder, String JavaDoc ctype) {
2110        // signal all the other objects that have shown interest in changes of nodes of this builder type.
2111
for (Iterator i = remoteObservers.iterator(); i.hasNext();) {
2112            MMBaseObserver o = (MMBaseObserver) i.next();
2113            if (o != this) {
2114                o.nodeRemoteChanged(machine, number, builder, ctype);
2115            } else {
2116                log.warn(getClass().getName() + " " + toString() + " observes itself");
2117            }
2118        }
2119        return true;
2120    }
2121
2122    /**
2123     * Called when a local node is changed.
2124     * Should be called by subclasses if they override it.
2125     * @param machine Name of the machine that changed the node.
2126     * @param number Number of the changed node as a <code>String</code>
2127     * @param builder type of the changed node
2128     * @param ctype command type, 'c'=changed, 'd'=deleted', 'r'=relations changed, 'n'=new
2129     * @return always <code>true</code>
2130     * @deprecated use notify(NodeEvent) in stead
2131     */

2132
2133   public boolean nodeLocalChanged(String JavaDoc machine, String JavaDoc number, String JavaDoc builder, String JavaDoc ctype) {
2134       // signal all the other objects that have shown interest in changes of nodes of this builder type.
2135
synchronized(localObservers) {
2136           for (Iterator i = localObservers.iterator(); i.hasNext();) {
2137               MMBaseObserver o = (MMBaseObserver)i.next();
2138               if (o != this) {
2139                   o.nodeLocalChanged(machine, number, builder, ctype);
2140               } else {
2141                   log.warn(getClass().getName() + " " + toString() + " observes itself");
2142               }
2143           }
2144       }
2145
2146       return true;
2147   }
2148
2149    /**
2150     * Called when a local field is changed.
2151     * @param number Number of the changed node as a <code>String</code>
2152     * @param builder type of the changed node
2153     * @param field name of the changed field
2154     * @param value value it changed to
2155     * @return always <code>true</code>
2156     */

2157    public boolean fieldLocalChanged(String JavaDoc number, String JavaDoc builder, String JavaDoc field, String JavaDoc value) {
2158        if (log.isDebugEnabled()) {
2159            log.debug("FLC=" + number + " BUL=" + builder + " FIELD=" + field + " value=" + value);
2160        }
2161        return true;
2162    }
2163
2164    /**
2165     * Adds a remote observer to this builder.
2166     * The observer is notified whenever an object of this builder is changed, added, or removed.
2167     * @return always <code>true</code>
2168     * @deprecated use the new event system as well. check out addEventListener(Object listener) or MMBase.addEventListener(EventListener listener)
2169     */

2170    public boolean addRemoteObserver(MMBaseObserver obs) {
2171        if (!remoteObservers.contains(obs)) {
2172            remoteObservers.add(obs);
2173        }
2174        return true;
2175    }
2176
2177    /**
2178     * Adds a local observer to this builder.
2179     * The observer is notified whenever an object of this builder is changed, added, or removed.
2180     * @return always <code>true</code>
2181     * @deprecated use the new event system as well. check out addEventListener(Object listener) or MMBase.addEventListener(EventListener listener)
2182     */

2183    public boolean addLocalObserver(MMBaseObserver obs) {
2184        if (!localObservers.contains(obs)) {
2185            localObservers.add(obs);
2186        }
2187        return true;
2188    }
2189
2190    /**
2191     * @since MMBase-1.8
2192     */

2193    public boolean removeLocalObserver(MMBaseObserver obs) {
2194        return localObservers.remove(obs);
2195    }
2196    /**
2197     * @since MMBase-1.8
2198     */

2199    public boolean removeRemoteObserver(MMBaseObserver obs) {
2200        return remoteObservers.remove(obs);
2201    }
2202
2203    /**
2204     * Used to create a default teaser by any builder
2205     * @deprecated Will be removed?
2206     */

2207    public MMObjectNode getDefaultTeaser(MMObjectNode node, MMObjectNode tnode) {
2208        log.warn("getDefaultTeaser(): Generate Teaser,Should be overridden");
2209        return tnode;
2210    }
2211
2212    /**
2213     * Waits until a node is changed (multicast).
2214     * @param node the node to wait for
2215     */

2216    /*
2217    public boolean waitUntilNodeChanged(MMObjectNode node) {
2218        return mmb.mmc.waitUntilNodeChanged(node);
2219    }
2220    */

2221
2222    /**
2223     * Obtains a list of string values by performing the provided command and parameters.
2224     * This method is SCAN related and may fail if called outside the context of the SCAN servlet.
2225     * @param sp The PageInfo (containing http and user info) that calls the function
2226     * @param tagger a Hashtable of parameters (name-value pairs) for the command
2227     * @param tok a list of strings that describe the (sub)command to execute
2228     * @return a <code>Vector</code> containing the result values as a <code>String</code>
2229     */

2230    public Vector getList(PageInfo sp, StringTagger tagger, StringTokenizer tok) {
2231        throw new UnsupportedOperationException JavaDoc(getClass().getName() +" should override the getList method (you've probably made a typo)");
2232    }
2233
2234
2235    /**
2236     * Obtains a string value by performing the provided command.
2237     * The command can be called:
2238     * <ul>
2239     * <li>by SCAN : $MOD-MMBASE-BUILDER-[buildername]-[command]</li>
2240     * <li>in jsp : cloud.getNodeManager(buildername).getInfo(command);</li>
2241     * </lu>
2242     * This method is SCAN related and some commands may fail if called outside the context of the SCAN servlet.
2243     * @param sp The PageInfo (containing http and user info) that calls the function
2244     * @param tok a list of strings that describe the (sub)command to execute
2245     * @return the result value as a <code>String</code>
2246     */

2247    public String JavaDoc replace(PageInfo sp, StringTokenizer tok) {
2248        log.warn("replace(): replace called should be overridden");
2249        return "";
2250    }
2251
2252    /**
2253     * The hook that passes all form related pages to the correct handler.
2254     * This method is SCAN related and may fail if called outside the context of the SCAN servlet.
2255     * The methood is currentkly called by the MMEDIT module, whenever a 'PRC-CMD-BUILDER-...' command
2256     * is encountered in the list of commands to be processed.
2257     * @param sp The PageInfo (containing http and user info) that calls the function
2258     * @param command a list of strings that describe the (sub)command to execute (the portion after ' PRC-CMD-BUILDER')
2259     * @param cmds the commands (PRC-CMD) that are iurrently being processed, including the current command.
2260     * @param vars variables (PRC-VAR) thatw ere set to be used during processing.
2261     * @return the result value as a <code>String</code>
2262     */

2263    public boolean process(PageInfo sp, StringTokenizer command, Hashtable cmds, Hashtable vars) {
2264        return false;
2265    }
2266
2267    /**
2268     * Set description of the builder
2269     * @param e the description text
2270     */

2271    public void setDescription(String JavaDoc e) {
2272        this.description=e;
2273        update();
2274    }
2275
2276    /**
2277     * Set descriptions of the builder
2278     * @param e a <code>Hashtable</code> containing the descriptions
2279     */

2280    public void setDescriptions(Hashtable e) {
2281        this.descriptions=e;
2282        update();
2283    }
2284
2285    /**
2286     * Get description of the builder
2287     * @return the description text
2288     */

2289    public String JavaDoc getDescription() {
2290        return description;
2291    }
2292
2293    /**
2294     * Gets description of the builder, using the specified language.
2295     * @param lang The language requested
2296     * @return the descriptions in that language, or <code>null</code> if it is not avaialble
2297     */

2298    public String JavaDoc getDescription(String JavaDoc lang) {
2299        if (descriptions == null) return null;
2300        String JavaDoc retval =(String JavaDoc)descriptions.get(lang);
2301        if (retval == null){
2302            return getDescription();
2303        }
2304        return retval;
2305    }
2306
2307    /**
2308     * Get descriptions of the builder
2309     * @return a <code>Hashtable</code> containing the descriptions
2310     */

2311    public Hashtable getDescriptions() {
2312        return descriptions;
2313    }
2314
2315    /**
2316     * Sets search Age.
2317     * @param age the search age as a <code>String</code>
2318     */

2319    public void setSearchAge(String JavaDoc age) {
2320        this.searchAge=age;
2321        update();
2322    }
2323
2324    /**
2325     * Gets search Age
2326     * @return the search age as a <code>String</code>
2327     */

2328    public String JavaDoc getSearchAge() {
2329        return searchAge;
2330    }
2331
2332    /**
2333     * Gets short name of the builder, using the specified language.
2334     * @param lang The language requested
2335     * @return the short name in that language, or <code>null</code> if it is not available
2336     */

2337    public String JavaDoc getSingularName(String JavaDoc lang) {
2338    String JavaDoc tmp = null;
2339        if (singularNames != null) {
2340            tmp = (String JavaDoc)singularNames.get(lang);
2341            if (tmp == null) tmp = (String JavaDoc)singularNames.get(mmb.getLanguage());
2342            if (tmp == null) tmp = (String JavaDoc)singularNames.get("en");
2343    }
2344        if (tmp == null) tmp = tableName;
2345        return tmp;
2346    }
2347
2348    /**
2349     * Gets short name of the builder in the current default language.
2350     * If the current language is not available, the "en" version is returned instead.
2351     * If that name is not available, the internal builder name (table name) is returned.
2352     * @return the short name in either the default language or in "en"
2353     */

2354    public String JavaDoc getSingularName() {
2355        return getSingularName(mmb.getLanguage());
2356    }
2357
2358    /**
2359     * Gets long name of the builder, using the specified language.
2360     * @param lang The language requested
2361     * @return the long name in that language, or <code>null</code> if it is not available
2362     */

2363    public String JavaDoc getPluralName(String JavaDoc lang) {
2364        String JavaDoc tmp = null;
2365        if (pluralNames != null){
2366        tmp= (String JavaDoc)pluralNames.get(lang);
2367            if (tmp == null) tmp = (String JavaDoc)pluralNames.get(mmb.getLanguage());
2368            if (tmp == null) tmp = (String JavaDoc)pluralNames.get("en");
2369            if (tmp == null) tmp = getSingularName(lang);
2370    }
2371        if (tmp == null) tmp = tableName;
2372        return tmp;
2373    }
2374
2375    /**
2376     * Gets long name of the builder in the current default language.
2377     * If the current language is not available, the "en" version is returned instead.
2378     * If that name is not available, the singular name is returned.
2379     * @return the long name in either the default language or in "en"
2380     */

2381    public String JavaDoc getPluralName() {
2382        return getPluralName(mmb.getLanguage());
2383    }
2384
2385    /**
2386     * Returns the classname of this builder
2387     * @deprecated don't use
2388     */

2389    public String JavaDoc getClassName() {
2390        return this.getClass().getName();
2391    }
2392
2393    /**
2394     * Send a signal to other servers that a field was changed.
2395     * @param node the node the field was changed in
2396     * @param fieldName the name of the field that was changed
2397     * @return always <code>true</code>
2398     */

2399    public boolean sendFieldChangeSignal(MMObjectNode node,String JavaDoc fieldName) {
2400        // we need to find out what the DBState is of this field so we know
2401
// who to notify of this change
2402
int state=getDBState(fieldName);
2403        log.debug("Changed field="+fieldName+" dbstate="+state);
2404
2405        // still a large hack need to figure out remote changes
2406
if (state==0) {}
2407        // convert the field to a string
2408

2409        int type=getDBType(fieldName);
2410        String JavaDoc value="";
2411        if ((type==Field.TYPE_INTEGER) || (type==Field.TYPE_NODE)) {
2412            value=""+node.getIntValue(fieldName);
2413        } else if (type==Field.TYPE_STRING) {
2414            value=node.getStringValue(fieldName);
2415        } else {
2416            // should be mapped to the builder
2417
}
2418
2419        fieldLocalChanged("" + node.getNumber(), tableName, fieldName, value);
2420        //mmb.mmc.changedNode(node.getNumber(),tableName,"f");
2421
return true;
2422    }
2423
2424    /**
2425     * Send a signal to other servers that a new node was created.
2426     * @param tableName the table in which a node was edited (?)
2427     * @param number the number of the new node
2428     * @return always <code>true</code>
2429     */

2430    /*
2431    public boolean signalNewObject(String tableName,int number) {
2432        if (mmb.mmc!=null) {
2433            mmb.mmc.changedNode(number,tableName,"n");
2434        }
2435        return true;
2436    }
2437    */

2438
2439
2440    /**
2441     * Sets a list of singular names (language - value pairs)
2442     */

2443    public void setSingularNames(Hashtable names) {
2444        singularNames=names;
2445        update();
2446    }
2447
2448    /**
2449     * Gets a list of singular names (language - value pairs)
2450     */

2451    public Hashtable getSingularNames() {
2452        return singularNames;
2453    }
2454
2455    /**
2456     * Sets a list of plural names (language - value pairs)
2457     */

2458    public void setPluralNames(Hashtable names) {
2459        pluralNames=names;
2460        update();
2461    }
2462
2463    /**
2464     * Gets a list of plural names (language - value pairs)
2465     */

2466    public Hashtable getPluralNames() {
2467        return pluralNames;
2468    }
2469
2470    /**
2471     * Get text from a blob field. This function is called to 'load' a field into the node, because
2472     * it was not loaded together with the node, because it is supposed to be too big.
2473     * @param fieldName name of the field
2474     * @param node
2475     * @return a <code>String</code> containing the complate contents of a field as text.
2476     * @since MMBase-1.8
2477     */

2478    protected String JavaDoc getShortedText(String JavaDoc fieldName, MMObjectNode node) {
2479        if (node.getNumber() < 0) return null; // capture calls from temporary nodes
2480
try {
2481            return mmb.getStorageManager().getStringValue(node, getField(fieldName));
2482        } catch (StorageException se) {
2483            log.error(se.getMessage());
2484            log.error(Logging.stackTrace(se));
2485            return null;
2486        }
2487    }
2488
2489    /**
2490     * Get binary data of a blob field. This function is called to 'load' a field into the node, because
2491     * it was not loaded together with the node, because it is supposed to be too big.
2492     * @param fieldName name of the field
2493     * @param node
2494     * @return an array of <code>byte</code> containing the complete contents of the field.
2495     * @since MMBase-1.8
2496     */

2497    protected byte[] getShortedByte(String JavaDoc fieldName, MMObjectNode node) {
2498        if (node.getNumber() < 0) return null; // capture calls from temporary nodes
2499
try {
2500            return mmb.getStorageManager().getBinaryValue(node, getField(fieldName));
2501        } catch (StorageException se) {
2502            log.error(se.getMessage());
2503            log.error(Logging.stackTrace(se));
2504            return null;
2505        }
2506    }
2507
2508    /**
2509     * Sets a key/value pair in the main values of this node.
2510     * Note that if this node is a node in cache, the changes are immediately visible to
2511     * everyone, even if the changes are not committed.
2512     * The fieldname is added to the (public) 'changed' vector to track changes.
2513     * @param fieldName the name of the field to change
2514     * @param node The node on which to change the field (the new value is in this node)
2515     * @param originalValue the value which was original in the field
2516     * @return <code>true</code> When an update is required(when changed),
2517     * <code>false</code> if original value was set back into the field.
2518     */

2519    public boolean setValue(MMObjectNode node, String JavaDoc fieldName, Object JavaDoc originalValue) {
2520        return setValue(node, fieldName);
2521    }
2522
2523    /**
2524     * Provides additional functionality when setting field values.
2525     * This method is called whenever a Node of the builder's type tries to change a value.
2526     * It allows the system to add functionality such as checking valid data.
2527     * Derived builders should override this method if they want to add functionality.
2528     * @param node the node whose fields are changed
2529     * @param fieldName the fieldname that is changed
2530     * @return <code>true</code> if the call was handled.
2531     */

2532    public boolean setValue(MMObjectNode node, String JavaDoc fieldName) {
2533        return true;
2534    }
2535
2536    /**
2537     * Returns a HTML-version of a string.
2538     * This replaces a number of tokens with HTML sequences.
2539     * The default output does not match well with the new xhtml standards (ugly html), nor does it replace all tokens.
2540     *
2541     * Default replacements can be overridden by setting the builder properties in your <builder>.xml:
2542     *
2543     * html.alinea
2544     * html.endofline
2545     *
2546     * Example:
2547     * <properties>
2548     * <property name="html.alinea"> &lt;br /&gt; &lt;br /&gt;</property>
2549     * <property name="html.endofline"> &lt;br /&gt; </property>
2550     * </properties>
2551     *
2552     * @param body text to convert
2553     * @return the convert text
2554     * @deprecated
2555     */

2556
2557    protected String JavaDoc getHTML(String JavaDoc body) {
2558        String JavaDoc rtn="";
2559        if (body != null) {
2560            StringObject obj=new StringObject(body);
2561            // escape ampersand first
2562
obj.replace("&", "&amp;");
2563
2564            obj.replace("<","&lt;");
2565            obj.replace(">","&gt;");
2566            // escape dollar-sign (prevent SCAN code to be run)
2567
obj.replace("$","&#36;");
2568            // unquote ampersand and quotes (see escapeXML method)
2569
obj.replace("\"", "&quot;");
2570            obj.replace("'", "&#39;");
2571
2572            String JavaDoc alinea=getInitParameter("html.alinea");
2573            String JavaDoc endofline=getInitParameter("html.endofline");
2574
2575            if (alinea != null) {
2576                obj.replace("\r\n\r\n",alinea);
2577                obj.replace("\n\n",alinea);
2578            } else {
2579                obj.replace("\r\n\r\n", DEFAULT_ALINEA);
2580                obj.replace("\n\n", DEFAULT_ALINEA);
2581            }
2582
2583            if (endofline != null) {
2584                obj.replace("\r\n",endofline);
2585                obj.replace("\n",endofline);
2586            } else {
2587                obj.replace("\r\n", DEFAULT_EOL);
2588                obj.replace("\n", DEFAULT_EOL);
2589            }
2590
2591            rtn=obj.toString();
2592        }
2593        return rtn;
2594    }
2595
2596    /**
2597     * Returns a WAP-version of a string.
2598     * This replaces a number of tokens with WAP sequences.
2599     * @param body text to convert
2600     * @return the convert text
2601     */

2602    protected String JavaDoc getWAP( String JavaDoc body ) {
2603        String JavaDoc result = "";
2604        if( body != null ) {
2605            StringObject obj=new StringObject(body);
2606            obj.replace("\"","&#34;");
2607            obj.replace("&","&#38;#38;");
2608            obj.replace("'","&#39;");
2609            obj.replace("<","&#38;#60;");
2610            obj.replace(">","&#62;");
2611            result = obj.toString();
2612        }
2613        return result;
2614    }
2615
2616    /**
2617     * Returns a URLEncoded-version (MIME x-www-form-urlencoded) of a string.
2618     * This version uses the java.net.URLEncoder class to encode it.
2619     * @param body text to convert
2620     * @return the URLEncoded text
2621     */

2622    protected String JavaDoc getURLEncode(String JavaDoc body) {
2623        String JavaDoc rtn="";
2624        if (body != null) {
2625            rtn = URLEncoder.encode(body); // UTF8?
2626
}
2627        return rtn;
2628    }
2629
2630    /**
2631     * Support routine to return shorter strings.
2632     * Cuts a string to a amximum length if it exceeds the length specified.
2633     * @param str the string to shorten
2634     * @param len the maximum length
2635     * @return the (possibly shortened) string
2636     */

2637    public String JavaDoc getShort(String JavaDoc str,int len) {
2638        if (str.length()>len) {
2639            return str.substring(0,(len-3))+"...";
2640        } else {
2641            return str;
2642        }
2643    }
2644
2645    /**
2646     * Stores fields information of this table.
2647     * Asside from the fields supplied by the caller, a field 'otype' is added (if missing).
2648     *
2649     * @param f A List with fields (as CoreField objects) as defined by MMBase. This may not be in sync with the actual database table, about which Storage will report then.
2650     */

2651    public void setFields(List f) {
2652        fields.clear();
2653
2654        Iterator i = f.iterator();
2655        while (i.hasNext()) {
2656            CoreField def = (CoreField) i.next();
2657            String JavaDoc name = def.getName();
2658            def.setParent(this);
2659            fields.put(name.toLowerCase(), def);
2660        }
2661
2662        // should be TYPE_NODE ???
2663
if (fields.get(FIELD_OBJECT_TYPE) == null) {
2664            log.warn("Object 'otype' field is not defined. Please update your object.xml, or update '" + getConfigResource() + "' to extend object");
2665            // if not defined in XML (legacy?)
2666
// It does currently not work if otype is actually defined in object.xml (as a NODE field)
2667
CoreField def = Fields.createSystemField(FIELD_OBJECT_TYPE, Field.TYPE_NODE);
2668            def.setGUIName("Type");
2669            // here, we should set the DBPos to 2 and adapt those of the others fields
2670
def.setStoragePosition(2);
2671            def.getDataType().setRequired(true);
2672            i = f.iterator();
2673            while (i.hasNext()) {
2674                CoreField field = (CoreField) i.next();
2675                int pos = field.getStoragePosition();
2676                if (pos > 1) field.setStoragePosition(pos + 1);
2677            }
2678            def.setParent(this);
2679            def.finish();
2680            fields.put(FIELD_OBJECT_TYPE, def);
2681        }
2682        updateFields();
2683    }
2684
2685    /**
2686     * Sets the subpath of the builder's xml configuration file.
2687     */

2688    public void setXMLPath(String JavaDoc m) {
2689        xmlPath = m;
2690        update();
2691    }
2692
2693    /**
2694     * Retrieves the subpath of the builder's xml configuration file.
2695     * Needed for builders that reside in subdirectories in the builder configuration file directory.
2696     */

2697    public String JavaDoc getXMLPath() {
2698         return xmlPath;
2699    }
2700
2701    /**
2702     * @since MMBase-1.8
2703     */

2704
2705    public String JavaDoc getConfigResource() {
2706        return "builders/" + getXMLPath() + "/" + getTableName() + ".xml";
2707    }
2708
2709    /**
2710     * Gets the file that contains the configuration of this builder
2711     * @return the builders configuration File object
2712     * @deprecated Need something as getConfigResource in stead.
2713     */

2714    public File JavaDoc getConfigFile() {
2715        // what is the location of our builder?
2716
List files = ResourceLoader.getConfigurationRoot().getFiles(getConfigResource());
2717        if (files.size() == 0) {
2718            return null;
2719        } else {
2720            return (File JavaDoc) files.get(0);
2721        }
2722        //return null;
2723
}
2724
2725    /**
2726     * Set all builder properties
2727     * Changed properties will not be saved.
2728     * @param properties the properties to set
2729     */

2730    void setInitParameters(Hashtable properties) {
2731        this.properties = properties;
2732        update();
2733    }
2734
2735    /**
2736     * Get all builder properties
2737     * @return a <code>Hashtable</code> containing the current properties
2738     */

2739    public Hashtable getInitParameters() {
2740        return properties;
2741    }
2742
2743    
2744    /**
2745     * Get all builder properties and override properties through application context
2746     * @param contextPath path in application context where properties are located
2747     * @return a <code>Map</code> containing the current properties
2748     * @since MMBase 1.8.2
2749     */

2750    public Map getInitParameters(String JavaDoc contextPath) {
2751        Map map = new HashMap();
2752        map.putAll(getInitParameters());
2753        
2754        try {
2755            Map contextMap = ApplicationContextReader.getProperties(contextPath);
2756            if (!contextMap.isEmpty()) {
2757                map.putAll(contextMap);
2758            }
2759        } catch (javax.naming.NamingException JavaDoc ne) {
2760            log.debug("Can't obtain properties from application context: " + ne.getMessage());
2761        }
2762        return map;
2763    }
2764    
2765    /**
2766     * Set a single builder property
2767     * The propertie will not be saved.
2768     * @param name name of the property
2769     * @param value value of the property
2770     */

2771    public void setInitParameter(String JavaDoc name, String JavaDoc value) {
2772        if (properties == null) properties = new Hashtable();
2773        properties.put(name,value);
2774        update();
2775    }
2776
2777    /**
2778     * Retrieve a specific property.
2779     * @param name the name of the property to get
2780     * @return the value of the property as a <code>String</code>
2781     */

2782    public String JavaDoc getInitParameter(String JavaDoc name) {
2783        if (properties == null) {
2784            return null;
2785        } else {
2786            return (String JavaDoc)properties.get(name);
2787        }
2788    }
2789
2790    /**
2791     * Sets the version of this builder
2792     * @param i the version number
2793     */

2794    public void setVersion(int i) {
2795        version=i;
2796        update();
2797    }
2798
2799    /**
2800     * Retrieves the version of this builder
2801     * @return the version number
2802     */

2803    public int getVersion() {
2804        return version;
2805    }
2806
2807    /**
2808     * Retrieves the maintainer of this builder
2809     * @return the name of the maintainer
2810     */

2811    public String JavaDoc getMaintainer() {
2812        return maintainer;
2813    }
2814
2815    /**
2816     * Sets the maintainer of this builder
2817     * @param m the name of the maintainer
2818     */

2819    public void setMaintainer(String JavaDoc m) {
2820        maintainer=m;
2821        update();
2822    }
2823
2824    /**
2825     * hostname, parses the hostname from a url, so http://www.mmbase.org/bug
2826     * becomed www.mmbase.org
2827     * @deprecated Has nothing to do with mmbase nodes. Should be in org.mmbase.util
2828     */

2829    public String JavaDoc hostname_function(String JavaDoc url) {
2830        if (url.startsWith("http://")) {
2831            url=url.substring(7);
2832        }
2833        if (url.startsWith("https://")) {
2834            url=url.substring(8);
2835        }
2836        int pos=url.indexOf("/");
2837        if (pos!=-1) {
2838            url=url.substring(0,pos);
2839        }
2840        return url;
2841    }
2842
2843    /**
2844     * Wraps a string.
2845     * Inserts newlines (\n) into a string at periodic intervals, to simulate wrapping.
2846     * This also removes whitespace to the start of a line.
2847     * @param text the text to wrap
2848     * @param width the maximum width to wrap at
2849     * @return the wrapped tekst
2850     */

2851    public String JavaDoc wrap(String JavaDoc text,int width) {
2852        StringTokenizer tok;
2853        String JavaDoc word;
2854        StringBuffer JavaDoc dst=new StringBuffer JavaDoc();
2855        int pos;
2856
2857        tok=new StringTokenizer(text," \n\r",true);
2858        pos=0;
2859        while(tok.hasMoreTokens()) {
2860            word=tok.nextToken();
2861            if (word.equals("\n")) {
2862                pos=0;
2863            } else if (word.equals(" ")) {
2864                if (pos==0) {
2865                    word="";
2866                } else {
2867                    pos++;
2868                    if (pos>=width) {
2869                        word="\n";
2870                        pos=0;
2871                    }
2872                }
2873            } else {
2874                pos+=word.length();
2875                if (pos>=width) {
2876                    dst.append("\n");
2877                    pos=word.length();
2878                }
2879            }
2880            dst.append(word);
2881        }
2882        return dst.toString();
2883    }
2884
2885    /**
2886     * Gets a substring.
2887     * @param value the string to get a substring of
2888     * @param len the length of the substring
2889     * @param filler if not null, this field is used as a trailing tekst
2890     * of the created substring.
2891     * @return the substring
2892     */

2893    private String JavaDoc substring(String JavaDoc value,int len,String JavaDoc filler) {
2894        if (filler == null) {
2895            if (value.length()>len) {
2896                return value.substring(0,len);
2897            } else {
2898                return value;
2899            }
2900        } else {
2901            int len2 = filler.length();
2902            if ((value.length()+len2)>len) {
2903                return value.substring(0,(len-len2))+filler;
2904            } else {
2905                return value;
2906            }
2907        }
2908    }
2909
2910    /**
2911     * Implmenting a sensible toString is usefull for debugging.
2912     *
2913     * @since MMBase-1.6.2
2914     */

2915
2916    public String JavaDoc toString() {
2917        return getSingularName();
2918    }
2919
2920    /**
2921     * Equals must be implemented because of the list of MMObjectBuilder which is used for ancestors
2922     *
2923     * Declared the method final, because the instanceof operator is used. This is the only
2924     * MMObjectBuilder is frequently extended and subclasses will always break
2925     * the equals contract.
2926     * When subclasses require to implement the equals method then we should use
2927     * getClass() == o.getClass(), but this has its own issues. For more info, search for equality in Java
2928     *
2929     * @since MMBase-1.6.2
2930     */

2931    public final boolean equals(Object JavaDoc o) {
2932        if (o == this) return true;
2933        if (o == null) return false;
2934        if (o instanceof MMObjectBuilder) {
2935            MMObjectBuilder b = (MMObjectBuilder) o;
2936            return tableName.equals(b.tableName);
2937        }
2938        return false;
2939    }
2940
2941    /**
2942     * @see java.lang.Object#hashCode()
2943     */

2944    public int hashCode() {
2945        return tableName == null ? 0 : tableName.hashCode();
2946    }
2947
2948    /**
2949     * Implements for MMObjectNode
2950     * @since MMBase-1.6.2
2951     */

2952
2953    public String JavaDoc toString(MMObjectNode n) {
2954        return n.defaultToString();
2955    }
2956
2957    /**
2958     * Implements equals for nodes (this is in MMObjectBuilder because you cannot override MMObjectNode)
2959     *
2960     * @since MMBase-1.6.2
2961     */

2962
2963    public boolean equals(MMObjectNode o1, MMObjectNode o2) {
2964        return o1.defaultEquals(o2);
2965    }
2966
2967    /**
2968     * Implements for MMObjectNode
2969     * @since MMBase-1.6.2
2970     */

2971
2972    public int hashCode(MMObjectNode o) {
2973        return 127 * o.getNumber();
2974    }
2975
2976    /**
2977     * simple way to register a NodeEvent listener and a RelationEventListener
2978     * at the same time.
2979     * @see MMBase#addNodeRelatedEventsListener
2980     * @param listener
2981     * @since MMBase-1.8
2982     */

2983    public void addEventListener(org.mmbase.core.event.EventListener listener){
2984        mmb.addNodeRelatedEventsListener(getTableName(), listener);
2985    }
2986
2987    /**
2988     * @param listener
2989     * @since MMBase-1.8
2990     */

2991    public void removeEventListener(org.mmbase.core.event.EventListener listener){
2992        mmb.removeNodeRelatedEventsListener(getTableName(), listener);
2993    }
2994
2995    /**
2996     * @see org.mmbase.core.event.NodeEventListener#notify(org.mmbase.core.event.NodeEvent)
2997     * here we handle all the backward compatibility stuff.
2998     * this method covers for both node and relation events.
2999     * @since MMBase-1.8
3000     */

3001    public void notify(NodeEvent event) {
3002        if (log.isDebugEnabled()) {
3003            log.debug("" + this + " received node event " + event);
3004        }
3005        int type = event.getType();
3006        eventBackwardsCompatible(event.getMachine(), event.getNodeNumber(), type);
3007
3008    }
3009
3010
3011    /**
3012     * @since MMBase-1.8
3013     */

3014    public void notify(RelationEvent event) {
3015        if (log.isDebugEnabled()) {
3016            log.debug("" + this + " received relation event " + event);
3017        }
3018         //for backwards compatibilty: create relation changed calls
3019
if (event.getRelationSourceType().equals(getTableName())) {
3020             eventBackwardsCompatible(event.getMachine(), event.getRelationSourceNumber(), NodeEvent.TYPE_RELATION_CHANGE);
3021         }
3022         if (event.getRelationDestinationType().equals(getTableName())) {
3023             eventBackwardsCompatible(event.getMachine(), event.getRelationDestinationNumber(), NodeEvent.TYPE_RELATION_CHANGE);
3024         }
3025         
3026         //update the cache
3027
Integer JavaDoc changedNode = new Integer JavaDoc((event.getRelationDestinationType().equals(getTableName()) ? event.getRelationSourceNumber() : event.getRelationDestinationNumber()));
3028         MMObjectNode.delRelationsCache(changedNode);
3029     }
3030
3031    /**
3032     * @see org.mmbase.core.event.NodeEventListener#notify(org.mmbase.core.event.NodeEvent)
3033     * here we handle all the backward compatibility stuff.
3034     * this method covers for both node and relation events.
3035     * @since MMBase-1.8
3036     * @param event
3037     */

3038    private void eventBackwardsCompatible(String JavaDoc machineName, int nodeNumber, int eventType) {
3039        String JavaDoc ctype = NodeEvent.newTypeToOldType(eventType);
3040        boolean localEvent = mmb.getMachineName().equals(machineName);
3041
3042        if(localEvent) {
3043            nodeLocalChanged(machineName, "" + nodeNumber, getTableName(), ctype);
3044        } else {
3045            nodeRemoteChanged(machineName, "" + nodeNumber, getTableName(), ctype);
3046        }
3047    }
3048
3049}
3050
3051
3052
Popular Tags