KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > util > xml > BuilderReader


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.util.xml;
11
12 import java.util.*;
13 import org.w3c.dom.*;
14 import org.xml.sax.InputSource JavaDoc;
15 import org.mmbase.bridge.Field;
16 import org.mmbase.bridge.NodeManager;
17 import org.mmbase.core.CoreField;
18 import org.mmbase.core.util.Fields;
19 import org.mmbase.datatypes.*;
20 import org.mmbase.datatypes.util.xml.DataTypeReader;
21 import org.mmbase.module.core.MMBase;
22 import org.mmbase.module.core.MMObjectBuilder;
23 import org.mmbase.storage.util.Index;
24
25 import org.mmbase.util.LocalizedString;
26 import org.mmbase.util.XMLEntityResolver;
27 import org.mmbase.util.functions.*;
28 import org.mmbase.util.logging.*;
29
30 /**
31  * Used to parse and retrieve data from a builder configuration file.
32  * The parser support builders for builder dtd 1.1.
33  *
34  * @since MMBase 1.7
35  * @author Case Roole
36  * @author Rico Jansen
37  * @author Pierre van Rooden
38  * @author Michiel Meeuwissen
39  * @version $Id: BuilderReader.java,v 1.74.2.4 2006/11/27 20:48:03 nklasens Exp $
40  */

41 public class BuilderReader extends DocumentReader {
42
43     /** Public ID of the Builder DTD version 1.0 */
44     public static final String JavaDoc PUBLIC_ID_BUILDER_1_0 = "-//MMBase//DTD builder config 1.0//EN";
45     /** Public ID of the Builder DTD version 1.1 */
46     public static final String JavaDoc PUBLIC_ID_BUILDER_1_1 = "-//MMBase//DTD builder config 1.1//EN";
47
48     // deprecated builder dtds
49
private static final String JavaDoc PUBLIC_ID_BUILDER_1_0_FAULT = "-//MMBase/DTD builder config 1.0//EN";
50     private static final String JavaDoc PUBLIC_ID_BUILDER_OLD = "/MMBase - builder//";
51     private static final String JavaDoc PUBLIC_ID_BUILDER_1_1_FAULT = "-//MMBase/DTD builder config 1.1//EN";
52
53     /** DTD resource filename of the Builder DTD version 1.0 */
54     public static final String JavaDoc DTD_BUILDER_1_0 = "builder_1_0.dtd";
55     /** DTD resource filename of the Builder DTD version 1.1 */
56     public static final String JavaDoc DTD_BUILDER_1_1 = "builder_1_1.dtd";
57
58     /** Public ID of the most recent Builder DTD */
59     public static final String JavaDoc PUBLIC_ID_BUILDER = PUBLIC_ID_BUILDER_1_1;
60     /** DTD respource filename of the most recent Builder DTD */
61     public static final String JavaDoc DTD_BUILDER = DTD_BUILDER_1_1;
62
63     public static final String JavaDoc XSD_BUILDER_2_0 = "builder.xsd";
64     public static final String JavaDoc NAMESPACE_BUILDER_2_0 = "http://www.mmbase.org/xmlns/builder";
65     public static final String JavaDoc NAMESPACE_BUILDER = NAMESPACE_BUILDER_2_0;
66
67     private static final Logger log = Logging.getLoggerInstance(BuilderReader.class);
68
69     /**
70      * Register the namespace and XSD used by DataTypeConfigurer
71      * This method is called by XMLEntityResolver.
72      */

73     public static void registerSystemIDs() {
74         XMLEntityResolver.registerSystemID(NAMESPACE_BUILDER_2_0 + ".xsd", XSD_BUILDER_2_0, BuilderReader.class);
75     }
76
77
78     /**
79      * Register the Public Ids for DTDs used by BuilderReader
80      * This method is called by XMLEntityResolver.
81      */

82     public static void registerPublicIDs() {
83         // various builder dtd versions
84
XMLEntityResolver.registerPublicID(PUBLIC_ID_BUILDER_1_0, DTD_BUILDER_1_0, BuilderReader.class);
85         XMLEntityResolver.registerPublicID(PUBLIC_ID_BUILDER_1_1, DTD_BUILDER_1_1, BuilderReader.class);
86         //XMLEntityResolver.registerPublicID("-//MMBase//DTD builder config 2.0//EN", "builder_2_0.dtd", BuilderReader.class);
87

88         // legacy public IDs (wrong, don't use these)
89
XMLEntityResolver.registerPublicID(PUBLIC_ID_BUILDER_1_0_FAULT, DTD_BUILDER_1_0, BuilderReader.class);
90         XMLEntityResolver.registerPublicID(PUBLIC_ID_BUILDER_OLD, DTD_BUILDER_1_0, BuilderReader.class);
91         XMLEntityResolver.registerPublicID(PUBLIC_ID_BUILDER_1_1_FAULT, DTD_BUILDER_1_1, BuilderReader.class);
92     }
93
94     /**
95      * MMBase instance, used to load parent (extending) builders
96      */

97     private MMBase mmbase;
98
99     /**
100      * Parent builder.
101      * If assigned, the properties of this builder are used as 'defaults'
102      * and the fields of the builder are inherited.
103      * @since MMbase-1.6
104      */

105     private MMObjectBuilder parentBuilder;
106
107     /**
108      * If false, the parent builder could not be resolved.
109      * A builder with an unresolved parent is set to 'inactive', regardless of actual status
110      * The default value is false, as resolving Inheritance is mandatory when loading builders.
111      * @since MMbase-1.6
112      */

113     private boolean inheritanceResolved = false;
114
115
116     /**
117      * searchPositions and inputPositions are used to adminstrate 'occupied' positions (from
118      * editor/positions), which is used to find defaults if not specified.
119      * @since MMBase-1.7
120      */

121     private SortedSet searchPositions = new TreeSet();
122     private SortedSet inputPositions = new TreeSet();
123
124     /**
125      * @since MMBase-1.7
126      */

127     public BuilderReader(InputSource JavaDoc source, MMBase mmb) {
128         super(source, BuilderReader.class);
129         mmbase = mmb;
130         resolveInheritance();
131     }
132
133     /**
134      * @since MMBase-1.8
135      */

136     public BuilderReader(Document doc, MMBase mmb) {
137         super(doc);
138         mmbase = mmb;
139         resolveInheritance();
140     }
141
142     /**
143      * Resolves inheritance.
144      * If a builder 'extends' another builder, the parser attempts to
145      * retrieve a reference to this builder (using getParentBuilder).
146      * Note that if inheritance cannot be resolved, the builder cannot be activated.
147      * This method returns false if the builder to extend from is inactive.
148      * It throws a RuntimeException is the builder to extend from is not allowed as
149      * an parent builder.
150      *
151      * @since MMBase-1.6
152      * @return true if inheritance could be resolved, false if the .
153      * @see #isInheritanceResolved()
154      * @throws RuntimeException when the builder to extend from is not allowed as parent
155      */

156     protected boolean resolveInheritance() {
157         String JavaDoc buildername = getExtends();
158         if (buildername.equals("")) {
159             parentBuilder = null;
160             inheritanceResolved = true;
161         } else {
162             inheritanceResolved = false;
163             if (mmbase != null) {
164                 parentBuilder = mmbase.getBuilder(buildername);
165                 inheritanceResolved = (parentBuilder != null);
166                 if (inheritanceResolved) { // fill inputPositions, searchPositions
167
Iterator fields = parentBuilder.getFields(NodeManager.ORDER_EDIT).iterator();
168                     while (fields.hasNext()) {
169                         CoreField def = (CoreField) fields.next();
170                         inputPositions.add(new Integer JavaDoc(def.getEditPosition()));
171                     }
172                     fields = parentBuilder.getFields(NodeManager.ORDER_SEARCH).iterator();
173                     while (fields.hasNext()) {
174                         CoreField def = (CoreField) fields.next();
175                         searchPositions.add(new Integer JavaDoc(def.getSearchPosition()));
176                     }
177                 }
178             }
179         }
180         return inheritanceResolved;
181     }
182
183     /**
184      * Detremines if inheritance is resolved.
185      * This method returns true if a call to resolveInheritance succeeded.
186      * it returns false if resolveInheritance failed (returned false or threw an exception)
187      *
188      * @since MMBase-1.6
189      * @return true if inheritance could be resolved
190      * @see #resolveInheritance()
191      */

192     public boolean isInheritanceResolved() {
193         return inheritanceResolved;
194     }
195
196     /**
197      * Get the status of this builder.
198      * Note that if inheritance cannot be resolved, this method always returns "inactive".
199      * @return a String decribing the status ("active" or "inactive")
200      */

201     public String JavaDoc getStatus() {
202         if (!inheritanceResolved) {
203             return "inactive"; // extends an inactive or non-existing builder
204
} else {
205             String JavaDoc val = getElementValue("builder.status").toLowerCase();
206             if (!val.equals("inactive")) {
207                 val = "active"; // fix invalid values, including empty value, in which case
208
// assume it extends an active builder (i.e. object)
209
}
210             return val;
211         }
212     }
213
214     /**
215      * Retrieves the Search Age.
216      * The search age may be used by editors or search forms to determine
217      * the maximum age in days of an object to be searched (limiting the resultset
218      * of a search)
219      * @return the search age in days
220      */

221     public int getSearchAge() {
222         int val = 30;
223         String JavaDoc sval = getElementValue("builder.searchage");
224         if (sval.equals("") && (parentBuilder != null)) {
225             sval = parentBuilder.getSearchAge();
226         }
227         try {
228             val = Integer.parseInt(sval);
229         } catch(Exception JavaDoc f) {}
230         return val;
231     }
232
233     /**
234      * Get the class name to use for instantiating this builder.
235      * Note that it is possible to specify a short-hand format in
236      * the builder configuration file.
237      * If only the classname (withoput package name) is given, the classname
238      * is expanded to fall into the <code>org.mmbase.module.builders</code> package.
239      * @return the classname to use.
240      */

241     public String JavaDoc getClassName() {
242         String JavaDoc val = getElementValue("builder.class");
243         if (val.equals("")) {
244             val = getElementValue("builder.classfile");// deprecated!! (makes no sense, it is no file)
245
}
246
247         if (val.equals("")) {
248             if (parentBuilder != null) {
249                 return parentBuilder.getClass().getName();
250             } else {
251                 return "";
252             }
253         }
254         // is it a full name or inside the org.mmbase.module.builders.* path
255
int pos = val.indexOf('.');
256         if (pos==-1) {
257             val = "org.mmbase.module.builders."+val;
258         }
259         if ("org.mmbase.module.corebuilders.ObjectTypes".equals(val)) {
260             log.warn("Specified the removed builder 'ObjectTypes', fall back to TypeDef. You can remove all core-builders from your configuration directory (the ones present in mmbase.jar are ok)");
261             val = "org.mmbase.module.corebuilders.TypeDef";
262         }
263         return val;
264     }
265
266     /**
267      * Get the datatypes defined for this builder.
268      * @param collector A DataTypeCollector to which the newly found DataTypes will be added.
269      * @return Returns the data-types of the given collector after adding the ones which are configured
270      * @since MMBase-1.8
271      */

272     public Map getDataTypes(DataTypeCollector collector) {
273         Element element = getElementByPath("builder.datatypes");
274         if (element != null) {
275             DataTypeReader.readDataTypes(element, collector);
276         }
277         return collector.getDataTypes();
278     }
279
280     /**
281      * Get the field definitions of this builder.
282      * If applicable, this includes the fields inherited from a parent builder.
283      *
284      * @return a List of all Fields as CoreField
285      * @since MMBase-1.8
286      */

287     public List getFields() {
288         return getFields(null, DataTypes.getSystemCollector());
289     }
290
291     /**
292      * Get the field definitions of this builder.
293      * If applicable, this includes the fields inherited from a parent builder.
294      *
295      * @param builder the MMObjectBuilder to which the fields will be added
296      * @param collector the datatype collector used to access the datatypes available for the fields to read.
297      * @return a List of all Fields as CoreField
298      * @since MMBase-1.8
299      */

300     public List getFields(MMObjectBuilder builder, DataTypeCollector collector) {
301         List results = new ArrayList();
302         Map oldset = new HashMap();
303         int pos = 1;
304         if (parentBuilder != null) {
305             List parentfields = parentBuilder.getFields(NodeManager.ORDER_CREATE);
306             if (parentfields != null) {
307                 // have to clone the parent fields
308
// need clone()!
309
for (Iterator i = parentfields.iterator();i.hasNext();) {
310                     CoreField f = (CoreField)i.next();
311                     CoreField newField = (CoreField)f.clone(f.getName());
312                     newField.setParent(builder);
313                     while(newField.getStoragePosition() >= pos) pos++;
314                     newField.finish();
315                     results.add(newField);
316                     oldset.put(newField.getName(), newField);
317                 }
318             }
319         }
320
321         for(Iterator ns = getChildElements("builder.fieldlist", "field"); ns.hasNext(); ) {
322             Element field = (Element) ns.next();
323             String JavaDoc fieldName = getElementAttributeValue(field, "name");
324             if ("".equals(fieldName)) {
325                 fieldName = getElementValue(getElementByPath(field,"field.db.name"));
326             }
327             CoreField def = (CoreField) oldset.get(fieldName);
328             try {
329                 if (def != null) {
330                     def.rewrite();
331                     DataType dataType = decodeDataType(builder, collector, def.getName(), field, def.getType(), def.getListItemType(), false);
332                     if (dataType != null) {
333                         def.setDataType(dataType); // replace datatype
334
}
335                     decodeFieldDef(field, def, collector);
336                     def.finish();
337                 } else {
338                     def = decodeFieldDef(builder, collector, field);
339                     def.setStoragePosition(pos++);
340                     def.finish();
341                     results.add(def);
342                 }
343             } catch (Exception JavaDoc e) {
344                 log.error("During parsing of " + XMLWriter.write(field, true, true) + " " + e.getMessage(), e);
345             }
346         }
347
348         return results;
349     }
350
351     /**
352      * Get the named indices of this builder.
353      * Note that the 'default' index (set with the 'key' attribute) is also included
354      * in this list (with the name {@link Index#MAIN}).
355      *
356      * @param builder the MMObjectBuilder to which the fields will be added
357      * @return a List of all Indices
358      */

359     public List getIndices(MMObjectBuilder builder) {
360         List results = new ArrayList();
361         Index mainIndex = null;
362         if (parentBuilder != null) {
363             // create the
364
Index parentIndex = parentBuilder.getStorageConnector().getIndex(Index.MAIN);
365             if (parentIndex != null) {
366                 mainIndex = new Index(builder, Index.MAIN);
367                 mainIndex.setUnique(true);
368                 for (Iterator i = parentIndex.iterator(); i.hasNext(); ) {
369                     Field field = (Field)i.next();
370                     mainIndex.add(builder.getField(field.getName()));
371                 }
372             }
373         }
374
375         for (Iterator fields = getChildElements("builder.fieldlist","field"); fields.hasNext(); ) {
376             Element field = (Element)fields.next();
377             Element dbtype = getElementByPath(field,"field.db.type");
378             if (dbtype != null) {
379                 String JavaDoc key = getElementAttributeValue(dbtype,"key");
380                 if (key != null && key.equalsIgnoreCase("true")) {
381                     String JavaDoc fieldName = getElementAttributeValue(field, "name");
382                     if ("".equals(fieldName)) {
383                         fieldName = getElementValue(getElementByPath(field,"field.db.name"));
384                     }
385                     if (mainIndex == null ) mainIndex = new Index(builder, Index.MAIN);
386                     mainIndex.add(builder.getField(fieldName));
387                 }
388             }
389         }
390         if (mainIndex != null) {
391            results.add(mainIndex);
392         }
393
394         if (parentBuilder != null) {
395             Collection parentIndices = parentBuilder.getStorageConnector().getIndices().values();
396             if (parentIndices != null) {
397                 for (Iterator i = parentIndices.iterator();i.hasNext();) {
398                     Index parentIndex = (Index)i.next();
399                     Index newIndex = new Index(builder, parentIndex.getName());;
400                     newIndex.setUnique(parentIndex.isUnique());
401                     for (Iterator parentIndexIter = parentIndex.iterator(); parentIndexIter.hasNext(); ) {
402                         Field field = (Field) parentIndexIter.next();
403                         newIndex.add(builder.getField(field.getName()));
404                     }
405                     results.add(newIndex);
406                 }
407             }
408         }
409
410         
411         for(Iterator indices = getChildElements("builder.indexlist","index"); indices.hasNext(); ) {
412             Element indexElement = (Element)indices.next();
413             String JavaDoc indexName = indexElement.getAttribute("name");
414             if (indexName != null && !indexName.equals("")) {
415                 String JavaDoc unique = indexElement.getAttribute("unique");
416                 Index index = new Index(builder, indexName);
417                 index.setUnique(unique != null && unique.equals("true"));
418                 for(Iterator fields = getChildElements(indexElement,"indexfield"); fields.hasNext(); ) {
419                     Element fieldElement = (Element)fields.next();
420                     String JavaDoc fieldName = fieldElement.getAttribute("name");
421                     Field field = builder.getField(fieldName);
422                     if (field == null) {
423                         log.error("field '" + fieldName +"' in index '" + indexName + "' in builder " + builder.getTableName() + " does not exist");
424                     } else {
425                         index.add(field);
426                     }
427                 }
428                 results.add(index);
429             } else {
430                 log.error("index in builder " + builder.getTableName() + " has no name");
431             }
432         }
433         return results;
434     }
435
436     /**
437      * @since MMBase-1.8
438      */

439     public Set getFunctions() {
440         Set results = new HashSet();
441         for(Iterator ns = getChildElements("builder.functionlist","function"); ns.hasNext(); ) {
442             try {
443                 Element functionElement = (Element)ns.next();
444                 final String JavaDoc functionName = functionElement.getAttribute("name");
445                 String JavaDoc providerKey = functionElement.getAttribute("key");
446                 String JavaDoc functionClass = getNodeTextValue(getElementByPath(functionElement, "function.class"));
447
448                 Function function;
449                 log.service("Using " + functionClass);
450                 Class JavaDoc claz = Class.forName(functionClass);
451                 if (Function.class.isAssignableFrom(claz)) {
452                     if (!providerKey.equals("")) {
453                         log.warn("Specified a key attribute for a Function " + claz + " in " + getSystemId() + ", this makes only sense for FunctionProviders.");
454                     }
455                     function = (Function) claz.newInstance();
456                 } else if (FunctionProvider.class.isAssignableFrom(claz)) {
457                     if ("".equals(providerKey)) providerKey = functionName;
458                     if ("".equals(providerKey)) {
459                         log.error("FunctionProvider " + claz + " specified in " + getSystemId() + " without key or name");
460                         continue;
461                     }
462                     FunctionProvider provider = (FunctionProvider) claz.newInstance();
463                     function = provider.getFunction(providerKey);
464                 } else {
465                     if ("".equals(providerKey)) providerKey = functionName;
466                     if ("".equals(providerKey)) {
467                         log.error("Speficied class " + claz + " in " + getSystemId() + "/functionslist/function is not a Function or FunctionProvider and can not be wrapped in a BeanFunction, because neither key nor name attribute were specified.");
468                         continue;
469                     }
470                     function = BeanFunction.getFunction(claz, providerKey);
471                 }
472                 if (! functionName.equals("") && ! function.getName().equals(functionName)) {
473                     log.service("Wrapping " + function.getName() + " to " + functionName);
474                     function = new WrappedFunction(function) {
475                             public String JavaDoc getName() {
476                                 return functionName;
477                             }
478                         };
479                 }
480                 if (! (function instanceof NodeFunction)) {
481                     // if it contains a 'node' parameter, it can be wrapped into a node-function,
482
// and be available on nodes of this builder.
483
Parameters test = function.createParameters();
484                     if (test.containsParameter(Parameter.NODE)) {
485                         final Function f = function;
486                         function = new NodeFunction(function.getName(), function.getParameterDefinition(), function.getReturnType()) {
487                                 protected Object JavaDoc getFunctionValue(org.mmbase.bridge.Node node, Parameters parameters) {
488                                     if (parameters == null) parameters = createParameters();
489                                     parameters.set(Parameter.NODE, node);
490                                     return f.getFunctionValue(parameters);
491                                 }
492                                 public Object JavaDoc getFunctionValue(Parameters parameters) {
493                                     return f.getFunctionValue(parameters);
494                                 }
495                             };
496                     }
497                 }
498
499                 results.add(function);
500             } catch (Throwable JavaDoc e) {
501                 log.error(e.getMessage(), e);
502             }
503
504         }
505
506
507         return results;
508
509     }
510
511     /**
512      * Determine an integer value from an elements body.
513      * Used for the List, Search, and Edit position values.
514      * @param elm The element containing the value.
515      * @return the parsed integer
516      */

517     private int getEditorPos(Element elm) {
518         try {
519             int val = Integer.parseInt(getElementValue(elm));
520             return val;
521         } catch(Exception JavaDoc e) {
522             return -1;
523         }
524     }
525     /**
526      * Alter a specified, named FieldDef object using information obtained from the buidler configuration.
527      * Only GUI information is retrieved and stored (name and type of the field sg=hould already be specified).
528      * @since MMBase-1.6
529      * @param elm The element containing the field information acc. to the buidler xml format
530      * @param def The field definition to alter
531      */

532     private void decodeFieldDef(Element field, CoreField def, DataTypeCollector collector) {
533         // Gui
534
Element descriptions = getElementByPath(field, "field.descriptions");
535         if (descriptions != null) {
536             def.getLocalizedDescription().fillFromXml("description", descriptions);
537         }
538
539         // XXX: deprecated tag 'gui'
540
Element gui = getElementByPath(field, "field.gui");
541         if (gui != null) {
542             def.getLocalizedGUIName().fillFromXml("guiname", gui);
543             // XXX: even more deprecated
544
def.getLocalizedGUIName().fillFromXml("name", gui);
545         }
546
547         // Editor
548
Element editorpos = getElementByPath(field, "field.editor.positions.input");
549         if (editorpos != null) {
550             int inputPos = getEditorPos(editorpos);
551             if (inputPos > -1) inputPositions.add(new Integer JavaDoc(inputPos));
552             def.setEditPosition(inputPos);
553         } else {
554             // if not specified, use lowest 'free' position.
555
int i = 1;
556             while (inputPositions.contains(new Integer JavaDoc(i))) {
557                 ++i;
558             }
559             inputPositions.add(new Integer JavaDoc(i));
560             def.setEditPosition(i);
561
562         }
563         editorpos = getElementByPath(field, "field.editor.positions.list");
564         if (editorpos != null) {
565             def.setListPosition(getEditorPos(editorpos));
566         }
567         editorpos = getElementByPath(field, "field.editor.positions.search");
568         if (editorpos != null) {
569             int searchPos = getEditorPos(editorpos);
570             if (searchPos > -1) searchPositions.add(new Integer JavaDoc(searchPos));
571             def.setSearchPosition(searchPos);
572         } else {
573             // if not specified, use lowest 'free' position, unless, db-type is BINARY (non-sensical searching on that)
574
// or the field is not in storage at all (search cannot be performed by database)
575
if (def.getType() != Field.TYPE_BINARY && !def.isVirtual()) {
576                 int i = 1;
577                 while (searchPositions.contains(new Integer JavaDoc(i))) {
578                     ++i;
579                 }
580                 searchPositions.add(new Integer JavaDoc(i));
581                 def.setSearchPosition(i);
582             } else {
583                 def.setSearchPosition(-1);
584             }
585         }
586     }
587
588     /**
589      * Determine a data type instance based on the given gui element
590      * @todo 'guitype' may become deprecated in favour of the 'datatype' element
591      * @param builder the MMObjectBuilder to which the field belongs
592      * @param collector The DataTypeCollector of the bulider.
593      * @param fieldName the name of the field (used in log messages)
594      * @param field The 'field' element of the builder xml
595      * @param type The database type of the field
596      * @param listItemType If the database type is a List, there is also a type of its element
597      * @param forceInstance If true, it will never return <code>null</code>, but will return (a clone) of the DataType associated with the database type.
598      * @since MMBase-1.8
599      */

600     protected DataType decodeDataType(final MMObjectBuilder builder, final DataTypeCollector collector, final String JavaDoc fieldName, final Element field, final int type, final int listItemType, final boolean forceInstance) {
601         BasicDataType baseDataType = null;
602         if (type == Field.TYPE_LIST) {
603             baseDataType = DataTypes.getListDataType(listItemType);
604         } else if (type != Field.TYPE_UNKNOWN) {
605             baseDataType = DataTypes.getDataType(type);
606         }
607         BasicDataType dataType = null;
608         Element guiTypeElement = getElementByPath(field, "field.gui.guitype");
609
610         // XXX: deprecated tag 'type'
611
if (guiTypeElement == null) {
612             guiTypeElement = getElementByPath(field, "field.gui.type");
613         }
614
615         // Backwards compatible 'guitype' support
616
if (guiTypeElement != null && collector != null) {
617             if (baseDataType == null) {
618                 throw new IllegalArgumentException JavaDoc("No type defined");
619             }
620             String JavaDoc guiType = getElementValue(guiTypeElement);
621             if (!guiType.equals("")) {
622                 if (guiType.indexOf('.') != -1) {
623                     // apparently, this is a class path, which means it is probably an enumeration
624
// (if not, what else?)
625
dataType = (BasicDataType) baseDataType.clone();
626                     dataType.getEnumerationFactory().addBundle(guiType, getClass().getClassLoader(), null, dataType.getTypeAsClass(), null);
627                     dataType.getEnumerationRestriction().setEnforceStrength(DataType.ENFORCE_NEVER);
628                 } else {
629                     // check for builder names when the type is NODE
630
MMObjectBuilder enumerationBuilder = null;
631                     // The guitype is deprecated. Normally coincides with datatype's id.
632
// The following are exceptions:
633
// 'string' is surrogated with the datatype 'line'.
634
if ("string".equals(guiType)) {
635                         guiType = "line";
636                         if (log.isDebugEnabled()) {
637                             log.debug("Converted deprecated guitype 'string' for field " + (builder != null ? builder.getTableName() + "." : "") + fieldName + " with datatype 'line'.");
638                         }
639                     } else
640                     // 'eventtime' is surrogated with the datatype 'datetime'.
641
if ("eventtime".equals(guiType)) {
642                         guiType = "datetime";
643                         if (log.isDebugEnabled()) {
644                             log.debug("Converted deprecated guitype 'eventtime' for field " + (builder != null ? builder.getTableName() + "." : "") + fieldName + " with datatype 'datetime'.");
645                         }
646                     } else
647                     // 'relativetime' is surrogated with the datatype 'line'.
648
if ("relativetime".equals(guiType)) {
649                         guiType = "duration";
650                         if (log.isDebugEnabled()) {
651                             log.debug("Converted deprecated guitype 'relativetime' for field " + (builder != null ? builder.getTableName() + "." : "") + fieldName + " with datatype 'duration'.");
652                         }
653                     } else
654                     // check for nodetypes
655
if (type == Field.TYPE_NODE) {
656                         try {
657                             enumerationBuilder = mmbase.getBuilder(guiType);
658                         } catch (RuntimeException JavaDoc re) {
659                             log.warn("Exception during parsing of field '" + fieldName + "' of " + (builder != null ? builder.getTableName() : "NULL") + ": " + re.getMessage());
660                         }
661                     }
662                     if (enumerationBuilder != null) {
663                         // Create a query element of the format:
664
// <query type="[buildername]" xmlns="http://www.mmbase.org/xmlns/searchquery" />
665
// and add it to the enumerationfactory using addQuery()
666
Element queryElement = guiTypeElement.getOwnerDocument().createElementNS("http://www.mmbase.org/xmlns/searchquery", "query");
667                         queryElement.setAttribute("type", enumerationBuilder.getTableName());
668                         dataType = (BasicDataType) baseDataType.clone();
669                         Document queryDocument = DocumentReader.toDocument(queryElement);
670                         dataType.getEnumerationFactory().addQuery(LocalizedString.getLocale(queryElement), queryDocument);
671                         dataType.getEnumerationRestriction().setEnforceStrength(DataType.ENFORCE_NEVER);
672                     } else {
673                         dataType = collector.getDataTypeInstance(guiType, baseDataType);
674                         if (dataType == null) {
675                             log.warn("Could not find data type for " + baseDataType + " / " + guiType + " for builder: '" + (builder == null ? "NULL" : builder.getTableName()) + "'");
676                         }
677                     }
678                 }
679             }
680         }
681
682         Element dataTypeElement = getElementByPath(field, "field.datatype");
683
684         if (dataTypeElement != null) {
685             if (dataType != null) {
686                 log.warn("Using both deprecated 'gui/guitype' and 'datatype' subelements in field tag for field '" + fieldName + "', ignoring the first one.");
687             }
688             BasicDataType requestedBaseDataType; // pointer to the original field's datatype which will be used as a base.
689
String JavaDoc base = dataTypeElement.getAttribute("base");
690             if (base.equals("")) {
691                 if (log.isDebugEnabled()) {
692                     log.debug("No base defined, using '" + baseDataType + "'");
693                 }
694                 if (baseDataType == null) {
695                     throw new IllegalArgumentException JavaDoc("No base datatype given, and no field type defined");
696                 }
697                 requestedBaseDataType = baseDataType;
698             } else {
699                 requestedBaseDataType = collector == null ? null : collector.getDataType(base, true);
700                 if (requestedBaseDataType == null) {
701                     log.error("Could not find base datatype for '" + base + "' falling back to " + baseDataType + " in builder '" + (builder == null ? "NULL" : builder.getTableName()) + "'");
702                     requestedBaseDataType = baseDataType;
703                 }
704             }
705             dataType = (BasicDataType) DataTypeReader.readDataType(dataTypeElement, requestedBaseDataType, collector).dataType;
706             if (log.isDebugEnabled()) log.debug("Found datatype " + dataType + " for field " + fieldName);
707         }
708
709         // try to resolve any issues where the datatype differs from the database type
710
if (dataType != null && baseDataType != null && !baseDataType.getClass().isAssignableFrom(dataType.getClass())) {
711             // the thus configured datatype is not compatible with the database type.
712
// Fix that as good as possible:
713
BasicDataType newDataType = (BasicDataType) dataType.clone();
714             newDataType.inherit(baseDataType);
715             if (log.isDebugEnabled()) log.debug("" + dataType + " in '" + getSystemId() + "' field " + fieldName + " is not compatible with " + baseDataType + ". Cloning and inheriting to support gracefull fall backs -> " + newDataType);
716             dataType = newDataType;
717         }
718
719         if (dataType == null && forceInstance) {
720             // DataType is null if no data type element was found
721
if (baseDataType == null) {
722                 throw new IllegalArgumentException JavaDoc("No datatype given, and no type defined");
723             }
724             dataType = (BasicDataType) baseDataType.clone(""); // clone with empty id
725
}
726
727         return dataType;
728     }
729
730     /**
731      * Construct a FieldDef object using a field Element using information
732      * obtained from the builder configuration.
733      * @since MMBase-1.8
734      */

735     private CoreField decodeFieldDef(MMObjectBuilder builder, DataTypeCollector collector, Element field) {
736         // create a new CoreField we need to fill
737

738         // obtain field name.
739
// if both the field name attribute and the <db><name> tag are specified, the attribute takes precedence.
740
String JavaDoc fieldName = getElementAttributeValue(field, "name");
741         String JavaDoc fieldDBName = getElementValue(getElementByPath(field, "field.db.name"));
742         if ("".equals(fieldName)) {
743             if ("".equals(fieldDBName)) {
744                 throw new IllegalArgumentException JavaDoc("Field name was not specified for builder " + builder.getTableName() + ".");
745             }
746             if (log.isDebugEnabled()) {
747                 log.debug("<db><name> tag for field '" + fieldDBName + "' is deprecated. Use the name attribute.");
748             }
749             fieldName = fieldDBName;
750         } else if (!"".equals(fieldDBName)) {
751             log.warn("Specified field name twice: once in the name attribute ('" + fieldName + "') and once in the <name> tag ('" + fieldDBName + "'). Ignoring name tag.");
752         }
753
754         String JavaDoc fieldState = getElementAttributeValue(field, "state");
755         String JavaDoc fieldReadOnly = getElementAttributeValue(field, "readonly");
756
757         // implied by datatype
758
// use db/type to override for legacy database issues
759
// (mostly to prevent warnings in the log, as mmbase fixes this anyway)
760
String JavaDoc fieldType = "";
761         String JavaDoc fieldSize = "";
762         String JavaDoc fieldNotNull = "";
763
764         // defined in datatype
765
String JavaDoc fieldRequired = "";
766         String JavaDoc fieldUnique = "";
767
768         // deprecated db type tag - only use if no other data is given!
769
Element dbtype = getElementByPath(field, "field.db.type");
770         if (dbtype != null) {
771             if (!"".equals(fieldType) || !"".equals(fieldNotNull) || !"".equals(fieldSize)) {
772                 log.warn("Specified field type info for '" + fieldName + "' twice: once in the field tag attributes and once in the <db><type> tag.");
773             } else {
774                 if (log.isDebugEnabled()) {
775                     log.debug("<db><type> tag for field '" + fieldName + "' is deprecated.");
776                 }
777                 fieldType = getElementValue(dbtype);
778                 fieldState = getElementAttributeValue(dbtype, "state");
779                 fieldReadOnly = getElementAttributeValue(dbtype, "readonly");
780                 fieldNotNull = getElementAttributeValue(dbtype, "notnull");
781                 fieldRequired = getElementAttributeValue(dbtype, "required");
782                 fieldUnique = getElementAttributeValue(dbtype, "unique");
783                 fieldSize = getElementAttributeValue(dbtype, "size");
784             }
785         }
786
787         // type - default unknown (derived from datatype)
788
int type = Field.TYPE_UNKNOWN;
789         int listItemType = Field.TYPE_UNKNOWN;
790         if (!"".equals(fieldType)) {
791             type = Fields.getType(fieldType);
792             if (type == Field.TYPE_LIST) {
793                 if (fieldType.length() > 5) {
794                     listItemType = Fields.getType(fieldType.substring(5, fieldType.length() - 1));
795                 }
796             }
797         }
798
799         // datatype
800
DataType dataType = decodeDataType(builder, collector, fieldName, field, type, listItemType, true);
801
802         // determine type from datatype, if possible)
803
if (type == Field.TYPE_UNKNOWN) {
804             type = dataType.getBaseType();
805             if (type == Field.TYPE_LIST) {
806                 listItemType = ((ListDataType)dataType).getItemDataType().getBaseType();
807             }
808         }
809
810         // state - default peristent
811
int state = Field.STATE_PERSISTENT;
812         if (!"".equals(fieldState)) { state = Fields.getState(fieldState); }
813
814         CoreField def = Fields.createField(fieldName, type, listItemType, state, dataType);
815         dataType = def.getDataType();
816
817         def.setParent(builder);
818
819         if (!fieldSize.equals("")) {
820             try {
821                 def.setMaxLength(Integer.parseInt(fieldSize));
822             } catch (NumberFormatException JavaDoc e) {
823                 log.warn("invalid value for size : " + fieldSize);
824             }
825         }
826
827         // set readonly property, but only if given
828
if (!"".equals(fieldReadOnly)) {
829             def.setReadOnly("true".equalsIgnoreCase(fieldReadOnly));
830         }
831
832         // set required property, but only if given
833
if (!"".equals(fieldRequired)) {
834             dataType.setRequired("true".equalsIgnoreCase(fieldRequired));
835         }
836
837         // default for notnull is value of required
838
def.setNotNull("true".equals(fieldNotNull) || ("".equals(fieldNotNull) && dataType.isRequired()));
839
840         // set unique property, but only if given
841
if ("implied".equalsIgnoreCase(fieldUnique)) {
842             dataType.setUnique(true);
843             dataType.getUniqueRestriction().setEnforceStrength(DataType.ENFORCE_NEVER);
844         } else if ("true".equalsIgnoreCase(fieldUnique)) {
845             dataType.setUnique(true);
846         }
847
848         decodeFieldDef(field, def, collector);
849
850         return def;
851     }
852
853     /**
854      * Get the properties of this builder
855      * @code-conventions return type should be Map
856      * @return the properties in a Hashtable (as name-value pairs)
857      */

858     public Hashtable getProperties() {
859         Hashtable results=new Hashtable();
860         if (parentBuilder != null) {
861             Map parentparams = parentBuilder.getInitParameters();
862             if (parentparams != null) {
863                 results.putAll(parentparams);
864             }
865         }
866         for(Iterator iter = getChildElements("builder.properties","property");
867                         iter.hasNext(); ) {
868             Element p = (Element)iter.next();
869             String JavaDoc name = getElementAttributeValue(p,"name");
870             String JavaDoc value = getElementValue(p);
871             results.put(name,value);
872         }
873         return results;
874     }
875
876
877     /**
878      * Get the descriptions of this builder
879      * @code-conventions return type should be Map
880      * @return the descriptions in a Hashtable, accessible by language
881      */

882     public Hashtable getDescriptions() {
883         Hashtable results=new Hashtable();
884         Element tmp;
885         String JavaDoc lang;
886         for (Iterator iter = getChildElements("builder.descriptions","description");
887              iter.hasNext(); ) {
888             tmp = (Element)iter.next();
889             lang = getElementAttributeValue(tmp,"xml:lang");
890             results.put(lang,getElementValue(tmp));
891         }
892         return results;
893     }
894
895     /**
896      * Get the plural names of this builder
897      * @code-conventions return type should be Map
898      * @return the plural names in a Hashtable, accessible by language
899      */

900     public Hashtable getPluralNames() {
901         Hashtable results=new Hashtable();
902         for (Iterator iter = getChildElements("builder.names","plural"); iter.hasNext(); ) {
903             Element tmp = (Element)iter.next();
904             String JavaDoc lang = getElementAttributeValue(tmp,"xml:lang");
905             results.put(lang,getElementValue(tmp));
906         }
907         return results;
908     }
909
910     /**
911      * Get the singular (GUI) names of this builder
912      * @code-conventions return type should be Map
913      * @return the singular names in a Hashtable, accessible by language
914      */

915     public Hashtable getSingularNames() {
916         Hashtable results=new Hashtable();
917         for (Iterator iter = getChildElements("builder.names","singular"); iter.hasNext(); ) {
918             Element tmp = (Element)iter.next();
919             String JavaDoc lang = getElementAttributeValue(tmp,"xml:lang");
920             results.put(lang,getElementValue(tmp));
921         }
922         return results;
923     }
924
925
926     /**
927      * Get the builder that this builder extends
928      *
929      * @since MMBase-1.6
930      * @return the parent as an MMObjectBuilder, or null if not specified or unresolved
931      */

932     public MMObjectBuilder getParentBuilder() {
933         return parentBuilder;
934     }
935
936     /**
937      * Get the name of the builder that this builder extends
938      * @since MMBase-1.8
939      * @return the name of the parent builder
940      */

941     public String JavaDoc getExtends() {
942         return getElementAttributeValue("builder", "extends");
943     }
944
945     /**
946      * Retrieve the (major) version number of this builder
947      * @since MMBase-1.8
948      * @return the version as an integer.
949      */

950     public int getVersion() {
951         String JavaDoc version = getElementAttributeValue("builder","version");
952         if (version.equals("") && parentBuilder != null) {
953            return parentBuilder.getVersion();
954         } else {
955             int n = 0;
956             if (!version.equals("")) {
957                 try {
958                     n = Integer.parseInt(version);
959                 } catch (Exception JavaDoc f) {}
960             }
961             return n;
962         }
963     }
964
965     /**
966      * Retrieve the name of the maintainer of this builder
967      * @since MMBase-1.8
968      * @return the name fo the maintainer as a String
969      */

970     public String JavaDoc getMaintainer() {
971         String JavaDoc maintainer = getElementAttributeValue("builder", "maintainer");
972         if (maintainer.equals("")) {
973             if (parentBuilder != null) {
974                 maintainer = parentBuilder.getMaintainer();
975             } else {
976                 maintainer = "mmbase.org";
977             }
978         }
979         return maintainer;
980     }
981
982     /**
983      * {@inheritDoc}
984      * @since MMBase-1.7
985      */

986     public boolean equals(Object JavaDoc o) {
987         if (o instanceof BuilderReader) {
988             BuilderReader b = (BuilderReader) o;
989             List fields = getFields();
990             List otherFields = b.getFields();
991             return
992                 fields.equals(otherFields) &&
993                 getMaintainer().equals(b.getMaintainer()) &&
994                 getVersion() == b.getVersion() &&
995                 getExtends().equals(b.getExtends()) &&
996                 getSingularNames().equals(b.getSingularNames()) &&
997                 getPluralNames().equals(b.getPluralNames()) &&
998                 getDescriptions().equals(b.getDescriptions()) &&
999                 getProperties().equals(b.getProperties()) &&
1000                getClassName().equals(b.getClassName())
1001                ;
1002        } else {
1003            return false;
1004        }
1005    }
1006
1007    /**
1008     * Whether this builderreader object is equal to another for storage purposes (so, ignoring gui and documentation fields)
1009     * @since MMBase-1.7
1010     */

1011    public boolean storageEquals(BuilderReader f) {
1012        List otherFields = f.getFields();
1013        List thisFields = getFields();
1014        if (otherFields.size() != thisFields.size()) return false;
1015        for (int i = 0; i < thisFields.size(); i++) {
1016            CoreField thisField = (CoreField) thisFields.get(i);
1017            CoreField otherField = (CoreField) otherFields.get(i);
1018            if (! thisField.storageEquals(otherField)) return false;
1019        }
1020        return true;
1021    }
1022
1023    /**
1024     * For testing only
1025     */

1026    public static void main(String JavaDoc[] argv) throws Exception JavaDoc {
1027        org.mmbase.util.ResourceLoader rl = org.mmbase.util.ResourceLoader.getSystemRoot();
1028        Document doc = rl.getDocument(argv[0], true, BuilderReader.class);
1029        new BuilderReader(doc, null);
1030    }
1031
1032}
1033
1034
Popular Tags