001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010:
011: package org.mmbase.bridge.util.xml;
012:
013: import org.w3c.dom.*;
014: import javax.xml.parsers.DocumentBuilder;
015: import org.mmbase.bridge.*;
016: import java.util.*;
017:
018: import org.mmbase.util.logging.*;
019: import org.mmbase.util.xml.XMLWriter;
020:
021: /**
022: * Uses the XML functions from the bridge to construct a DOM document representing MMBase data structures.
023: *
024: * @author Michiel Meeuwissen
025: * @author Eduard Witteveen
026: * @version $Id: Generator.java,v 1.48 2007/04/09 19:10:27 michiel Exp $
027: * @since MMBase-1.6
028: */
029: public class Generator {
030:
031: private static final Logger log = Logging
032: .getLoggerInstance(Generator.class);
033:
034: public final static String NAMESPACE = "http://www.mmbase.org/xmlns/objects";
035: private final static String DOCUMENTTYPE_PUBLIC = "-//MMBase//DTD objects config 1.0//EN";
036: private final static String DOCUMENTTYPE_SYSTEM = "http://www.mmbase.org/dtd/objects_1_0.dtd";
037:
038: private Document document = null;
039: private DocumentBuilder documentBuilder = null;
040: private Cloud cloud = null;
041:
042: private boolean namespaceAware = false;
043:
044: private long buildCost = 0; // ns
045: private int size = 0;
046:
047: /**
048: * To create documents representing structures from the cloud, it
049: * needs a documentBuilder, to contruct the DOM Document, and the
050: * cloud from which the data to be inserted will come from.
051: *
052: * @param documentBuilder The DocumentBuilder which will be used to create the Document.
053: * @param cloud The cloud from which the data will be.
054: * @see org.mmbase.util.xml.DocumentReader#getDocumentBuilder()
055: */
056: public Generator(DocumentBuilder documentBuilder, Cloud cloud) {
057: this .documentBuilder = documentBuilder;
058: this .cloud = cloud;
059:
060: }
061:
062: public Generator(DocumentBuilder documentBuilder) {
063: this (documentBuilder, null);
064: }
065:
066: public Generator(Document doc) {
067: document = doc;
068: namespaceAware = document.getDocumentElement()
069: .getNamespaceURI() != null;
070: }
071:
072: /**
073: * Returns an estimation on how long it took to construct the document.
074: * @return a duration in nanoseconds.
075: * @since MMBase-1.9
076: */
077: public long getCost() {
078: return buildCost;
079: }
080:
081: /**
082:
083: * The number of presented MMBase nodes in the document.
084: * @since MMBase-1.9
085: */
086: public int getSize() {
087: return size;
088: }
089:
090: /**
091: * Returns the working DOM document.
092: * @return The document, build with the operations done on the generator class
093: */
094: public Document getDocument() {
095: if (document == null) {
096: long start = System.nanoTime();
097: DOMImplementation impl = documentBuilder
098: .getDOMImplementation();
099: document = impl.createDocument(namespaceAware ? NAMESPACE
100: : null, "objects", impl
101: .createDocumentType("objects", DOCUMENTTYPE_PUBLIC,
102: DOCUMENTTYPE_SYSTEM));
103: if (cloud != null) {
104: addCloud();
105: }
106: buildCost += System.nanoTime() - start;
107: }
108: return document;
109: }
110:
111: /**
112: * If namespace aware, element are created with the namespace http://www.mmbase.org/objects,
113: * otherwise, without namespace.
114: * @since MMBase-1.8
115: */
116: public void setNamespaceAware(boolean n) {
117: if (document != null)
118: throw new IllegalStateException(
119: "Already started constructing");
120: namespaceAware = n;
121: }
122:
123: /**
124: * @since MMBase-1.8
125: */
126: public boolean isNamespaceAware() {
127: return namespaceAware;
128: }
129:
130: /**
131: * @since MMBase-1.8
132: */
133: protected Element createElement(String name) {
134: getDocument();
135: if (namespaceAware) {
136: return document.createElementNS(NAMESPACE, name);
137: } else {
138: return document.createElement(name);
139: }
140:
141: }
142:
143: protected final void setAttribute(Element element, String name,
144: String value) {
145: // attributes normally have no namespace. You can assign one, but then they will always have
146: // to be indicated explicitely (in controdiction to elements).
147: // So attributes are created without namespace.
148: /*
149: if (namespaceAware) {
150: element.setAttributeNS(NAMESPACE, name, value);
151: } else {
152: element.setAttribute(name, value);
153: }
154: */
155: element.setAttribute(name, value);
156: }
157:
158: protected final String getAttribute(Element element, String name) {
159: // see setAttribute
160: /*
161: if (namespaceAware) {
162: return element.getAttributeNS(NAMESPACE, name);
163: } else {
164: return element.getAttribute(name);
165: }
166: */
167: return element.getAttribute(name);
168: }
169:
170: /**
171: * Returns the document as a String.
172: * @return the xml generated as an string
173: */
174: public String toString() {
175: return toString(false);
176: }
177:
178: /**
179: * Returns the document as a String.
180: * @param ident if the string has to be idented
181: * @return the generated xml as a (formatted) string
182: */
183: public String toString(boolean ident) {
184: return XMLWriter.write(document, ident);
185: }
186:
187: private void addCloud() {
188: setAttribute(document.getDocumentElement(), "cloud", cloud
189: .getName());
190: }
191:
192: /**
193: * Adds a field to the DOM Document. This means that there will
194: * also be added a Node if this is necessary.
195: * @param node An MMbase bridge Node.
196: * @param fieldDefinition An MMBase bridge Field.
197: */
198: public Element add(org.mmbase.bridge.Node node,
199: Field fieldDefinition) {
200: long start = System.nanoTime();
201: getDocument();
202: if (cloud == null) {
203: cloud = node.getCloud();
204: addCloud();
205: }
206:
207: Element object = getNode(node);
208:
209: if (!(object.getFirstChild() instanceof Element)) {
210: log.warn("Cannot find first field of "
211: + XMLWriter.write(object, false));
212: buildCost += System.nanoTime() - start;
213: return object;
214: }
215: // get the field...
216: Element field = (Element) object.getFirstChild();
217: while (field != null
218: && !fieldDefinition.getName().equals(
219: getAttribute(field, "name"))) {
220: field = (Element) field.getNextSibling();
221: }
222: // when not found, we are in a strange situation..
223: if (field == null)
224: throw new BridgeException(
225: "field with name: "
226: + fieldDefinition.getName()
227: + " of node "
228: + node.getNumber()
229: + " with nodetype: "
230: + fieldDefinition.getNodeManager()
231: .getName()
232: + " not found, while it should be in the node skeleton.. xml:\n"
233: + toString(true));
234: // when it is filled (allready), we can return
235: if (field.getTagName().equals("field")) {
236: buildCost += System.nanoTime() - start;
237: return field;
238: }
239:
240: // was not filled, so fill it... first remove the unfilled
241: Element filledField = createElement("field");
242:
243: field.getParentNode().replaceChild(filledField, field);
244: field = filledField;
245: // the name
246: setAttribute(field, "name", fieldDefinition.getName());
247: // now fill it with the new info...
248: // format
249: setAttribute(field, "format", getFieldFormat(fieldDefinition));
250: // the value
251: switch (fieldDefinition.getType()) {
252: case Field.TYPE_XML:
253: Document doc = node.getXMLValue(fieldDefinition.getName());
254: // only fill the field, if field has a value..
255: if (doc != null) {
256: // put the xml inside the field...
257: field.appendChild(importDocument(field, doc));
258: }
259: break;
260: case Field.TYPE_BINARY:
261: org.mmbase.util.transformers.Base64 transformer = new org.mmbase.util.transformers.Base64();
262: field.appendChild(document.createTextNode(transformer
263: .transform(node.getByteValue(fieldDefinition
264: .getName()))));
265: break;
266: case Field.TYPE_DATETIME:
267: // shoudlw e use ISO_8601_LOOSE here or ISO_8601_UTC?
268: field
269: .appendChild(document
270: .createTextNode(org.mmbase.util.Casting.ISO_8601_LOOSE
271: .get()
272: .format(
273: node
274: .getDateValue(fieldDefinition
275: .getName()))));
276: break;
277: default:
278: field.appendChild(document.createTextNode(node
279: .getStringValue(fieldDefinition.getName())));
280: }
281:
282: // or do we need more?
283: buildCost += System.nanoTime() - start;
284: return field;
285: }
286:
287: /**
288: * Adds one Node to a DOM Document.
289: * @param node An MMBase bridge Node.
290: */
291: public Element add(org.mmbase.bridge.Node node) {
292: // process all the fields..
293: NodeManager nm = node.getNodeManager();
294: FieldIterator i = nm.getFields(NodeManager.ORDER_CREATE)
295: .fieldIterator();
296: while (i.hasNext()) {
297: Field field = i.nextField();
298: if (field.getType() != Field.TYPE_BINARY) {
299: add(node, field);
300: }
301: }
302: return getNode(node);
303: }
304:
305: /**
306: * Adds one Relation to a DOM Document.
307: * @param relation An MMBase bridge Node.
308: */
309: public Element add(Relation relation) {
310: return add((org.mmbase.bridge.Node) relation);
311:
312: }
313:
314: /**
315: * Adds a whole MMBase bridge NodeList to the DOM Document.
316: * @param nodes An MMBase bridge NodeList.
317: */
318: public void add(List<? extends org.mmbase.bridge.Node> nodes) {
319: for (org.mmbase.bridge.Node n : nodes) {
320: if (n instanceof Relation) {
321: add((Relation) n);
322: } else {
323: add(n);
324: }
325: }
326: }
327:
328: /**
329: * Creates an Element which represents a bridge.Node with all fields unfilled.
330: * @param node MMbase node
331: * @return Element which represents a bridge.Node
332: */
333: private Element getNode(org.mmbase.bridge.Node node) {
334:
335: // if we are a relation,.. behave like one!
336: // why do we find it out now, and not before?
337: Element object = getDocument().getElementById(
338: "" + node.getNumber());
339:
340: if (object != null) {
341: return object;
342: }
343:
344: // if it is a realtion... first add source and destination attributes..
345: // can only happen after the node = node.getCloud().getNode(node.getNumber()); thing!
346: if (node instanceof Relation) {
347: Relation relation = (Relation) node;
348: getNode(relation.getSource())
349: .appendChild(
350: createRelationEntry(relation, relation
351: .getSource()));
352: getNode(relation.getDestination()).appendChild(
353: createRelationEntry(relation, relation
354: .getDestination()));
355: }
356:
357: // node didnt exist, so we need to create it...
358: object = createElement("object");
359: size++;
360:
361: setAttribute(object, "id", "" + node.getNumber());
362: object.setIdAttribute("id", true);
363: setAttribute(object, "type", node.getNodeManager().getName());
364: StringBuffer ancestors = new StringBuffer(" "); // having spaces before and after the attribute's value, makes it easy to use xsl's 'contains' function.
365: if (!node.getNodeManager().getName().equals("object")) {
366: NodeManager parent = node.getNodeManager();
367: do {
368: parent = parent.getParent();
369: ancestors.append(parent.getName());
370: ancestors.append(" ");
371: } while (!parent.getName().equals("object"));
372: }
373: setAttribute(object, "ancestors", ancestors.toString());
374:
375: // and the otype (type as number)
376: setAttribute(object, "otype", node.getStringValue("otype"));
377:
378: // add the fields (empty)
379: // While still having 'unfilledField's
380: // you know that the node is not yet presented completely.
381:
382: for (Field fieldDefinition : node.getNodeManager().getFields(
383: NodeManager.ORDER_CREATE)) {
384: Element field = createElement("unfilledField");
385: // the name
386: setAttribute(field, "name", fieldDefinition.getName());
387: // add it to the object
388: object.appendChild(field);
389: }
390: document.getDocumentElement().appendChild(object);
391: return object;
392: }
393:
394: /**
395: * Imports an XML document as a value of a field. Can be any XML, so the namespace is set.
396: *
397: * @param fieldElement The Element describing the field
398: * @param toImport The Document to set as the field's value
399: * @return The fieldContent.
400: */
401: private Element importDocument(Element fieldElement,
402: Document toImport) {
403: DocumentType dt = toImport.getDoctype();
404: String tagName = toImport.getDocumentElement().getTagName();
405:
406: String namespace;
407: if (dt != null) {
408: namespace = dt.getSystemId();
409: } else {
410: namespace = "http://www.mmbase.org/xmlns/" + tagName;
411: }
412: if (log.isDebugEnabled()) {
413: log.debug("using namepace: " + namespace);
414: }
415: Element fieldContent = (Element) document.importNode(toImport
416: .getDocumentElement(), true);
417: fieldContent.setAttribute("xmlns", namespace);
418: fieldElement.appendChild(fieldContent);
419: return fieldContent;
420: }
421:
422: private String getFieldFormat(Field field) {
423: switch (field.getType()) {
424: case Field.TYPE_XML:
425: return "xml";
426: case Field.TYPE_STRING:
427: return "string";
428: case Field.TYPE_NODE:
429: return "object"; // better would be "node" ?
430: case Field.TYPE_INTEGER:
431: case Field.TYPE_LONG:
432: // was it a builder?
433: String fieldName = field.getName();
434: String guiType = field.getGUIType();
435:
436: // I want a object database type!
437: if (fieldName.equals("otype") || fieldName.equals("number")
438: || fieldName.equals("snumber")
439: || fieldName.equals("dnumber")
440: || fieldName.equals("rnumber")
441: || fieldName.equals("role")
442: || guiType.equals("reldefs")) {
443: return "object"; // better would be "node" ?
444: }
445: if (guiType.equals("eventtime")) {
446: return "date";
447: }
448: case Field.TYPE_FLOAT:
449: case Field.TYPE_DOUBLE:
450: return "numeric";
451: case Field.TYPE_BINARY:
452: return "bytes";
453: case Field.TYPE_DATETIME:
454: return "datetime";
455: case Field.TYPE_BOOLEAN:
456: return "boolean";
457: case Field.TYPE_LIST:
458: return "list";
459: default:
460: throw new RuntimeException("could not find field-type for:"
461: + field.getType() + " for field: " + field);
462: }
463: }
464:
465: private Element createRelationEntry(Relation relation,
466: org.mmbase.bridge.Node relatedNode) {
467: Element fieldElement = createElement("relation");
468: // we have to know what the relation type was...
469: org.mmbase.bridge.Node reldef = cloud.getNode(relation
470: .getStringValue("rnumber"));
471:
472: setAttribute(fieldElement, "object", "" + relation.getNumber());
473:
474: if (relation.getSource().getNumber() == relatedNode.getNumber()) {
475: setAttribute(fieldElement, "role", reldef
476: .getStringValue("sname"));
477: setAttribute(fieldElement, "related", ""
478: + relation.getDestination().getNumber());
479: setAttribute(fieldElement, "type", "source");
480: } else {
481: setAttribute(fieldElement, "role", reldef
482: .getStringValue("dname"));
483: setAttribute(fieldElement, "related", ""
484: + relation.getSource().getNumber());
485: setAttribute(fieldElement, "type", "destination");
486: }
487: return fieldElement;
488: }
489: }
|