KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > applications > dove > Dove


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
11 package org.mmbase.applications.dove;
12
13 import java.util.*;
14 import java.util.regex.Pattern JavaDoc;
15 import org.w3c.dom.*;
16 import org.mmbase.bridge.*;
17 import org.mmbase.bridge.util.Queries;
18 import org.mmbase.datatypes.*;
19 import org.mmbase.module.builders.*;
20 import org.mmbase.storage.search.RelationStep;
21 import org.mmbase.util.Casting;
22 import org.mmbase.util.Encode;
23 import org.mmbase.util.xml.UtilReader;
24 import org.mmbase.util.logging.*;
25
26 /**
27  * This class handles Remote Procedure Calls described using a DOM model.
28  * The original data is received in xml, likely through a specialized servlet such
29  * as the DoveServlet (but this is not required. it may, for instance, be
30  * possible to use this class in conjunction with SOAP or even outside a servlet
31  * context).
32  * The call should result in a new DOM tree (see the EditWizard API for details).
33  * <br />
34  * XXX: Currently, this class performs some type of validation on the xml received.
35  * If errors occur, they are included in the response, at the place where they
36  * occur. This means that the &lt;error &gt; tag can occur anywhere in the
37  * response, which is not very neat.
38  * <br />
39  * Errors have three types:<br />
40  * parser : the xml given is invalid or does not follow the grammar.
41  * This likely means there is a bug in the client code.<br />
42  * server : the code invoked is either not yet implemented or another,
43  * server-related, error occurred (such as no memory, bad configuration,
44  * etc.).
45  * Server errors entirely fail a request.<br />
46  * client : The data requested could not be retrieved or values specified
47  * were invalid. I.e. a requested node does not exist (any more), or a
48  * <code>put</code> failed due to locking or invalid data.<br />
49  * <br />
50  * This is ONLY for debugging purposes!
51  * XML validation should occur at the parser and be captured early.
52  * If we reach consensus that we must always validate, the xml-checking can be left
53  * out, as we can then assumes a correct model. This will optimize processing.
54  *
55  * @author Pierre van Rooden
56  * @since MMBase-1.5
57  * @version $Id: Dove.java,v 1.78.2.1 2006/10/05 15:06:39 pierre Exp $
58  */

59
60 public class Dove extends AbstractDove {
61
62     private static final Logger log = Logging.getLoggerInstance(Dove.class);
63
64
65     /**
66      *@since MMBase-1.8.1
67      */

68     private static final UtilReader properties = new UtilReader("dove.xml");
69     private static final String JavaDoc PROP_CHANGES = "changes";
70     private static final String JavaDoc CHANGES_IGNORE = "ignore";
71     private static final String JavaDoc CHANGES_WARN = "warn";
72     private static final String JavaDoc CHANGES_EXCEPTION = "exception";
73
74     /**
75      * Constructor
76      * @param doc the Document that is constructed. This should only be used to
77      * construct new DOM elements. New (child) nodes should not be added
78      * to this document, but to the out element.
79      */

80     public Dove(Document doc) {
81         super(doc);
82     }
83
84     /**
85      * Utility function, determines whether a field is a data field.
86      * Data fields are fields mentioned explicitly in the builder configuration file.
87      * These can include virtual fields.
88      * Fields number, owner, and ottype, and the relation fields snumber,dnumber, rnumber, and dir
89      * are excluded; these fields should be handled through the attributes of Element.
90      * @param node the MMBase node that owns the field (or null)
91      * @param f The field to check
92      */

93     private boolean isDataField(NodeManager nodeManager, Field f) {
94         String JavaDoc fname = f.getName();
95         return (nodeManager.hasField(fname)) && // skip temporary fields
96
(!"owner".equals(fname)) && // skip owner/otype/number fields!
97
(!"otype".equals(fname)) &&
98                (!"number".equals(fname)) &&
99                (!"snumber".equals(fname)) &&
100                (!"dnumber".equals(fname)) &&
101                (!"rnumber".equals(fname)) &&
102                (!"dir".equals(fname));
103     }
104
105     /**
106      * Utility function, determines whether a field is a data field.
107      * Data fields are fields mentioned explicitly in the builder configuration file.
108      * These can include virtual fields.
109      * Fields number, owner, and ottype, and the relation fields snumber,dnumber, and rnumber
110      * are not excluded; these fields should be handled through the attributes of Element.
111      * @param node the MMBase node that owns the field
112      * @param fname The name of the field to check
113      */

114     private boolean isDataField(NodeManager nodeManager, String JavaDoc fname) {
115         return nodeManager.hasField(fname);
116     }
117
118     private boolean isEditableField(NodeManager nodeManager, String JavaDoc fname) {
119         return isDataField(nodeManager, fname) && nodeManager.getField(fname).getState() == Field.STATE_PERSISTENT;
120     }
121
122     /**
123      * Handles a node storing its content in a DOM element.
124      * This method accepts a object to store, as well as a DOM element, which
125      * may contain as it child nodes elements describing the fields to retrieve.
126      * The tagname of these elements should be 'field'.
127      * Each element should have a 'name' attribute that identifies the field.
128      * The result of this call should be a list of DOM elements describing each field,
129      * which are appended to the out element.
130      * If the 'in' node has no field elements, all fields are returned, with the
131      * exception of some system-specific fields.
132      * <br />
133      * @todo Currently, the Dove does not return values for binary (byte) fields - a binary field is
134      * always returned empty. This is done for optimization of the editwizards, which would otherwise
135      * eat a lot of memory, but that DO need a reference to a bytefield.
136      * Future versions of Dove should handle a better mechanism for handling binary fields.
137      *
138      * @param in the element that described the <code>getdata</code> call.
139      * The childnodes should describe the nodes to retrieve.
140      * @param out the element that described the <code>getdata</code> result.
141      * Retrieved nodes should be added as childs to this element.
142      * @param node The node to store in out.
143      */

144     public void getDataNode(Element in, Element out, org.mmbase.bridge.Node node) {
145         NodeManager nm = node.getNodeManager();
146         out.setAttribute(ELM_TYPE, nm.getName());
147         out.setAttribute(ELM_MAYWRITE, "" + node.mayWrite());
148         out.setAttribute(ELM_MAYDELETE, "" + node.mayDelete());
149         // load fields
150
Element field = getFirstElement(in, FIELD);
151         if (field == null) {
152             for (FieldIterator i = nm.getFields(NodeManager.ORDER_CREATE).fieldIterator(); i.hasNext(); ) {
153                 Field f = i.nextField();
154                 String JavaDoc fname = f.getName();
155                 if (isDataField(nm,f)) {
156                     Element fel;
157                     DataType dataType = f.getDataType();
158                     if (dataType instanceof BinaryDataType) {
159                         fel = addContentElement(FIELD, "", out);
160
161                         int byteLength = 0;
162                         if (nm.hasField(AbstractImages.FIELD_FILESIZE)) {
163                             byteLength = node.getIntValue(AbstractImages.FIELD_FILESIZE);
164                         }
165                         else {
166                             if (nm.hasField(Attachments.FIELD_SIZE)) {
167                                 byteLength = node.getIntValue(Attachments.FIELD_SIZE);
168                             }
169                             else {
170                                 byte[] bytes = node.getByteValue(fname);
171                                 byteLength = bytes != null ? bytes.length : 0;
172                             }
173                         }
174                         fel.setAttribute(ELM_SIZE, "" + byteLength);
175                     } else if (dataType instanceof DateTimeDataType ||
176                                dataType instanceof IntegerDataType ||
177                                dataType instanceof LongDataType
178                                ) {
179                         // have to convert ourselves because bridge will use user-defined formatting
180
fel = addContentElement(FIELD, "" + node.getLongValue(fname), out);
181                     } else {
182                         fel = addContentElement(FIELD, node.isNull(fname) ? null : node.getStringValue(fname), out);
183                     }
184                     fel.setAttribute(ELM_TYPE, dataType.getBaseTypeIdentifier());
185                     fel.setAttribute(ELM_NAME, fname);
186                 }
187             }
188         } else {
189             while (field != null) { // select all child tags, should be 'field'
190
String JavaDoc fname = field.getAttribute(ELM_NAME);
191                 if ((fname == null) || (fname.equals(""))) {
192                     Element err = addContentElement(ERROR, "name required for field",out);
193                     err.setAttribute(ELM_TYPE, IS_PARSER);
194                 } else if (isDataField(nm,fname)) {
195                     Element fel;
196                     Field f = nm.getField(fname);
197                     DataType dataType = f.getDataType();
198                     if (dataType instanceof BinaryDataType) {
199                         fel = addContentElement(FIELD, "", out);
200                         byte[] bytes = node.getByteValue(fname);
201                         fel.setAttribute(ELM_SIZE, "" + (bytes != null ? bytes.length : 0));
202                     } else if (dataType instanceof DateTimeDataType ||
203                                dataType instanceof IntegerDataType ||
204                                dataType instanceof LongDataType
205                                ) {
206                         // have to convert ourselves because bridge will use user-defined formatting
207
fel = addContentElement(FIELD, "" + node.getLongValue(fname), out);
208                     } else {
209                         fel = addContentElement(FIELD, node.getStringValue(fname), out);
210                     }
211                     fel.setAttribute(ELM_TYPE, dataType.getBaseTypeIdentifier());
212                     fel.setAttribute(ELM_NAME, fname);
213                 } else {
214                     Element err = addContentElement(ERROR, "field with name " + fname + " does not exist", out);
215                     err.setAttribute(ELM_TYPE, IS_PARSER);
216                 }
217                 field = getNextElement(field,FIELD);
218             }
219         }
220         // load relations
221
Element relation = getFirstElement(in, RELATION);
222         while (relation != null) { // select all child tags, should be 'relation'
223
addRelationNodes(relation, out, node);
224             relation = getNextElement(relation, RELATION);
225         }
226     }
227
228     /**
229      * Handles a node by retrieving its data and storing its content in a
230      * DOM element.
231      * This method accepts a DOM element (describing a node), which may contain as it
232      * child nodes elements describing the fields to retrieve.
233      * The tagname of these elements should be 'field'.
234      * Each element should have a 'name' attribute that identifies the field.
235      * The result of this call should be a list of DOM elements describing each field,
236      * which are appended to the out element.
237      * If the 'in' node has no field elements, all fields are returned, with the
238      * exception of some system-specific fields.
239      * <br />
240      * Note also that if no name was given for a field, a NullPointer exception is thrown.
241      *
242      * @param in the element that described the <code>getdata</code> call.
243      * The childnodes should describe the nodes to retrieve.
244      * @param out the element that described the <code>getdata</code> result.
245      * Retrieved nodes should be added as childs to this element.
246      * @param cloud the cloud to work on
247      */

248     public void getDataNode(Element in, Element out, Cloud cloud) {
249         String JavaDoc alias = in.getAttribute(ELM_NUMBER);
250         try {
251             org.mmbase.bridge.Node nd = cloud.getNode(alias);
252             getDataNode(in,out,nd);
253         } catch (RuntimeException JavaDoc e) {
254             Element err = addContentElement(ERROR,"node not found",out);
255             err.setAttribute(ELM_TYPE, IS_SERVER);
256         }
257     }
258
259     /**
260      * Retrieves the relations of a node and adds its content to a DOM element.
261      * This method accepts a DOM element (describing a relation), which may contain as it
262      * child nodes elements describing the fields as well as the related object to retrieve.
263      *
264      * @param relation the element that described the relation
265      * The childnodes (if present) should describe the fields to retrieve, and possibly
266      * a description of the related object.
267      * @param out the element that describes the source node.
268      * Retrieved relations should be added as childs to this element.
269      * @param nd the MMBase node that shoudl eb sued to query the relations
270      */

271     protected void addRelationNodes(Element relation, Element out, org.mmbase.bridge.Node nd) {
272         int thisNumber=nd.getNumber();
273         String JavaDoc role=relation.getAttribute(ELM_ROLE);
274         if ("".equals(role)) role=null;
275         String JavaDoc destinationType = relation.getAttribute(ELM_DESTINATIONTYPE);
276         if (("".equals(destinationType)) || (destinationType==null)) {
277             destinationType = relation.getAttribute(ELM_DESTINATION);
278         }
279         if ("".equals(destinationType)) destinationType=null;
280         int searchDir=0;
281         String JavaDoc searchDirs = relation.getAttribute(ELM_SEARCHDIR).toLowerCase();
282         if("destination".equals(searchDirs)) {
283             searchDir=1;
284         } else if("source".equals(searchDirs)) {
285             searchDir=2;
286         }
287
288         // determines whether to load the object (and possible restrictions)
289
Element objectDef = getFirstElement(relation,OBJECT);
290         try {
291             for (RelationIterator i = nd.getRelations(role,destinationType).relationIterator(); i.hasNext(); ) {
292                 Relation nrel=i.nextRelation();
293                 if (searchDir==1) {
294                     if (thisNumber!=nrel.getIntValue("snumber")) continue;
295                 }
296                 if (searchDir==2) {
297                     if (thisNumber!=nrel.getIntValue("dnumber")) continue;
298                 }
299                 Element data=doc.createElement(RELATION);
300                 if (role!=null) {
301                     data.setAttribute(ELM_ROLE, role);
302                 } else {
303                     data.setAttribute(ELM_ROLE,nrel.getRelationManager().getForwardRole());
304                 }
305                 data.setAttribute(ELM_SOURCE, "" + nrel.getIntValue("snumber"));
306                 data.setAttribute(ELM_DESTINATION, "" + nrel.getIntValue("dnumber"));
307
308                 int otherNumber;
309                 if (thisNumber == nrel.getIntValue("snumber")) {
310                     otherNumber = nrel.getIntValue("dnumber");
311                 } else {
312                     otherNumber = nrel.getIntValue("snumber");
313                 }
314                 data.setAttribute(ELM_NUMBER, ""+nrel.getNumber());
315                 out.appendChild(data);
316                 getDataNode(relation,data,nrel);
317                 if (objectDef!=null) {
318                     Element nodeData=doc.createElement(OBJECT);
319                     nodeData.setAttribute(ELM_NUMBER, ""+otherNumber);
320                     data.appendChild(nodeData);
321                     getDataNode(objectDef, nodeData,nd.getCloud().getNode(otherNumber));
322                 }
323             }
324         } catch (RuntimeException JavaDoc e) {
325             Element err = addContentElement(ERROR,"role or nodetype for relation invalid ("+e.getMessage()+")",out);
326             err.setAttribute(ELM_TYPE, IS_CLIENT);
327         }
328     }
329
330     /**
331      * Handles a node by retrieving its relations and storing its content in a
332      * DOM element.
333      * This method accepts a DOM element (describing a node), which may contain as it
334      * child nodes elements describing the relations to retrieve.
335      * The tagname of these elements should be 'relation'.
336      * If the 'in' node has no relation elements, all relations are returned.
337      *
338      * @param in the element that described the <code>getdata</code> call.
339      * The childnodes should describe the nodes to retrieve.
340      * @param out the element that described the <code>getdata</code> result.
341      * Retrieved nodes should be added as childs to this element.
342      * @param cloud the cloud to work on
343      */

344     public void getRelationsNodes(Element in, Element out, Cloud cloud) {
345         String JavaDoc alias = in.getAttribute(ELM_NUMBER);
346         try {
347             org.mmbase.bridge.Node nd = cloud.getNode(alias);
348             NodeManager nm=nd.getNodeManager();
349             out.setAttribute(ELM_TYPE,nm.getName());
350
351             Element relation=getFirstElement(in,RELATION);
352             if (relation==null) {
353                 int thisNumber=nd.getNumber();
354                 for (RelationIterator i=nd.getRelations().relationIterator(); i.hasNext(); ) {
355                     Relation nrel=i.nextRelation();
356                     Element data=doc.createElement(RELATION);
357                     data.setAttribute(ELM_NUMBER, ""+nrel.getNumber());
358                     data.setAttribute(ELM_ROLE, nrel.getRelationManager().getForwardRole());
359                     if (thisNumber==nrel.getIntValue("snumber")) {
360                         data.setAttribute(ELM_SOURCE, ""+nrel.getIntValue("snumber"));
361                         data.setAttribute(ELM_DESTINATION, ""+nrel.getIntValue("dnumber"));
362                     } else {
363                         data.setAttribute(ELM_SOURCE, ""+nrel.getIntValue("dnumber"));
364                         data.setAttribute(ELM_DESTINATION, ""+nrel.getIntValue("snumber"));
365                     }
366                     out.appendChild(data);
367                     getDataNode(null,data,nrel);
368                 }
369             } else {
370                 while (relation!=null) { // select all child tags, should be 'relation'
371
addRelationNodes(relation,out,nd);
372                     relation=getNextElement(relation,RELATION);
373                }
374             }
375         } catch (RuntimeException JavaDoc e) {
376             Element err = addContentElement(ERROR,"node not found",out);
377             err.setAttribute(ELM_TYPE, IS_SERVER);
378         }
379     }
380
381     /**
382      * Handles a getdata call, by obtaining data for each node specified.
383      * This method accepts a DOM element, which should contain as it
384      * child nodes elements describing the nodes to retrieve.
385      * The tagname of these elements should be 'object'.
386      * Each element should have an 'number' attribute that described the number
387      * or alias of the node to retrieve.
388      * The result of this call should be a list of DOM elements describing each node,
389      * which are appended to the out element.
390      *
391      * @param in the element that described the <code>getdata</code> call.
392      * The childnodes should describe the nodes to retrieve.
393      * @param out the element that described the <code>getdata</code> result.
394      * Retrieved nodes should be added as childs to this element.
395      * @param cloud the cloud to work on
396      */

397     public void getData(Element in, Element out, Cloud cloud) {
398         Element node=getFirstElement(in);
399         while (node!=null) { // select all child tags, should be 'object'
400
if (node.getTagName().equals(OBJECT)) {
401                 String JavaDoc number = node.getAttribute(ELM_NUMBER); // check id;
402
if (number.equals("")) {
403                     Element err = addContentElement(ERROR,"number required for node",out);
404                     err.setAttribute(ELM_TYPE, IS_PARSER);
405                 } else {
406                     Element data=doc.createElement(OBJECT);
407                     data.setAttribute(ELM_NUMBER, number);
408                     out.appendChild(data);
409                     getDataNode(node,data,cloud);
410                 }
411             } else {
412                 Element err = addContentElement(ERROR,"Unknown subtag in getdata: "+node.getTagName(),out);
413                 err.setAttribute(ELM_TYPE, IS_PARSER);
414             }
415             node=getNextElement(node);
416         }
417     }
418
419     /**
420      * Handles a getnew call, by obtaining data for the new node of a specified type.
421      * This method accepts a DOM element, which should contain as it's attribute
422      * the type of the node to return.
423      * The result of this call should be DOM element describing a new node,
424      * which is appended to the out element.
425      *
426      * @param in the element that described the <code>getnew</code> call.
427      * @param out the element that described the <code>getnew</code> result.
428      * The new node should be added as a child to this element.
429      * @param cloud the cloud to work on
430      */

431     public void getNew(Element in, Element out, Cloud cloud) {
432         String JavaDoc nodemanagername = in.getAttribute(ELM_TYPE); // check type;
433
if (nodemanagername.equals("")) {
434             Element err = addContentElement(ERROR,"type required for getnew",out);
435             err.setAttribute(ELM_TYPE, IS_PARSER);
436         } else {
437             try {
438                 out.setAttribute(ELM_TYPE,nodemanagername);
439                 NodeManager nm =cloud.getNodeManager(nodemanagername);
440                 org.mmbase.bridge.Node n = nm.createNode();
441                 try {
442                     Element data=doc.createElement(OBJECT);
443                     int number=java.lang.Math.abs(n.getNumber());
444                     data.setAttribute(ELM_NUMBER, "n"+number);
445                     out.appendChild(data);
446                     getDataNode(null,data,n);
447                 } finally {
448                     n.cancel(); // have to cancel node ! It will only be really made in the putNewNode function
449
}
450             } catch (RuntimeException JavaDoc e) {
451                 Element err = addContentElement(ERROR,"node type " + nodemanagername + " does not exist(" + e.toString() + ")",out);
452                 log.warn(Logging.stackTrace(e));
453                 err.setAttribute(ELM_TYPE, IS_CLIENT);
454             }
455         }
456     }
457
458     /**
459      * Handles a getnewrelation call, by obtaining data for the new node of a specified type.
460      * This method accepts a DOM element, which should contain as it's attribute
461      * the role of the relation to return.
462      * The result of this call should be DOM element describing a new node,
463      * which is appended to the out element.
464      *
465      * @param in the element that described the <code>getnew</code> call.
466      * @param out the element that described the <code>getnew</code> result.
467      * The new node should be added as a child to this element.
468      * @param cloud the cloud to work on
469      */

470     public void getNewRelation(Element in, Element out, Cloud cloud) {
471         String JavaDoc rolename = in.getAttribute(ELM_ROLE); // check role;
472
String JavaDoc destination = in.getAttribute(ELM_DESTINATION); // check destination;
473
String JavaDoc source = in.getAttribute(ELM_SOURCE); // check source;
474
String JavaDoc destinationType = in.getAttribute(ELM_DESTINATIONTYPE); // check destination type;
475
String JavaDoc sourceType = in.getAttribute(ELM_SOURCETYPE); // check source type;
476

477         int createDir = Queries.getRelationStepDirection(in.getAttribute(ELM_CREATEDIR));
478
479         if (rolename.equals("")) {
480             Element err = addContentElement(ERROR,"role required for getrelations",out);
481             err.setAttribute(ELM_TYPE, IS_PARSER);
482         } else {
483             try {
484                 out.setAttribute(ELM_ROLE,rolename);
485                 out.setAttribute(ELM_DESTINATION, destination);
486                 out.setAttribute(ELM_SOURCE, source);
487                 // if both types are given, use these as a constraint for the Relationmanager
488
RelationManager nm;
489                 if (destinationType.equals("") || sourceType.equals("") ) {
490                     nm =cloud.getRelationManager(rolename);
491                 } else {
492                     nm =cloud.getRelationManager(sourceType,destinationType,rolename);
493                 }
494                 org.mmbase.bridge.Node n = nm.createNode();
495                 try {
496                     Element data=doc.createElement(RELATION);
497                     int number=java.lang.Math.abs(n.getNumber());
498                     data.setAttribute(ELM_NUMBER, "n"+number);
499                     if (createDir == RelationStep.DIRECTIONS_SOURCE) {
500                         log.debug("Creating relation in the INVERSE direction");
501                         data.setAttribute(ELM_DESTINATION, source);
502                         data.setAttribute(ELM_SOURCE, destination);
503                     } else {
504                         log.debug("Creating relation in the NORMAL direction");
505                         data.setAttribute(ELM_DESTINATION, destination);
506                         data.setAttribute(ELM_SOURCE, source);
507                     }
508                     data.setAttribute(ELM_ROLE,rolename);
509                     out.appendChild(data);
510                     getDataNode(null, data, n);
511                 } finally {
512                     n.cancel(); // have to cancel node !
513
}
514             } catch (RuntimeException JavaDoc e) {
515                 if (destinationType.equals("") || sourceType.equals("") ) {
516                     Element err = addContentElement(ERROR,"role ("+rolename+") does not exist",out);
517                     err.setAttribute(ELM_TYPE, IS_CLIENT);
518                 } else {
519                     Element err = addContentElement(ERROR,"relation ("+sourceType+"-"+rolename+"->"+destinationType+") constraint does not exist",out);
520                     err.setAttribute(ELM_TYPE, IS_CLIENT);
521                 }
522             }
523         }
524     }
525
526     /**
527      * Handles a getrelations call, by obtaining relations for each node specified.
528      * This method accepts a DOM element, which should contain as it
529      * child nodes elements describing the nodes to retrieve the relations from.
530      * The tagname of these elements should be 'object'.
531      * Each element should have an 'number' attribute that described the number
532      * or alias of the node to retrieve.
533      * The result of this call should be a list of DOM elements with a data element for
534      * each node,which are appended to the out element. The node element's children
535      * are the relations.
536      *
537      * @param in the element that described the <code>getrelations</code> call.
538      * The childnodes should describe the nodes to retrieve the relations from.
539      * @param out the element that described the <code>getrelations</code> result.
540      * Retrieved nodes and their relations should be added as childs to this element.
541      * @param cloud the cloud to work on
542      */

543     public void getRelations(Element in, Element out, Cloud cloud) {
544         Element node = getFirstElement(in);
545         while (node != null) { // select all child tags, should be 'object'
546
if (node.getTagName().equals(OBJECT)) {
547                 String JavaDoc number = node.getAttribute(ELM_NUMBER); // check id;
548
if (number.equals("")) {
549                     Element err = addContentElement(ERROR,"number required for node", out);
550                     err.setAttribute(ELM_TYPE, IS_PARSER);
551                 } else {
552                     Element data = doc.createElement(OBJECT);
553                     data.setAttribute(ELM_NUMBER, number);
554                     out.appendChild(data);
555                     getRelationsNodes(node,data,cloud);
556                 }
557             } else {
558                 Element err = addContentElement(ERROR,"Unknown subtag in getrelations: " + node.getTagName(), out);
559                 err.setAttribute(ELM_TYPE, IS_PARSER);
560             }
561             node = getNextElement(node);
562         }
563     }
564
565     /**
566      * Handles a getconstraints call.
567      *
568      * @param in the element that described the <code>getconstraints</code> call.
569      * The childnodes should describe the node types to retrieve the constraints of.
570      * @param out the element that described the <code>getconstraints</code> result.
571      * Retrieved constraints should be added as childs to this element.
572      * @param cloud the cloud to work on
573      */

574     public void getConstraints(Element in, Element out, Cloud cloud) {
575         String JavaDoc nodeManagerName = in.getAttribute(ELM_TYPE); // check type;
576
if (nodeManagerName.equals("")) {
577             Element err = addContentElement(ERROR,"type required for getconstraints",out);
578             err.setAttribute(ELM_TYPE, IS_PARSER);
579         } else {
580             try {
581                 out.setAttribute(ELM_TYPE,nodeManagerName);
582                 NodeManager nm =cloud.getNodeManager(nodeManagerName);
583
584                 Locale locale = cloud.getLocale();
585                 String JavaDoc lang= in.getAttribute(ELM_LANG);
586                 if (!"".equals(lang)) {
587                     out.setAttribute(ELM_LANG,lang);
588                     locale = new Locale(lang);
589                 }
590
591                 // singular name
592
Element elm=addContentElement(SINGULARNAME,nm.getGUIName(NodeManager.GUI_SINGULAR,locale),out);
593                 if (lang!=null) elm.setAttribute(ELM_LANG,lang);
594
595                 // plural name
596
elm=addContentElement(PLURALNAME,nm.getGUIName(NodeManager.GUI_PLURAL,locale),out);
597                 if (lang!=null) elm.setAttribute(ELM_LANG,lang);
598
599                 // description
600
elm=addContentElement(DESCRIPTION,nm.getDescription(locale),out);
601                 if (lang!=null) elm.setAttribute(ELM_LANG,lang);
602
603                 // parent
604
try {
605                     NodeManager nmparent = nm.getParent();
606                     Element parent = doc.createElement(PARENT);
607                     out.appendChild(parent);
608                     parent.setAttribute(ELM_TYPE,nmparent.getName());
609                 } catch (NotFoundException nfe) {
610                     log.debug("no parent available");
611                 }
612
613                 // descendants
614
NodeManagerList nmdesclist = nm.getDescendants();
615                 if (nmdesclist.size()>0) {
616                     Element descendants = doc.createElement(DESCENDANTS);
617                     out.appendChild(descendants);
618                     for (NodeManagerIterator i = nmdesclist.nodeManagerIterator(); i.hasNext();) {
619                         Element descendant=doc.createElement(DESCENDANT);
620                         descendants.appendChild(descendant);
621                         descendant.setAttribute(ELM_TYPE,i.nextNodeManager().getName());
622                     }
623                 }
624
625                 // fields
626
Element fields=doc.createElement(FIELDS);
627                 out.appendChild(fields);
628                 for(FieldIterator i = nm.getFields(NodeManager.ORDER_CREATE).fieldIterator(); i.hasNext(); ) {
629                     Field fielddef=i.nextField();
630                     String JavaDoc fname=fielddef.getName();
631                     // Filter out the owner/otype/number/CacheCount fields and
632
// the virtual fields.
633
if (isDataField(nm,fielddef)) {
634                         Element field=doc.createElement(FIELD);
635                         field.setAttribute(ELM_NAME,fname);
636                         fields.appendChild(field);
637                         // guiname (XXX:language is ignored)
638
elm = addContentElement(GUINAME, fielddef.getGUIName(locale),field);
639                         elm = addContentElement(DESCRIPTION, fielddef.getDescription(locale),field);
640                         if (lang != null) elm.setAttribute(ELM_LANG, lang);
641                         // guitype
642
DataType dataType = fielddef.getDataType();
643                         String JavaDoc baseType = dataType.getBaseTypeIdentifier();
644                         String JavaDoc specialization = dataType.getName();
645                         if ("".equals(specialization)) {
646                             DataType origin = dataType.getOrigin();
647                             if (origin != null) {
648                                 specialization = origin.getName();
649                             }
650                         }
651                         // exceptions, for backward comp. with old guitypes
652
if (specialization.equals("field")) {
653                             specialization = "text";
654                         } else if (specialization.equals("eventtime")) {
655                             baseType = "datetime";
656                             specialization = "datetime";
657                         } else if (specialization.equals("newimage")) {
658                             specialization = "image";
659                         } else if (specialization.equals("newfile")) {
660                             specialization = "file";
661                         } else {
662                             // backward compatibility: NODE and XML are passed as int and string
663
// TODO: should change ?
664
if (dataType instanceof NodeDataType) {
665                                 baseType = "int";
666                             } else if (dataType instanceof XmlDataType) {
667                                 baseType = "string";
668                             } else if (dataType instanceof BinaryDataType) {
669                                 Pattern JavaDoc p = ((BinaryDataType) dataType).getValidMimeTypes();
670                                 if (p.matcher("image/someimageformat").matches()) {
671                                     specialization = "image";
672                                 } else {
673                                     specialization = "file";
674                                 }
675                             }
676                         }
677                         String JavaDoc guiType = baseType + "/" + specialization;
678
679                         addContentElement(GUITYPE, guiType, field);
680                         int maxLength = fielddef.getMaxLength();
681                         if (maxLength>0) {
682                             addContentElement(MAXLENGTH, "" + maxLength, field);
683                         }
684
685                         if (fielddef.isRequired()) {
686                             addContentElement(REQUIRED, IS_TRUE, field);
687                         } else {
688                             addContentElement(REQUIRED, IS_FALSE, field);
689                         }
690                     }
691                 }
692                 // relations
693
Element relations = doc.createElement(RELATIONS);
694                 out.appendChild(relations);
695                 // XXX: getAllowedRelations is not yet supported by the MMCI
696
// This code commented out
697
} catch (RuntimeException JavaDoc e) {
698                 Element err = addContentElement(ERROR, "node type " + nodeManagerName + " does not exist(" + e.toString() + ")",out);
699                 log.warn(Logging.stackTrace(e));
700                 err.setAttribute(ELM_TYPE, IS_CLIENT);
701             }
702         }
703     }
704
705     /**
706      * Handles a getlist call.
707      * This method accepts a DOM element, which should contain as it
708      * child nodes elements describing the queries to retrieve the nodes with.
709      * The tagname of these elements should be 'query'.
710      * Each element should have a 'xpath' attribute to indicate the nodemanager to run the list on,
711      * and optionally a 'where' that contains the constraints.
712      * The result of this call should be a list of DOM elements with a data element for
713      * each query, which are appended to the out element. The node element's children
714      * are the nodes from the list.
715      * @param in the element that described the <code>getlist</code> call.
716      * The childnodes should describe the queries to run.
717      * @param out the element that described the <code>getlist</code> result.
718      * Retrieved nodes should be added as childs to this element.
719      * @param cloud the cloud to work on
720      */

721     public void getList(Element in, Element out, Cloud cloud) {
722         Element query=getFirstElement(in);
723         while (query!=null) { // select all child tags, should be 'query'
724
if (query.getTagName().equals(QUERY)) {
725                 String JavaDoc xpath = query.getAttribute(ELM_XPATH); // get xpath (nodetype);
726
String JavaDoc where = query.getAttribute(ELM_WHERE); // get constraints;
727
String JavaDoc orderby = query.getAttribute(ELM_ORDERBY); // get orderby;
728
if ("".equals(orderby)) orderby=null;
729                 String JavaDoc directions = query.getAttribute(ELM_DIRECTIONS); // get directions;
730
if ("".equals(directions)) directions=null;
731                 if (xpath.equals("")) {
732                     Element err = addContentElement(ERROR,"xpath required for query",out);
733                     err.setAttribute(ELM_TYPE, IS_PARSER);
734                 } else {
735                     Element querydata=doc.createElement(QUERY);
736                     querydata.setAttribute(ELM_XPATH, xpath);
737                     if (!where.equals("")) {
738                         querydata.setAttribute(ELM_WHERE, where);
739                     }
740                     if (orderby!=null) {
741                         querydata.setAttribute(ELM_ORDERBY, orderby);
742                     }
743                     if (directions!=null) {
744                         querydata.setAttribute(ELM_DIRECTIONS, directions);
745                     }
746                     out.appendChild(querydata);
747
748                     // get node template
749
Element node=getFirstElement(query);
750
751                     if (xpath.indexOf("/*@")!=0) {
752                         Element err = addContentElement(ERROR,"invalid xpath",out);
753                         err.setAttribute(ELM_TYPE, IS_CLIENT);
754                     } else {
755                         String JavaDoc nodepath = xpath.substring(3);
756                         try {
757                             NodeIterator i = null;
758
759                             if (nodepath.indexOf("/") == -1) {
760                                 // If there is no '/' seperator, we only get fields from one nodemanager. This is the fastest
761
// way of getting those.
762
i = cloud.getNodeManager(xpath.substring(3)).getList(where,orderby,directions).nodeIterator();
763                             } else {
764                                 // If there are '/' seperators, we need to do a multilevel search. Therefore we first need to
765
// get a list of all the fields (as subnodes) to query.
766
String JavaDoc fields = "";
767                                 Element field = getFirstElement(node, FIELD);
768                                 nodepath = nodepath.replace('/', ',');
769                                 while (field != null) {
770                                     String JavaDoc fname = field.getAttribute(ELM_NAME);
771                                     if (!fields.equals(""))
772                                         fields += ",";
773                                     fields += fname;
774                                     field = getNextElement(field, FIELD);
775                                 }
776                                 i = cloud.getList("", nodepath, fields, where, orderby, directions, null, true).nodeIterator();
777                             }
778
779                             for(; i.hasNext(); ) {
780                                 org.mmbase.bridge.Node n=i.nextNode();
781                                 Element data=doc.createElement(OBJECT);
782                                 data.setAttribute(ELM_NUMBER, ""+n.getNumber());
783                                 querydata.appendChild(data);
784                                 getDataNode(node,data,n);
785                             }
786                         } catch(RuntimeException JavaDoc e) {
787                             Element err = addContentElement(ERROR,"node type " + xpath.substring(3) + " does not exist(" + e.toString() + ")",out);
788                             log.warn(Logging.stackTrace(e));
789                             err.setAttribute(ELM_TYPE, IS_CLIENT);
790                         }
791                     }
792                 }
793             } else {
794                 Element err = addContentElement(ERROR,"Unknown subtag in getlist: "+query.getTagName(),out);
795                 err.setAttribute(ELM_TYPE, IS_PARSER);
796             }
797             query=getNextElement(query);
798         }
799     }
800
801     /**
802      * Handles a put call.
803      * This method accepts a DOM element, which should contain as it
804      * child nodes elements describing the original and the new cloud.
805      * @param in the element that described the <code>put</code> call.
806      * The childnodes should describe the old and the new cloud.
807      * @param out the element that described the <code>put</code> result.
808      * The result of the put (an error or the resulting cloud) should be added
809      * as childs to this element.
810      * @param cloud the cloud to work on
811      * @param repository Repository that contains the blobs
812      */

813     public void put(Element in, Element out, Cloud cloud, Map repository) {
814         // first collect all new and original nodes
815
Map originalNodes = new HashMap();
816         Map newNodes = new HashMap();
817         Map originalRelations = new HashMap();
818         Map newRelations = new HashMap();
819
820         // get all the needed info from the xml stream
821
Element query = getFirstElement(in);
822         while (query!=null) { // select child tags, should be 'original' or 'new'
823
if (query.getTagName().equals(ORIGINAL) || query.getTagName().equals(NEW)) {
824                 boolean isOriginal = query.getTagName().equals(ORIGINAL);
825                 Element node = getFirstElement(query);
826
827                 while (node!=null) { // select all child tags, should be 'object'
828
if (node.getTagName().equals(OBJECT) || node.getTagName().equals(RELATION)) {
829                         boolean isRelation = node.getTagName().equals(RELATION);
830
831                         // store the values of one node
832
Map values = new HashMap();
833                         if (isRelation) {
834                             if (isOriginal) {
835                                 originalRelations.put(node.getAttribute(ELM_NUMBER), values);
836                             } else {
837                                 newRelations.put(node.getAttribute(ELM_NUMBER), values);
838                             }
839                         } else {
840                             if (isOriginal) {
841                                 originalNodes.put(node.getAttribute(ELM_NUMBER), values);
842                             } else {
843                                 newNodes.put(node.getAttribute(ELM_NUMBER),values);
844                             }
845                         }
846                         if (! isOriginal) {
847                             values.put("_status",node.getAttribute(ELM_STATUS));
848                         }
849
850                         String JavaDoc context = node.getAttribute(ELM_CONTEXT);
851                         if (context != null && !context.equals("")) {
852                             values.put("_context", context);
853                         }
854
855                         if (isRelation) {
856                             String JavaDoc role = node.getAttribute(ELM_ROLE);
857                             if (role!=null) values.put("_role",role);
858
859                             String JavaDoc source = node.getAttribute(ELM_SOURCE);
860                             if (source!=null) values.put("_source",source);
861
862                             String JavaDoc destination = node.getAttribute(ELM_DESTINATION);
863                             if (destination!=null) values.put("_destination", destination);
864                         } else {
865                             String JavaDoc type = node.getAttribute(ELM_TYPE);
866                             if (type!=null) values.put("_otype", type);
867                         }
868                         Element field = getFirstElement(node);
869                         while (field != null) { // select all child tags, should be 'fields'
870
if (field.getTagName().equals(FIELD)) {
871                                 String JavaDoc fieldname = field.getAttribute(ELM_NAME);
872                                 String JavaDoc href = field.getAttribute(ELM_HREF);
873                                 String JavaDoc encoding = field.getAttribute(ELM_ENCODING);
874                                 if (!href.equals("")) {
875                                     // binary data.
876
Object JavaDoc repval = repository.get(href);
877                                     if (repval != null) {
878                                         values.put(fieldname, repval);
879                                         // also retrieve and set filename
880
if(field.getFirstChild() != null) {
881                                             values.put("filename", field.getFirstChild().getNodeValue());
882                                         }
883                                     }
884                                 } else if (!encoding.equals("")) {
885                                     if (encoding.toLowerCase().equals("base64")) {
886                                         values.put(fieldname, new Encode("BASE64").decodeBytes(field.getFirstChild().getNodeValue()));
887                                     }
888                                 } else {
889                                     if(field.getFirstChild() == null) {
890                                         values.put(fieldname, "");
891                                     } else {
892                                         values.put(fieldname, field.getFirstChild().getNodeValue());
893                                     }
894                                 }
895                             }
896                             field = getNextElement(field);
897                         }
898                     }
899                     node = getNextElement(node);
900                 }
901             }
902             query = getNextElement(query);
903         }
904         // are there new nodes to handle ?
905
if (newNodes.size() > 0 || newRelations.size() > 0) {
906             Transaction trans = cloud.createTransaction();
907             Map addedNodes = new HashMap ();
908             Map addedRelations = new HashMap();
909             if (mergeClouds(originalNodes, newNodes, originalRelations, newRelations, addedNodes, addedRelations, out, trans) ) {
910                 try {
911                     trans.commit();
912                     // retrieve all numbers and reset them to the right value
913
// This is possible, as the nodes themselves contain this info after the
914
// transaction
915
//
916
for (Iterator i = addedNodes.entrySet().iterator(); i.hasNext(); ) {
917                         Map.Entry me=(Map.Entry)i.next();
918                         org.mmbase.bridge.Node n = (org.mmbase.bridge.Node)me.getKey();
919                         Element oe = (Element)me.getValue();
920                         oe.setAttribute(ELM_NUMBER, n.getStringValue("number"));
921                     }
922                     // retrieve all numbers, snumbers, dnumbers and reset them to the right value
923
for (Iterator i = addedRelations.entrySet().iterator(); i.hasNext(); ) {
924                         Map.Entry me=(Map.Entry)i.next();
925                         org.mmbase.bridge.Node n = (org.mmbase.bridge.Node)me.getKey();
926                         Element re = (Element)me.getValue();
927                         re.setAttribute(ELM_NUMBER, n.getStringValue("number"));
928                         re.setAttribute(ELM_SOURCE, "" + n.getIntValue("snumber"));
929                         re.setAttribute(ELM_DESTINATION, "" + n.getIntValue("dnumber"));
930                     }
931                 } catch (RuntimeException JavaDoc e) {
932                     Element err = addContentElement(ERROR, "Transaction failed : " + e.getMessage(), out);
933                     log.error(Logging.stackTrace(e));
934                     err.setAttribute(ELM_TYPE, IS_SERVER);
935                 }
936             } else {
937                 trans.cancel();
938             }
939         }
940     }
941
942
943     /**
944      * Converts the value of a field to a node reference (either a temporary id or an object alias or number).
945      * Used for determing values of the snumber and dnumber relation fields.
946      * @param name the name of the field
947      * @param values a Map with field values
948      * @param aliases a Map with mappings from XML aliases to node reference values
949      * @return the node reference value
950      */

951     protected String JavaDoc getNodeReferenceFromValue(String JavaDoc name, Map values, Map aliases) {
952         String JavaDoc result=null;
953         String JavaDoc value=(String JavaDoc)values.get(name);
954         Object JavaDoc tmp=aliases.get(value);
955         if (tmp==null) {
956             // oke its not a temporary alias, return the original value
957
result=value;
958         } else {
959             result=""+tmp;
960         }
961         return result;
962     }
963
964     /**
965      * Fills the fields of the specified node with the supplied values.
966      * The node's XML element is upadeytd with the affected field elements.
967      * This method does not validate whether a node's values have changed since the
968      * transaction started.
969      * @param alias the node alias in the put tree
970      * @param node the node to fill
971      * @param objectelement the XML element to fill with the changed data for feedback
972      * @param values a Map with new node values
973      * @return true if succesful, false if an error ocurred
974      */

975     protected boolean fillFields(String JavaDoc alias, org.mmbase.bridge.Node node, Element objectelement, Map values) {
976         return fillFields(alias, node, objectelement, values, null);
977     }
978
979     /**
980      * Fills the fields of the specified node with the supplied values.
981      * The node's XML element is updated with the affected field elements.
982      * This method validates whether a node's values have changed since the
983      * transaction started, and fails if this was teh acse.
984      * @param alias the node alias in the put tree
985      * @param node the node to fill
986      * @param out the XML element to fill with the changed data for feedback
987      * @param values a Map with new node values
988      * @param originalValues a Map with the original values of the node, needed for checking.
989      * if <code>null</code>, no checking takes place
990      * @return true if succesful, false if an error ocurred
991      */

992     protected boolean fillFields(String JavaDoc alias, org.mmbase.bridge.Node node, Element out, Map values, Map originalValues) {
993         node.getCloud().setProperty(Cloud.PROP_XMLMODE, "flat");
994         for (Iterator i = values.entrySet().iterator(); i.hasNext(); ) {
995             Map.Entry me = (Map.Entry)i.next();
996             String JavaDoc key = (String JavaDoc)me.getKey();
997             if (isEditableField(node.getNodeManager(),key)) {
998                 Object JavaDoc value = me.getValue();
999                 DataType dt = node.getNodeManager().getField(key).getDataType();
1000                String JavaDoc changes = (String JavaDoc) properties.getProperties().get(PROP_CHANGES);
1001                if ((! CHANGES_IGNORE.equals(changes)) &&
1002                    (originalValues != null) &&
1003                    (!(value instanceof byte[]))) { // XXX: currently, we do not validate on byte fields
1004
String JavaDoc originalValue = (String JavaDoc) originalValues.get(key);
1005                    String JavaDoc mmbaseValue;
1006                    if (dt instanceof DateTimeDataType ||
1007                        dt instanceof LongDataType ||
1008                        dt instanceof IntegerDataType) {
1009                        // have to convert ourselves because bridge will use user-defined formatting
1010
mmbaseValue = "" + node.getLongValue(key);
1011                    } else {
1012                        mmbaseValue = node.isNull(key) ? null : node.getStringValue(key);
1013                    }
1014                    if (mmbaseValue == null) {
1015                        if ("".equals(originalValue)) {
1016                            // XML cannot make difference between NULL and empty.
1017
originalValue = null;
1018                        }
1019                        if ("".equals(value)) {
1020                            value = null;
1021                        }
1022                    }
1023                    if ((originalValue != null ) && !originalValue.equals(mmbaseValue)) {
1024                        String JavaDoc message = "Node was changed in the cloud, node number : " + alias + " field name '" + key + "' value found: '" + mmbaseValue + "' value expected '" + originalValue + "' changes: " + changes;
1025                        // give error node was changed in cloud
1026
if (CHANGES_WARN.equals(changes)) {
1027                            log.warn(message);
1028                        } else {
1029                            log.debug(message);
1030                            Element err = addContentElement(ERROR, message, out);
1031                            err.setAttribute(ELM_TYPE, IS_SERVER);
1032                            return false;
1033                        }
1034                    }
1035                }
1036                if (log.isDebugEnabled()) {
1037                    log.debug("Setting field " + key + " to '" + value + "'");
1038                }
1039                if (value instanceof byte[]) {
1040                    node.setValue(key, value);
1041                } else {
1042                    node.setStringValue(key, value != null ? Casting.toString(value) : null);
1043                }
1044                Element fieldElement = doc.createElement(FIELD);
1045                fieldElement.setAttribute(ELM_NAME, key);
1046                if (!(value instanceof byte[])) {
1047                    Text tel = doc.createTextNode(value == null ? "" : value.toString());
1048                    fieldElement.appendChild(tel);
1049                }
1050                out.appendChild(fieldElement);
1051            }
1052        }
1053        return true;
1054    }
1055
1056    /**
1057     * Creates a new node.
1058     * @param alias the node alias in the put tree
1059     * @param values a Map with new node values
1060     * @param aliases a Map with mappings from XML aliases to node reference values
1061     * @param addedNodes a Map used to keep associations between xml elements and newly created nodes,
1062     * needed for resolving new node numbers
1063     * @param out the element that describes 'new' cloud.
1064     * The result of the put (an error or the resulting cloud) should be added
1065     * as childs to this element.
1066     * @param cloud the cloud to work on
1067     * @return true if succesful, false if an error ocurred
1068     */

1069    protected boolean putNewNode(String JavaDoc alias, Map values, Map aliases, Map addedNodes, Element out, Cloud cloud) {
1070        String JavaDoc type = (String JavaDoc) values.get("_otype");
1071        try {
1072            NodeManager nm = cloud.getNodeManager(type);
1073            org.mmbase.bridge.Node newnode = nm.createNode();
1074            Element objectelement = doc.createElement(OBJECT);
1075            objectelement.setAttribute(ELM_TYPE, type);
1076            fillFields(alias, newnode, objectelement, values);
1077            try {
1078                String JavaDoc context = (String JavaDoc) values.get("_context");
1079                if (context!=null) {
1080                  newnode.setContext(context);
1081                }
1082                newnode.commit();
1083                int number = newnode.getNumber();
1084                if (log.isDebugEnabled())
1085                    log.debug("Created new node " + number);
1086                aliases.put(alias,new Integer JavaDoc(number));
1087                objectelement.setAttribute(ELM_NUMBER,""+number);
1088                objectelement.setAttribute(ELM_OLDNUMBER, alias);
1089                // keep in transaction for later update...
1090
addedNodes.put(newnode,objectelement);
1091                // add node to response
1092
out.appendChild(objectelement);
1093                return true;
1094            } catch (RuntimeException JavaDoc e) {
1095                // give error
1096
Element err = addContentElement(ERROR,"Cloud can not insert this object, alias number : "+alias + "(" + e.toString() + ")",out);
1097                err.setAttribute(ELM_TYPE, IS_SERVER);
1098            }
1099        } catch (BridgeException e) {
1100            // give error this cloud doesn't support this type
1101
Element err = addContentElement(ERROR, Logging.stackTrace(e), out);
1102            err.setAttribute(ELM_TYPE, IS_SERVER);
1103        }
1104        return false;
1105    }
1106
1107    /**
1108     * Creates a new node.
1109     * @param alias the node alias in the put tree
1110     * @param values a Map with new node values
1111     * @param aliases a Map with mappings from XML aliases to node reference values
1112     * @param addedRelations a Map used to keep associations between xml elements and newly created relations,
1113     * needed for resolving new node numbers and references
1114     * @param out the element that describes 'new' cloud.
1115     * The result of the put (an error or the resulting cloud) should be added
1116     * as childs to this element.
1117     * @param cloud the cloud to work on
1118     * @return true if succesful, false if an error ocurred
1119     */

1120    protected boolean putNewRelation(String JavaDoc alias, Map values, Map aliases, Map addedRelations, Element out, Cloud cloud) {
1121        String JavaDoc role=(String JavaDoc)values.get("_role");
1122        try {
1123            RelationManager relman=cloud.getRelationManager(role);
1124            String JavaDoc sourcenumber=getNodeReferenceFromValue("_source",values, aliases);
1125            String JavaDoc destinationnumber=getNodeReferenceFromValue("_destination",values, aliases);
1126            if (log.isDebugEnabled()) log.debug("Creating a relations between node " + sourcenumber + " and " + destinationnumber);
1127            Relation newnode=relman.createRelation(cloud.getNode(sourcenumber), cloud.getNode(destinationnumber));
1128            Element relationelement=doc.createElement(RELATION);
1129            // note that source and destination may be switched (internally) when you
1130
// commit a bi-directional relation, if the order of the two differs in typerel
1131
relationelement.setAttribute(ELM_SOURCE,""+sourcenumber);
1132            relationelement.setAttribute(ELM_DESTINATION,""+destinationnumber);
1133            relationelement.setAttribute(ELM_ROLE,role);
1134            fillFields(alias,newnode,relationelement,values);
1135            try {
1136                String JavaDoc context = (String JavaDoc) values.get("_context");
1137                if (context!=null) {
1138                  newnode.setContext(context);
1139                }
1140                newnode.commit();
1141                int number = newnode.getNumber();
1142                if (log.isServiceEnabled()) log.service("Created new relation " + number);
1143                aliases.put(alias, new Integer JavaDoc(number));
1144                // keep in transaction for later update...
1145
addedRelations.put(newnode,relationelement);
1146                // add node to response
1147
relationelement.setAttribute(ELM_NUMBER,""+number); // result in transaction ???
1148
out.appendChild(relationelement);
1149                return true;
1150            } catch (RuntimeException JavaDoc e) {
1151                // give error
1152
Element err = addContentElement(ERROR,"Cloud can not insert this object, alias number : " + alias + "(" + e.toString() + ")",out);
1153                err.setAttribute(ELM_TYPE, IS_SERVER);
1154            }
1155        } catch (RuntimeException JavaDoc e) {
1156            // give error can't find builder of that type
1157
Element err = addContentElement(ERROR,"Error. Does the cloud support role :" + role + "?:" + e.getMessage(), out);
1158            err.setAttribute(ELM_TYPE, IS_CLIENT);
1159        }
1160        return false;
1161    }
1162
1163
1164    /**
1165     * Creates a new node.
1166     * @param alias the node alias in the put tree
1167     * @param originalValues a Map with the original values of the node
1168     * @param out the element that describes 'new' cloud.
1169     * The result of the put (an error or the resulting cloud) should be added
1170     * as childs to this element.
1171     * @param cloud the cloud to work on
1172     * @return true if succesful, false if an error ocurred
1173     */

1174    protected boolean putDeleteNode(String JavaDoc alias, Map originalValues, Element out, Cloud cloud) {
1175        // check if this org node is also found in
1176
// mmbase cloud and if its still the same
1177
// also check if its the same type
1178
String JavaDoc type = (String JavaDoc)originalValues.get("_otype");
1179        try {
1180            NodeManager nm = cloud.getNodeManager(type);
1181            try {
1182                org.mmbase.bridge.Node mmbaseNode = cloud.getNode(alias);
1183                if (mmbaseNode.getNodeManager().getName().equals(nm.getName()) ) {
1184                    mmbaseNode.delete(true);
1185                    return true;
1186                } else {
1187                    // give error its the wrong type
1188
Element err = addContentElement(ERROR,"Node not same type as in the cloud, node number : "+alias+", cloud type="+mmbaseNode.getNodeManager().getName()+", expected="+nm.getName()+")",out);
1189                    err.setAttribute(ELM_TYPE, IS_SERVER);
1190                }
1191            } catch(RuntimeException JavaDoc e) {
1192                // give error node not found
1193
Element err = addContentElement(ERROR,"Node not in the cloud (anymore?), node number : "+alias + "(" + e.toString() + ")",out);
1194                err.setAttribute(ELM_TYPE, IS_SERVER);
1195            }
1196        } catch(RuntimeException JavaDoc e) {
1197            // give error can't find builder of that type
1198
Element err = addContentElement(ERROR,"Cloud does not support type : "+type + "(" + e.toString() + ")",out);
1199            err.setAttribute(ELM_TYPE, IS_CLIENT);
1200        }
1201        return false;
1202     }
1203
1204    /**
1205     * Creates a new node.
1206     * @param alias the node alias in the put tree
1207     * @param originalValues a Map with the original values of the relation
1208     * @param out the element that describes 'new' cloud.
1209     * The result of the put (an error or the resulting cloud) should be added
1210     * as childs to this element.
1211     * @param cloud the cloud to work on
1212     * @return true if succesful, false if an error ocurred
1213     */

1214    protected boolean putDeleteRelation(String JavaDoc alias, Map originalValues, Element out, Cloud cloud) {
1215        String JavaDoc role = (String JavaDoc)originalValues.get("_role");
1216        try {
1217            RelationManager relman = cloud.getRelationManager(role);
1218            try {
1219                org.mmbase.bridge.Node mmbaseNode = cloud.getNode(alias);
1220                // check if they are the same type
1221
if (mmbaseNode.getNodeManager().getName().equals(relman.getName()) ) {
1222                    mmbaseNode.delete(true);
1223                    return true;
1224                } else {
1225                    Element err = addContentElement(ERROR,"Node not same type as in the cloud, node number : "+alias+", cloud type="+mmbaseNode.getNodeManager().getName()+", expected="+relman.getName()+")",out);
1226                    err.setAttribute(ELM_TYPE, IS_SERVER);
1227                }
1228            } catch (RuntimeException JavaDoc e) {
1229                Element err = addContentElement(ERROR,"Relation not in the cloud (anymore?), relation number : "+alias,out);
1230                err.setAttribute(ELM_TYPE, IS_SERVER);
1231            }
1232        } catch (RuntimeException JavaDoc e) {
1233            // give error can't find builder of that type
1234
Element err = addContentElement(ERROR,"Cloud does not support role : "+role, out);
1235            err.setAttribute(ELM_TYPE, IS_SERVER);
1236        }
1237        return false;
1238     }
1239
1240    /**
1241     * Creates a new node.
1242     * @param alias the node alias in the put tree
1243     * @param values a Map with new node values
1244     * @param originalValues a Map with original node values, used for validation
1245     * @param aliases a Map with mappings from XML aliases to node reference values
1246     * @param out the element that describes 'new' cloud.
1247     * The result of the put (an error or the resulting cloud) should be added
1248     * as childs to this element.
1249     * @param cloud the cloud to work on
1250     * @return true if succesful, false if an error ocurred
1251     */

1252    protected boolean putChangeNode(String JavaDoc alias, Map values, Map originalValues, Map aliases, Element out, Cloud cloud) {
1253        // now check if this org node is also found in
1254
// mmbase cloud and if its still the same
1255
// also check if its the same type
1256
String JavaDoc type = (String JavaDoc)values.get("_otype");
1257        try {
1258            NodeManager nm = cloud.getNodeManager(type);
1259            try {
1260                org.mmbase.bridge.Node mmbaseNode = cloud.getNode(alias);
1261                // check if they are the same type
1262
if (mmbaseNode.getNodeManager().getName().equals(nm.getName()) ) {
1263                    // create new node for response
1264
Element objectElement = doc.createElement(OBJECT);
1265                    objectElement.setAttribute(ELM_TYPE, type);
1266                    objectElement.setAttribute(ELM_NUMBER, alias);
1267                    if (!fillFields(alias, mmbaseNode, objectElement, values, originalValues)) {
1268                        out.appendChild(objectElement);
1269                        return false;
1270                    }
1271                    mmbaseNode.commit();
1272                    // add node to response
1273
out.appendChild(objectElement);
1274                    return true;
1275                } else {
1276                    // give error its the wrong type
1277
Element err = addContentElement(ERROR,"Node not same type as in the cloud, node number : " + alias + ", cloud type=" + mmbaseNode.getNodeManager().getName()+", expected=" + nm.getName() + ")", out);
1278                    err.setAttribute(ELM_TYPE, IS_SERVER);
1279                }
1280
1281            } catch(RuntimeException JavaDoc e) {
1282                // give error node not found
1283
log.error(Logging.stackTrace(e));
1284                Element err = addContentElement(ERROR,"Node not in the cloud (any more), node number : " + alias + "(" + e.toString() + ")", out);
1285                err.setAttribute(ELM_TYPE, IS_SERVER);
1286            }
1287        } catch(RuntimeException JavaDoc e) {
1288            // give error can't find builder of that type
1289
Element err = addContentElement(ERROR, "Cloud does not support type : " + type + "(" + e.toString() + ")", out);
1290            err.setAttribute(ELM_TYPE, IS_CLIENT);
1291        }
1292        return false;
1293    }
1294
1295    /**
1296     * Performs the put within a transaction.
1297     * @param originalNodes the nodes in the original cloud, used in validation
1298     * @param newNodes the nodes in the new cloud
1299     * @param originalRelations the relations in the original cloud, used in validation
1300     * @param newRelations the relations in the new cloud
1301     * @param addedNodes a Map used to keep associations between xml elements and newly created nodes,
1302     * needed for resolving new node numbers and references
1303     * @param addedRelations a Map used to keep associations between xml elements and newly created relations,
1304     * needed for resolving new node numbers and references
1305     * @param out the element that described the <code>put</code> result.
1306     * The result of the put (an error or the resulting cloud) should be added
1307     * as childs to this element.
1308     * @param cloud the cloud to work on
1309     */

1310    protected boolean mergeClouds(Map originalNodes, Map newNodes, Map originalRelations, Map newRelations,
1311                                  Map addedNodes, Map addedRelations, Element out, Cloud cloud) {
1312        Map aliases = new HashMap(); // hash from alias names to real names
1313

1314        // create new tag and add it to response
1315
Element newElement = doc.createElement(NEW);
1316        out.appendChild(newElement);
1317
1318        // lets enum all the newNodes
1319
for (Iterator i = newNodes.entrySet().iterator(); i.hasNext(); ) {
1320            // handle the several cases we have when we have
1321
// a new node (merge, really new)
1322
Map.Entry me = (Map.Entry)i.next();
1323            String JavaDoc alias = (String JavaDoc)me.getKey();
1324            Map values = (Map)me.getValue();
1325            String JavaDoc status = (String JavaDoc)values.get("_status");
1326
1327            // is it a new node if so create one and remember its alias
1328
if (status != null && status.equals("new")) {
1329                if (!putNewNode(alias, values, aliases, addedNodes, newElement, cloud)) return false;
1330            } else if (status != null && status.equals("delete")) {
1331                // to check if they send a original
1332
// XXX: no original is not an error ???
1333
Map originalValues = (Map) originalNodes.get(alias);
1334                if (originalValues!=null) {
1335                    if (!putDeleteNode(alias, originalValues, newElement, cloud)) return false;;
1336                }
1337            } else if (status == null || status.equals("") || status.equals("change")) {
1338                // check if they send a original
1339
Map originalValues = (Map )originalNodes.get(alias);
1340                if (originalValues != null) {
1341                    if(!putChangeNode(alias, values, originalValues, aliases, newElement, cloud)) return false;
1342                } else {
1343                    // give error not a org. node
1344
Element err = addContentElement(ERROR,"Node not defined in original tag, node number : " + alias, out);
1345                    err.setAttribute(ELM_TYPE, IS_SERVER);
1346                    return false;
1347                }
1348            } else {
1349                // give error not a org. node
1350
Element err = addContentElement(ERROR,"Invalid status "+status+" for node : "+alias,out);
1351                err.setAttribute(ELM_TYPE, IS_SERVER);
1352                return false;
1353            }
1354        }
1355
1356        // now handle all the relations
1357
for (Iterator i = newRelations.entrySet().iterator(); i.hasNext(); ) {
1358            // handle the several cases we have when we have
1359
// a new node (merge, really new)
1360
Map.Entry me = (Map.Entry)i.next();
1361            String JavaDoc alias = (String JavaDoc)me.getKey();
1362            Map values = (Map)me.getValue();
1363            String JavaDoc status = (String JavaDoc)values.get("_status");
1364
1365            // is it a new node if so create one and remember its alias
1366
if (status != null && status.equals("new")) {
1367                if (!putNewRelation(alias, values, aliases, addedRelations, newElement, cloud)) return false;
1368            } else if (status != null && status.equals("delete")) {
1369                Map originalValues = (Map)originalRelations.get(alias);
1370                if (originalValues != null) {
1371                    if(!putDeleteRelation(alias, originalValues, newElement, cloud)) return false;;
1372                } // no error ???
1373
// add code for change relation ?
1374
} else {
1375                // give error not a org. node
1376
Element err = addContentElement(ERROR, "Invalid status " + status + " for node : " + alias, out);
1377                err.setAttribute(ELM_TYPE, IS_SERVER);
1378                return false;
1379            }
1380        }
1381        return true;
1382    }
1383
1384    /**
1385     * Handles a request running one or more RPCs.
1386     * This method accepts a root DOM element, which should contain as it
1387     * child nodes elements describing the call(s) to perform.
1388     * Each element is an xml element with as the tagname the name of the
1389     * call to run. Valid calls are getdata, getrelations, getlist, getconstraints,
1390     * getnew, and put.
1391     * The calls are redirected to the appropriate encapsulating method in
1392     * this class. The end result of each call should be a DOM tree, built
1393     * using the given parameters, which is then appended to the out element.
1394     * <br />
1395     * XXX:This method runs all commands in order of entrance. It does
1396     * not currently check whether all child nodes refer to the same call.
1397     * This means that it is possible to mix 'getdata' and 'getrelations' calls
1398     * in one request. Ina dditon, it ignores calls it does not know how to handle.
1399     * as described above, this should be handled by the xml parser.
1400     * Note also that if no id was given for a call, a NullPointer exception is thrown.
1401     *
1402     * @param in the element that described the request (or input).
1403     * The childnodes should describe the calls to perform.
1404     * @param out the element that described the response (or return value).
1405     * Results of calls should be added as childs to this element.
1406     * @param cloud the cloud to work on - if null, a cloud will be created by the Dove class
1407     * @param repository Repository that contains the blobs
1408     */

1409    public void doRequest(Element in, Element out, Cloud cloud, Map repository) {
1410        // set repository for blobs
1411
if (repository == null) repository = new HashMap();
1412
1413        Element command = getFirstElement(in);
1414        if ((cloud == null)) {
1415            // make new cloud if not specified,
1416
cloud = checkSecurity(command,out);
1417            if(cloud == null) {
1418                return; // Athentication failed, so stop
1419
}
1420            command = getNextElement(command);
1421        }
1422        // skip security if cloud was given - or throw exception???
1423
if (SECURITY.equals(command.getTagName())) {
1424            command = getNextElement(command);
1425        }
1426
1427        while (command!=null) {
1428            String JavaDoc cmd = command.getTagName();
1429            Element data = doc.createElement(cmd);
1430            if (command.hasAttribute(ELM_ID)) {
1431                data.setAttribute(ELM_ID, command.getAttribute(ELM_ID));
1432            }
1433            out.appendChild(data);
1434            if (cmd.equals(GETDATA)) {
1435                getData(command, data, cloud);
1436            } else if (cmd.equals(GETNEW)) {
1437                getNew(command, data, cloud);
1438            } else if (cmd.equals(GETNEWRELATION)) {
1439                getNewRelation(command, data, cloud);
1440            } else if (cmd.equals(GETRELATIONS)) {
1441                getRelations(command, data, cloud);
1442            } else if (cmd.equals(GETCONSTRAINTS)) {
1443                getConstraints(command, data, cloud);
1444            } else if (cmd.equals(GETLIST)) {
1445                getList(command, data, cloud);
1446            } else if (cmd.equals(PUT)) {
1447                put(command, data, cloud, repository);
1448            } else {
1449                Element err = addContentElement(ERROR, "Unknown command: " + cmd, data);
1450                err.setAttribute(ELM_TYPE, IS_PARSER);
1451            }
1452            command = getNextElement(command);
1453        }
1454    }
1455
1456    /**
1457     * checkSecurity tries to authenticate the user that performs the request
1458     * @return true if the user is authenticated, false otherwise
1459     */

1460    private Cloud checkSecurity(Element command, Element out) {
1461        Element data = doc.createElement(SECURITY);
1462        out.appendChild(data);
1463
1464        if (command!=null && command.getTagName().equals(SECURITY)) {
1465            String JavaDoc userName = command.getAttribute(SECURITY_NAME); // retrieve name
1466
try {
1467                String JavaDoc password = command.getAttribute(SECURITY_PASSWORD); // retrieve password
1468
String JavaDoc methodName = command.getAttribute(SECURITY_METHOD ); // retrieve method name
1469
if ((methodName == null) || (methodName.equals(""))) methodName="name/password";
1470                String JavaDoc cloudName = command.getAttribute(SECURITY_CLOUD); // retrieve cloud name
1471
if ((cloudName == null) || (cloudName.equals(""))) cloudName="mmbase";
1472                Map user = new HashMap();
1473                if ((userName!=null) && (!userName.equals(""))) {
1474                    user.put("username", userName);
1475                    user.put("password", password);
1476                }
1477                Cloud cloud = ContextProvider.getDefaultCloudContext().getCloud(cloudName, methodName, user);
1478                return cloud;
1479            } catch (RuntimeException JavaDoc e) {
1480                // most likely security failed...
1481
log.warn("Authentication error : " + e.getMessage());
1482                Element err = addContentElement(ERROR, "Authentication error : " + e.getMessage(), data);
1483                err.setAttribute(ELM_TYPE, IS_CLIENT);
1484            }
1485        } else {
1486            log.warn("Authentication error (security info missing)");
1487            Element err = addContentElement(ERROR, "Authentication error (security info missing).", data);
1488            err.setAttribute(ELM_TYPE, IS_CLIENT);
1489        }
1490        return null;
1491    }
1492}
1493
1494
Popular Tags