KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > applications > editwizard > WizardDatabaseConnector


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

10 package org.mmbase.applications.editwizard;
11
12 import org.mmbase.bridge.Cloud;
13 import java.util.*;
14 import org.mmbase.applications.dove.Dove;
15 import org.mmbase.util.logging.*;
16 import org.mmbase.util.Casting;
17 import org.w3c.dom.*;
18
19
20 /**
21  * This class handles all communition with mmbase. It uses the MMBase-Dove code to do the transactions and get the information
22  * needed for rendering the wizard screens.
23  * The WizardDatabaseConnector can connect to MMBase and get data, relations, constraints, lists. It can also
24  * store changes, create new objects and relations.
25  *
26  * The connector can be instantiated without the wizard class, but will usually be called from the Wizard class itself.
27  *
28  * EditWizard
29  * @javadoc
30  * @author Kars Veling
31  * @author Michiel Meeuwissen
32  * @author Pierre van Rooden
33  * @since MMBase-1.6
34  * @version $Id: WizardDatabaseConnector.java,v 1.46 2006/02/13 16:16:50 pierre Exp $
35  *
36  */

37 public class WizardDatabaseConnector {
38
39     // logging
40
private static final Logger log = Logging.getLoggerInstance(WizardDatabaseConnector.class);
41
42     int didcounter=1;
43     private Cloud userCloud = null;
44
45     /**
46      * Constructor: Creates the connector. Call #init also.
47      */

48     public WizardDatabaseConnector(){
49     }
50
51     /**
52      * Sets the right cloud for the user info.
53      *
54      * @param cloud The cloud from which the userinfo should be set.
55      */

56     public void setUserInfo(Cloud cloud) {
57         userCloud=cloud;
58     }
59
60     /**
61      * This method tags the datanodes in the given document. The wizards use the tagged datanodes so that each datanode can be identified.
62      *
63      * @param data The data document which should be tagged.
64      */

65     public void tagDataNodes(Document data) {
66         didcounter = 1;
67         tagDataNode(data.getDocumentElement());
68     }
69
70     /**
71      * This method tags datanodes starting from the current node.
72      * A internal counter is used to make sure identifiers are still unique.
73      */

74     public void tagDataNode(Node node) {
75         NodeList nodes = Utils.selectNodeList(node, ".|.//*");
76         didcounter = Utils.tagNodeList(nodes, "did", "d", didcounter);
77         // if value is a datetime or boolean value,it should be parsed and converted to an integer,
78
// as this is how the wizards currently accept datetime/boolean values.
79
/* not needed for dates as currently dates are still passed as integers
80           convertDateTimeToInt(node);
81         */

82         convertBooleanToInt(node);
83     }
84
85     /**
86      * This method loads relations from MMBase and stores the result in the given object node.
87      *
88      * @param object The objectNode where the results should be appended to.
89      * @param objectnumber The objectnumber of the parentobject from where the relations should originate.
90      * @param loadaction The node with loadaction data. Has inforation about what relations should be loaded and what fields should be retrieved.
91      * @return The new relations (in the data object), or <code>null</code> if none.
92      * @throws WizardException if loading the relations fails
93      */

94
95     Collection loadRelations(Node object, String JavaDoc objectNumber, Node loadAction) throws WizardException {
96         // loads relations using the loadaction rules
97
NodeList allRelations = Utils.selectNodeList(loadAction, ".//relation");
98
99         if (log.isDebugEnabled()) {
100             log.debug("All relations " + Utils.getXML(allRelations) + " adding to " + Utils.getXML(object));
101         }
102         // complete relations: add empty <object> tag where there is none.
103
for (int i = 0; i < allRelations.getLength(); i++) {
104             Node relation = allRelations.item(i);
105             // if there is not yet an object attached, load it now
106
NodeList objects = Utils.selectNodeList(relation, "object");
107             if (objects.getLength() == 0) {
108                 relation.appendChild(relation.getOwnerDocument().createElement("object"));
109             }
110         }
111         // root list of relations
112
NodeList relations = Utils.selectNodeList(loadAction, "relation");
113         // load relations (automatically loads related objects and 'deep' relations)
114
if (relations.getLength() > 0) {
115             return getRelations(object, objectNumber, relations);
116         } else {
117             return new ArrayList();
118         }
119
120     }
121
122     /**
123      * This method loads an object and the necessary relations and fields, according to the given schema.
124      *
125      * @param schema The schema carrying all the information needed for loading the proper object and related fields and objects.
126      * @param objectNumber The objectnumber of the object to start with.
127      * @return The resulting data document.
128      * @throws WizardException if loading the schema fails
129      * @since MMBase-1.7
130      */

131     public Document load(Node schema, String JavaDoc objectNumber) throws WizardException {
132         // intialize data xml
133
Document data = Utils.parseXML("<data />");
134
135         // load initial object using object number
136
log.debug("Loading: " + objectNumber);
137
138         // restrict fields to load
139
NodeList fieldstoload = Utils.selectNodeList(schema, "action[@type='load']/field");
140         Node object=null;
141         if (fieldstoload == null || fieldstoload.getLength() == 0) {
142             object = getData(data.getDocumentElement(), objectNumber);
143         } else {
144             object = getData(data.getDocumentElement(), objectNumber, fieldstoload);
145         }
146
147         // load relations, if present
148
Node loadAction = Utils.selectSingleNode(schema, "action[@type='load']");
149         if (loadAction != null) {
150             loadRelations(object, objectNumber, loadAction);
151         }
152         tagDataNodes(data);
153         return data;
154     }
155
156
157     /**
158      * This method gets constraint information from mmbase about a specific objecttype.
159      *
160      * @param objecttype the objecttype where you want constraint information from.
161      * @return the constraintsnode as received from mmbase (Dove)
162      * @throws WizardException if the constraints could not be obtained
163      */

164     public Node getConstraints(String JavaDoc objecttype) throws WizardException {
165         // fires getData command and places result in targetNode
166
ConnectorCommand cmd = new ConnectorCommandGetConstraints(objecttype);
167         fireCommand(cmd);
168
169         if (!cmd.hasError()) {
170             // place object in targetNode
171
Node result = cmd.getResponseXML().getFirstChild().cloneNode(true);
172             return result;
173         } else {
174             throw new WizardException("Could not obtain constraints for " + objecttype + " : " + cmd.getError());
175         }
176     }
177
178     /**
179      * This method retrieves a list from mmbase. It uses a query which is sent to mmbase.
180      * @param query the node containign the query to run
181      * @return a node containing as its childnodes the list of nodes queried
182      * @throws WizardException if the constraints could not be obtained
183      */

184     public Node getList(Node query) throws WizardException {
185         // fires getData command and places result in targetNode
186
ConnectorCommand cmd = new ConnectorCommandGetList(query);
187         fireCommand(cmd);
188
189         if (!cmd.hasError()) {
190            // place object in targetNode
191
if (log.isDebugEnabled()) log.debug(Utils.getSerializedXML(cmd.getResponseXML()));
192             Node result = cmd.getResponseXML().cloneNode(true);
193             return result;
194         } else {
195             throw new WizardException("Could not get list : " + cmd.getError());
196         }
197     }
198
199     /**
200      * This method retrieves data (objectdata) from mmbase.
201      *
202      * @param targetNode Results are appended to this targetNode.
203      * @param objectnumber The number of the object to load.
204      * @return The resulting node with the objectdata.
205      * @throws WizardException if loading the object fails
206      */

207     public Node getData(Node targetNode, String JavaDoc objectnumber) throws WizardException {
208         return getData(targetNode, objectnumber, null);
209     }
210
211     /**
212      * This method retrieves data (objectdata) from mmbase.
213      *
214      * @param targetNode Results are appended to this targetNode.
215      * @param objectnumber The number of the object to load.
216      * @param restrictions These restrictions will restrict the load action. So that not too large or too many fields are retrieved.
217      * @throws WizardException if the object could not be retrieved
218      * @return The resulting node with the objectdata.
219      */

220     public Node getData(Node targetNode, String JavaDoc objectnumber, NodeList restrictions) throws WizardException {
221         // fires getData command and places result in targetNode
222
Node objectNode = getDataNode(targetNode.getOwnerDocument(), objectnumber, restrictions);
223         // place object in targetNode
224
targetNode.appendChild(objectNode);
225         return objectNode;
226     }
227
228     /**
229      * This method retrieves data (objectdata) from mmbase.
230      *
231      * @param document Results are imported in this document
232      * @param objectnumber The number of the object to load.
233      * @param restrictions These restrictions will restrict the load action. So that not too large or too many fields are retrieved.
234      * @return The resulting node with the objectdata.
235      * @throws WizardException if the object could not be retrieved
236      */

237     public Node getDataNode(Document document, String JavaDoc objectnumber, NodeList restrictions) throws WizardException {
238         // fires getData command and places result in targetNode
239
ConnectorCommandGetData cmd = new ConnectorCommandGetData(objectnumber, restrictions);
240         fireCommand(cmd);
241
242         if (!cmd.hasError()) {
243             // place object in targetNode
244
Node objectNode = Utils.selectSingleNode(cmd.getResponseXML(), "/*/object[@number='" + objectnumber + "']");
245             // if no destination document is given , do not copy or tag the node, just return it
246
if (document != null ) {
247                 // copy ??? not sure if all of this is really necessary?
248
objectNode = document.importNode(objectNode.cloneNode(true),true);
249                 tagDataNode(objectNode);
250             }
251             return objectNode;
252         } else {
253             throw new WizardException("Could not obtain object " + objectnumber + " : " + cmd.getError());
254         }
255     }
256
257     /**
258      * This method gets relation information from mmbase.
259      *
260      * @param targetNode The targetnode where the results should be appended.
261      * @param objectnumber The objectnumber of the parent object from where the relations originate.
262      * @throws WizardException if the relations could not be obtained
263      */

264     public void getRelations(Node targetNode, String JavaDoc objectnumber) throws WizardException {
265         getRelations(targetNode, objectnumber, null);
266     }
267
268     /**
269      * This method gets relation information from mmbase.
270      *
271      * @param targetNode The targetnode where the results should be appended.
272      * @param objectNumber The objectnumber of the parent object from where the relations originate.
273      * @param queryRelations A list of 'relation' DOM-nodes, defining the relations which must be fetched.
274      * @throws WizardException if the relations could not be obtained
275      */

276     public Collection getRelations(Node targetNode, String JavaDoc objectNumber, NodeList queryRelations) throws WizardException {
277
278         // fires getRelations command and places results inside targetNode
279
ConnectorCommandGetRelations cmd = new ConnectorCommandGetRelations(objectNumber, queryRelations);
280         fireCommand(cmd);
281         if (!cmd.hasError()) {
282             NodeList relations = Utils.selectNodeList(cmd.getResponseXML(), "/*/object/relation");
283             for (int i = 0; i < relations.getLength(); i++) {
284                 tagDataNode(relations.item(i));
285             }
286             return Utils.appendNodeList(relations, targetNode);
287             // return relations;
288
} else {
289             throw new WizardException("Could not ontain relations for " + objectNumber + " : " + cmd.getError());
290         }
291
292     }
293
294     /**
295      * This method gets a new temporarily object of the given type.
296      *
297      * @param targetNode The place where the results should be appended.
298      * @param objecttype The objecttype which should be created.
299      * @return The resulting object node.
300      * @throws WizardException if the node could not be created
301      */

302     public Node getNew(Node targetNode, String JavaDoc objecttype) throws WizardException {
303         // fires getNew command and places result in targetNode
304
ConnectorCommandGetNew cmd = new ConnectorCommandGetNew(objecttype);
305         fireCommand(cmd);
306
307         if (!cmd.hasError()) {
308             if (targetNode == null) {
309                 throw new WizardException("No targetNode found");
310             }
311             Node objectNode = targetNode.getOwnerDocument().importNode(Utils.selectSingleNode(cmd.getResponseXML(), "/*/object[@type='"+objecttype+"']").cloneNode(true), true);
312             tagDataNode(objectNode);
313             targetNode.appendChild(objectNode);
314             return objectNode;
315         } else {
316             throw new WizardException("Could not create new object of type " + objecttype + " : " + cmd.getError());
317         }
318     }
319
320     /**
321      * This method creates a new temporarily relation.
322      *
323      * @param targetNode The place where the results should be appended.
324      * @param role The name of the role the new relation should have.
325      * @param sourceObjectNumber the number of the sourceobject
326      * @param sourceType the type of the sourceobject
327      * @param destinationObjectNumber the number of the destination object
328      * @param destinationType the type of the destination object
329      * @return The resulting relation node.
330      * @throws WizardException if the relation could not be created
331      */

332     public Node getNewRelation(Node targetNode, String JavaDoc role,
333                               String JavaDoc sourceObjectNumber, String JavaDoc sourceType,
334                               String JavaDoc destinationObjectNumber, String JavaDoc destinationType, String JavaDoc createDir) throws WizardException {
335         // fires getNewRelation command and places result in targetNode
336
ConnectorCommandGetNewRelation cmd = new ConnectorCommandGetNewRelation(role, sourceObjectNumber, sourceType, destinationObjectNumber, destinationType, createDir);
337         fireCommand(cmd);
338
339         if (!cmd.hasError()) {
340             Node objectNode = targetNode.getOwnerDocument().importNode(Utils.selectSingleNode(cmd.getResponseXML(), "/*/relation").cloneNode(true), true);
341             tagDataNode(objectNode);
342             targetNode.appendChild(objectNode);
343             return objectNode;
344         } else {
345             throw new WizardException("Could not create new relation, role=" + role +
346                         ", source=" + sourceObjectNumber + " (" + sourceType + ")" +
347                         ", destination=" + destinationObjectNumber + " ("+destinationType+")" +
348                         " : " + cmd.getError());
349         }
350     }
351
352     /**
353      * Adds or replaces values specified for fields in the wizard to a recently created node.
354      * @param data the document conatining all (current) object data
355      * @param targetParentNode The place where the results should be appended.
356      * @param objectDef The objectdefinition.
357      * @param objectNode The new object
358      * @param params The parameters to use when creating the objects and relations.
359      * @param createorder ordernr under which this object is added (i.e. when added to a list)
360      * The first ordernr in a list is 1
361      */

362     private void fillObjectFields(Document data, Node targetParentNode, Node objectDef,
363                                   Node objectNode, Map params, int createorder) throws WizardException {
364         // fill-in (or place) defined fields and their values.
365
NodeList fields = Utils.selectNodeList(objectDef, "field");
366         for (int i=0; i<fields.getLength(); i++) {
367             Node field = fields.item(i);
368             String JavaDoc fieldname = Utils.getAttribute(field, "name");
369             // does this field already exist?
370
Node datafield = Utils.selectSingleNode(objectNode, "field[@name='"+fieldname+"']");
371             if (datafield==null) {
372                 // None-existing field (getNew/getNewRelationa always return all fields)
373
String JavaDoc type = Utils.getAttribute(objectDef, "type");
374                 throw new WizardException("field " + fieldname + " does not exist in '" + type + "'");
375             }
376             String JavaDoc value = Utils.getText(field);
377
378             // if you add a list of items, the order of the list may be of import.
379
// the variable $pos is used to make that distinction
380
params.put("pos",createorder+"");
381             Node parent = data.getDocumentElement();
382             if (log.isDebugEnabled()) log.debug("parent="+parent.toString());
383             value = Utils.transformAttribute(parent,value,false,params);
384             params.remove("pos");
385             if (value == null) {
386                 value = "";
387             }
388             if (datafield != null) {
389                 // yep. fill-in
390
Utils.storeText(datafield, value, params); // place param values inside if needed
391
} else {
392                 // nope. create. (Or, actually, clone and import node from def and place it in data
393
Node newfield = targetParentNode.getOwnerDocument().importNode(field.cloneNode(true), true);
394                 objectNode.appendChild(newfield);
395                 Utils.storeText(newfield, value, params); // process innerText: check for params
396
}
397         }
398     }
399
400     /**
401      * This method can create a object (or a tree of objects and relations)
402      *
403      * @param targetParentNode The place where the results should be appended.
404      * @param objectDef The objectdefinition.
405      * @param params The params to use when creating the objects and relations.
406      * @return The resulting object(tree) node.
407      * @throws WizardException if the object cannot be created
408      */

409     public Node createObject(Document data, Node targetParentNode, Node objectDef, Map params) throws WizardException {
410         return createObject(data, targetParentNode, objectDef, params, 1);
411     }
412
413     /**
414      * This method can create a object (or a tree of objects and relations)
415      *
416      * this method should be called from the wizard if it needs to create a new
417      * combination of fields, objects and relations.
418      * See further documentation or the samples for the definition of the action
419      *
420      * in global: the Action node looks like this: (Note: this function expects the $lt;object/> node,
421      * not the action node.
422      * <pre>
423      * $lt;action type="create"$gt;
424      * $lt;object type="authors"$gt;
425      * $lt;field name="name"$gt;Enter name here$lt;/field$gt;
426      * $lt;relation role="related" [destination="234" *]$gt;
427      * $lt;object type="locations"$gt;
428      * $lt;field name="street"$gt;Enter street$lt;/field$gt;
429      * $lt;/object$gt;
430      * $lt;/relation$gt;
431      * $lt;/object$gt;
432      * $lt;/action$gt;
433      * </pre>
434      * *) if dnumber is supplied, no new object is created (shouldn't be included in the relation node either),
435      * but the relation will be created and directly linked to an object with number "dnumber".
436      *
437      * @param targetParentNode The place where the results should be appended.
438      * @param objectDef The objectdefinition.
439      * @param params The params to use when creating the objects and relations.
440      * @param createorder ordernr under which this object is added (i.e. when added to a list)
441      * The first ordernr in a list is 1
442      * @return The resulting object(tree) node.
443      * @throws WizardException if the object cannot be created
444      */

445     public Node createObject(Document data, Node targetParentNode, Node objectDef, Map params, int createorder) throws WizardException {
446
447         String JavaDoc context = (String JavaDoc)params.get("context");
448
449         if (objectDef == null) throw new WizardException("No 'objectDef' given"); // otherwise NPE in getAttribute
450

451         String JavaDoc nodeName = objectDef.getNodeName();
452
453         // check if we maybe should create multiple objects or relations
454

455         if (nodeName.equals("action")) {
456             NodeList objectdefs = Utils.selectNodeList(objectDef, "object|relation");
457             Node firstobject = null;
458             for (int i=0; i < objectdefs.getLength(); i++) {
459                 firstobject = createObject(data, targetParentNode, objectdefs.item(i), params);
460             }
461             log.debug("This is an action"); // no relations to add here..
462
return firstobject;
463         }
464
465         NodeList relations;
466         Node objectNode;
467
468         if (nodeName.equals("relation")) {
469             // objectNode equals targetParentNode
470
objectNode = targetParentNode;
471             if (objectNode == null) {
472                 throw new WizardException("Could not find a parent node for relation " + Utils.stringFormatted(objectDef));
473             }
474             relations = Utils.selectNodeList(objectDef, ".");
475         } else if (nodeName.equals("object")) {
476             String JavaDoc objectType = Utils.getAttribute(objectDef, "type");
477             if (objectType.equals("")) throw new WizardException("No 'type' attribute used in " + Utils.stringFormatted(objectDef));
478             if (log.isDebugEnabled()) log.debug("Create object of type " + objectType);
479             // create a new object of the given type
480
objectNode = getNew(targetParentNode, objectType);
481             if (context!=null && !context.equals("")) {
482                 Utils.setAttribute(objectNode, "context", context);
483             }
484             fillObjectFields(data,targetParentNode,objectDef,objectNode,params,createorder);
485             relations = Utils.selectNodeList(objectDef, "relation");
486         } else {
487            throw new WizardException("Can only create with 'action' 'object' or 'relation' nodes");
488         }
489
490
491         // Let's see if we need to create new relations (maybe even with new objects inside...
492
Node lastCreatedRelation = null;
493
494         for (int i=0; i < relations.getLength(); i++) {
495             Node relation = relations.item(i);
496             // create the relation now we can get all needed params
497
String JavaDoc role = Utils.getAttribute(relation, "role", "related");
498             String JavaDoc snumber = Utils.getAttribute(objectNode, "number");
499             String JavaDoc stype = Utils.getAttribute(objectNode, "type");
500             // determine destination
501
// dnumber can be null
502
String JavaDoc dnumber = Utils.getAttribute(relation, "destination", null);
503             dnumber=Utils.transformAttribute(data.getDocumentElement(), dnumber, false, params);
504             String JavaDoc dtype = "";
505
506             String JavaDoc createDir = Utils.getAttribute(relation, Dove.ELM_CREATEDIR, "either");
507             Node inside_object = null;
508             Node inside_objectdef = Utils.selectSingleNode(relation, "object");
509             if (dnumber != null) {
510                 // dnumber is given (direct reference to an existing mmbase node)
511
// obtain the object.
512
// we can do this here as it is a single retrieval
513
try {
514                     inside_object = getDataNode(targetParentNode.getOwnerDocument(), dnumber, null);
515                 } catch (Exception JavaDoc e) {
516                     throw new WizardException("Could not load object (" + dnumber + "). Message: " + Logging.stackTrace(e));
517                 }
518                 // but annotate that this one is loaded from mmbase. Not a new one
519
Utils.setAttribute(inside_object, "already-exists", "true");
520                 // grab the type
521
dtype = Utils.getAttribute(inside_object, "type", "");
522             } else {
523                 // type should be determined from the destinationtype
524
dtype=Utils.getAttribute(relation, "destinationtype", "");
525                 // OR the objectdefiniton
526
if (dtype.equals("")) {
527                     if (inside_objectdef!=null) {
528                         dtype = Utils.getAttribute(inside_objectdef, "type");
529                     }
530                 }
531             }
532
533             Node relationNode = getNewRelation(objectNode, role, snumber, stype, dnumber, dtype,createDir);
534             if (context!=null && !context.equals("")) {
535                 Utils.setAttribute(relationNode, "context", context);
536             }
537             fillObjectFields(data,targetParentNode,relation,relationNode,params,createorder);
538
539             tagDataNode(relationNode);
540             lastCreatedRelation = relationNode;
541
542             if (inside_object==null) {
543                 // no dnumber given! create the object
544
if (inside_objectdef==null) {
545                     // no destination is given AND no object to-be-created-new is placed.
546
// so, no destination should be added...
547
inside_object=data.createElement("object");
548                     ((Element)inside_object).setAttribute("number","");
549                     ((Element)inside_object).setAttribute("type",Utils.getAttribute(relation, "destinationtype", ""));
550                     ((Element)inside_object).setAttribute("disposable","true");
551                 } else {
552                     inside_object = createObject(data,relationNode, inside_objectdef, params);
553                     dnumber = Utils.getAttribute(inside_object, "number");
554                     ((Element)relationNode).setAttribute("destination",dnumber);
555                 }
556             }
557             relationNode.appendChild(inside_object);
558         }
559         if (nodeName.equals("relation")) {
560             return lastCreatedRelation;
561         } else {
562             return objectNode;
563         }
564     }
565
566     /**
567      * Sends an Element containing the xml-representation of a command to a Dove
568      * class, and retuirns the result as an Element.
569      * @param cmd the command Element to execute
570      * @param binaries a HashMap containing files (binaries) uploaded in the wizard
571      */

572     private Element sendCommand(Element cmd, Map binaries) throws WizardException {
573         Dove dove = new Dove(Utils.emptyDocument());
574         Element results = dove.executeRequest(cmd, userCloud, binaries);
575         NodeList errors = Utils.selectNodeList(results, ".//error");
576         if (errors.getLength() > 0){
577             StringBuffer JavaDoc errorMessage = new StringBuffer JavaDoc("Errors received from MMBase Dove servlet: ");
578             for (int i = 0; i < errors.getLength(); i++){
579                 errorMessage.append(Utils.getText(errors.item(i))).append("\n");
580             }
581             throw new WizardException(errorMessage.toString());
582         }
583         return results;
584     }
585
586     /**
587      * This is an internal method which is used to fire a command to connect to mmbase via Dove.
588      * @throws WizardException if the command failed
589      */

590     private Document fireCommand(ConnectorCommand command) throws WizardException {
591         List tmp = new Vector();
592         tmp.add(command);
593         return fireCommandList(tmp);
594     }
595
596     /**
597      * This is an internal method which is used to fire commands to connect to mmbase via Dove.
598      * @throws WizardException if one or more commands failed
599      */

600     private Document fireCommandList(List commands) throws WizardException {
601         // send commands away from here... away!
602
// first create request xml
603
Document req = Utils.parseXML("<request/>");
604         Element docel = req.getDocumentElement();
605
606         Iterator i = commands.iterator();
607         while (i.hasNext()) {
608             ConnectorCommand cmd = (ConnectorCommand) i.next();
609             docel.appendChild(req.importNode(cmd.getCommandXML().getDocumentElement().cloneNode(true), true));
610         }
611
612         String JavaDoc res="";
613
614         Element results=sendCommand(docel,null);
615
616         Document response = Utils.emptyDocument();
617         response.appendChild(response.importNode(results,true));
618
619         // map response back to each command.
620
i = commands.iterator();
621         while (i.hasNext()) {
622             ConnectorCommand cmd = (ConnectorCommand) i.next();
623
624             // find response for this command
625
Node resp = Utils.selectSingleNode(response, "/*/"+cmd.getName() +"[@id]");
626             if (resp!=null) {
627                 // yes we found a response
628
cmd.setResponse(resp);
629             } else {
630                 log.error("Could NOT store response "+cmd.getId()+" in a ConnectorCommand");
631                 log.error(cmd.getResponseXML());
632             }
633         }
634         return response;
635     }
636
637     /**
638      * This method can fire a Put command to Dove. It uses #getPutData to construct the transaction.
639      *
640      * @param originalData The original data object tree.
641      * @param newData The new and manipulated data. According to differences between the original and the new data, the transaction is constructed.
642      * @param binaries A hashmap with the uploaded binaries.
643      * @return The element containing the results of the put transaction.
644      */

645     public Element put(Document originalData, Document newData, Map binaries) throws WizardException {
646         Node putcmd =getPutData(originalData, newData);
647         return sendCommand(putcmd.getOwnerDocument().getDocumentElement(), binaries);
648     }
649
650     /**
651      * The editwizard uses integer values to represent dates.
652      * This method convert the integer values of date fields (seconds per 1/1/1970) to the
653      * MMBase string representation of a date value before sending the data to MMBase.
654      * @since MMBase 1.8
655      * @param rootNode the node whose field sub nodes should be converted
656      */

657     protected void convertIntToDateTime(Node rootNode) {
658         // convert all datetime values
659
NodeList nodes = Utils.selectNodeList(rootNode, ".//field[@type='datetime']");
660         for (int i = 0; i < nodes.getLength(); i++) {
661             Node node = nodes.item(i);
662             String JavaDoc value = Utils.getText(node);
663             if (!"".equals(value)) {
664                 value = Casting.toString(Casting.toDate(value));
665                 Utils.storeText(node, value);
666             }
667         }
668     }
669
670     /**
671      * Booleans in MMBase are represented by a boolean value. The editwizard uses integer values to represent true/false.
672      * This method convert the integer values of boolean fields to as boolean string value (true or false)
673      * before sending the data to MMBase.
674      * @since MMBase 1.8
675      * @param rootNode the node whose field sub nodes should be converted
676      */

677     protected void convertIntToBoolean(Node rootNode) {
678         // convert all datetime values
679
NodeList nodes = Utils.selectNodeList(rootNode, ".//field[@type='boolean']");
680         for (int i = 0; i < nodes.getLength(); i++) {
681             Node node = nodes.item(i);
682             String JavaDoc value = Utils.getText(node);
683             if (!"".equals(value)) {
684                 int boolAsInt = Casting.toInt(value);
685                 value = Casting.toString(Boolean.valueOf(boolAsInt > 0));
686                 Utils.storeText(node, value);
687             }
688         }
689     }
690
691     /**
692      * The editwizard uses integer values to represent dates.
693      * This method converts the MMBase string representation of a date field's value to this
694      * integer value (seconds per 1/1/1970) after reading the data from MMBase.
695      * @since MMBase 1.8
696      * @param rootNode the node whose field sub nodes should be converted
697      */

698     protected void convertDateTimeToInt(Node rootNode) {
699         // convert all datetime values
700
NodeList nodes = Utils.selectNodeList(rootNode, ".//field[@type='datetime']");
701         for (int i = 0; i < nodes.getLength(); i++) {
702             Node node = nodes.item(i);
703             String JavaDoc value = Utils.getText(node);
704             if (!"".equals(value)) {
705                 long time = Casting.toDate(value).getTime();
706                 value = time == -1 ? "-1" : "" + time / 1000;
707                 Utils.storeText(node, value);
708             }
709         }
710     }
711
712     /**
713      * Booleans in MMBase are represented by a boolean value. The editwizard uses integer values to represent true/false.
714      * This method convert the boolean values of boolean fields to an integer value (0 or 1)
715      * after reading the data from MMBase.
716      * @since MMBase 1.8
717      * @param rootNode the node whose field sub nodes should be converted
718      */

719     protected void convertBooleanToInt(Node rootNode) {
720         // convert all datetime values
721
NodeList nodes = Utils.selectNodeList(rootNode, ".//field[@type='boolean']");
722         for (int i = 0; i < nodes.getLength(); i++) {
723             Node node = nodes.item(i);
724             String JavaDoc value = Utils.getText(node);
725             if (!"".equals(value)) {
726                 if (Casting.toBoolean(value)) {
727                     value = "1";
728                 } else {
729                     value = "0";
730                 }
731                 Utils.storeText(node, value);
732             }
733         }
734     }
735
736     /**
737      * This method constructs a update transaction ready for mmbase.
738      * The differences between the original and the new data define the transaction.
739      *
740      * @param originalData The original data.
741      * @param newData The new data.
742      */

743     public Node getPutData(Document originalData, Document newData) throws WizardException {
744         Document workDoc = Utils.emptyDocument();
745         workDoc.appendChild(workDoc.importNode(newData.getDocumentElement().cloneNode(true), true));
746
747         Node workRoot = workDoc.getDocumentElement();
748
749         // initialize request xml
750
Document req = Utils.parseXML("<request><put id=\"put\"><original/><new/></put></request>");
751
752         Node reqorig = Utils.selectSingleNode(req, "/request/put/original");
753         Node reqnew = Utils.selectSingleNode(req, "/request/put/new");
754
755         // Remove disposable objects (disposable=true)
756
// Remove all relations which have NO destination chosen (destination="-");
757
NodeList disposables = Utils.selectNodeList(workRoot, ".//*[@disposable or @destination='-']");
758         for (int i=0; i<disposables.getLength(); i++) {
759             Node disp = disposables.item(i);
760             disp.getParentNode().removeChild(disp);
761         }
762
763         // serialize original data. Place objects first, relations second
764
makeFlat(originalData, reqorig, ".//object", "field");
765         makeFlat(originalData, reqorig, ".//relation", "field");
766
767         // serialize new data. Place objects first, relations second
768
makeFlat(workRoot, reqnew, ".//object", "field");
769         makeFlat(workRoot, reqnew, ".//relation", "field");
770
771         // if value is a datetime or boolean field, it should converted from integer
772
// to a date or time string value
773
/* not needed for dates as currently dates are still passed as integers
774         convertIntToDateTime(reqorig);
775         convertIntToDateTime(reqnew);
776 */

777         convertIntToBoolean(reqorig);
778         convertIntToBoolean(reqnew);
779
780         // find all changed or new relations and objects
781
NodeList nodes = Utils.selectNodeList(reqnew, ".//relation|.//object[not(@disposable)]");
782         for (int i=0; i<nodes.getLength(); i++) {
783             Node node = nodes.item(i);
784             String JavaDoc nodename = node.getNodeName();
785
786             String JavaDoc did = Utils.getAttribute(node, "did", "");
787             Node orignode = Utils.selectSingleNode(reqorig, ".//*[@did='"+did+"' and not(@already-exists)]");
788
789 // String nodenumber = Utils.getAttribute(node, "number", "");
790
// Node orignode = Utils.selectSingleNode(reqorig, ".//*[@number='"+nodenumber+"' and not(@already-exists)]");
791
if (orignode!=null) {
792                 // we found the original relation. Check to see if destination has changed.
793
if (nodename.equals("relation")) {
794                     String JavaDoc destination = Utils.getAttribute(node,"destination", "");
795                     String JavaDoc olddestination = Utils.getAttribute(orignode,"destination", "");
796                     if (!destination.equals(olddestination) && !destination.equals("")) {
797                         // ok. it's different
798
Utils.setAttribute(node, "status", "changed");
799                         // store original destination also. easier to process later on
800
Utils.setAttribute(node, "olddestination", olddestination);
801                     } else {
802                         // it's the same (or at least: the destination is the same)
803
// now check if some inside-fields are changed.
804
boolean valueschanged = checkRelationFieldsChanged(orignode, node);
805
806                         if (valueschanged) {
807                             // values in the fields are changed, destination/source are still te same.
808
// let's store that knowledge.
809
Utils.setAttribute(node,"status", "fieldschangedonly");
810                         } else {
811                             // really nothing changed.
812
// remove relation from both orig as new
813
node.getParentNode().removeChild(node);
814                                 orignode.getParentNode().removeChild(orignode);
815                         }
816                     }
817                 }
818                 if (nodename.equals("object")) {
819                     // object
820
// check if it is changed
821
boolean different = isDifferent(node, orignode);
822                     if (!different) {
823                         // remove both objects
824
node.getParentNode().removeChild(node);
825                         Utils.setAttribute(orignode, "repository", "true");
826                     } else {
827                         // check if fields are different?
828
NodeList fields=Utils.selectNodeList(node,"field");
829                         for (int j=0; j<fields.getLength(); j++) {
830                             Node origfield = Utils.selectSingleNode(orignode, "field[@name='"+Utils.getAttribute(fields.item(j), "name")+"']");
831                             if (origfield!=null) {
832                                 if (!isDifferent(fields.item(j), origfield)) {
833                                     // the same. let's remove this field also
834
fields.item(j).getParentNode().removeChild(fields.item(j));
835                                     origfield.getParentNode().removeChild(origfield);
836                                 }
837                             }
838                         }
839                         Utils.setAttribute(orignode, "repository", "false");
840                     }
841                 }
842             } else {
843                 // this is a new relation or object. Remember that
844
// but, check first if the may-be-new object has a "already-exists" attribute. If so,
845
// we don't have a new object, no no, this is a later-loaded object which is not added to the
846
// original datanode (should be better in later versions, eg. by using a repository).
847
String JavaDoc already_exists = Utils.getAttribute(node, "already-exists", "false");
848                 if (!already_exists.equals("true")) {
849                     // go ahead. this seems to be a really new one...
850
Utils.setAttribute(node, "status", "new");
851
852                         // check if fields values have been set
853
// insert values are not sent - this allows use of virtual fields to edit
854
// other fields
855
NodeList fields=Utils.selectNodeList(node,"field");
856                         for (int j=0; j<fields.getLength(); j++) {
857                             // if a new field is empty, don't enter it, but use the default value
858
// as set in the builder's setDefault() method
859
// note that strictly speaking, this may not be correct
860
// a better way is perhaps to first retrieve a new node and compare the values
861
if ("".equals(Utils.getText(fields.item(j)))) {
862                                 fields.item(j).getParentNode().removeChild(fields.item(j));
863                             }
864                         }
865
866                 } else {
867                     // remove it from the list.
868
node.getParentNode().removeChild(node);
869                 }
870             }
871         }
872
873         // remove all repository nodes
874
NodeList repnodes = Utils.selectNodeList(reqorig, ".//relation[@repository='true']|.//object[@repository='true']");
875         for (int i=0; i<repnodes.getLength(); i++) {
876             Node repnode = repnodes.item(i);
877             repnode.getParentNode().removeChild(repnode);
878         }
879
880         // find all deleted relations and objects
881
NodeList orignodes = Utils.selectNodeList(reqorig, ".//relation|.//object");
882         for (int i=0; i<orignodes.getLength(); i++) {
883             Node orignode = orignodes.item(i);
884             String JavaDoc nodenumber = Utils.getAttribute(orignode, "number", "");
885             Node node = Utils.selectSingleNode(reqnew, ".//*[@number='"+nodenumber+"']");
886             if (node==null) {
887                 // item is apparently deleted.
888
// place relation node anyway but say that it should be deleted (and make it so more explicit)
889
Node newnode = req.createElement(orignode.getNodeName());
890                 Utils.copyAllAttributes(orignode, newnode);
891                 Utils.setAttribute(newnode, "status", "delete");
892                 reqnew.appendChild(newnode);
893             }
894         }
895
896         // now, do our final calculations:
897
//
898
//
899
// 2. change "changed" relations into a delete + a create command.
900
// and, make sure the create command is in the right format.
901
NodeList rels = Utils.selectNodeList(req, "//new/relation[@status='changed']");
902         for (int i=0; i<rels.getLength(); i++) {
903             Node rel = rels.item(i);
904             Node newrel = rel.cloneNode(true);
905
906             // say that old relation should be deleted
907
Utils.setAttribute(rel, "destination", Utils.getAttribute(rel, "olddestination", ""));
908             rel.getAttributes().removeNamedItem("olddestination");
909             Utils.setAttribute(rel, "status", "delete");
910
911             // say that a new relation should be formed
912
newrel.getAttributes().removeNamedItem("number");
913             newrel.getAttributes().removeNamedItem("olddestination");
914             Utils.setAttribute(newrel, "status", "new");
915             String JavaDoc role = Utils.getAttribute(newrel, "role", "related");
916             Utils.setAttribute(newrel, "role", role);
917
918             // copy inside fields also (except dnumber, snumber and rnumber fields)
919
NodeList flds = Utils.selectNodeList(rel, "field");
920             Utils.appendNodeList(flds,rel);
921
922             // store the new rel in the list also
923
rel.getParentNode().appendChild(newrel);
924         }
925
926         //
927
// 3. search for 'fieldschangedonly' fields of the relations. If so, we should make a special command for the Dove
928
// servlet.
929
//
930
rels = Utils.selectNodeList(req, "//new/relation[@status='fieldschangedonly']");
931         for (int i=0; i<rels.getLength(); i++) {
932             Node rel = rels.item(i);
933             String JavaDoc number = Utils.getAttribute(rel,"number","");
934             Node origrel = Utils.selectSingleNode(req, "//original/relation[@number='"+number+"']");
935             if (!number.equals("") && origrel!=null) {
936                 // we found the original relation also. Now, we can process these nodes.
937
convertRelationIntoObject(origrel);
938                 convertRelationIntoObject(rel);
939             }
940         }
941         return req.getDocumentElement();
942     }
943
944     /**
945      * This method makes the object data tree flat, so that Dove can construct a transaction from it.
946      *
947      * @param sourcenode The sourcenode from which should be flattened.
948      * @param targetParentNode The targetParentNode where the results should be appended.
949      * @param allowedChildrenXpath This xpath defines what children may be copied in te proces and should NOT be flattened.
950      */

951     public void makeFlat(Node sourcenode, Node targetParentNode, String JavaDoc xpath, String JavaDoc allowedChildrenXpath) {
952         NodeList list = Utils.selectNodeList(sourcenode, xpath);
953         for (int i=0; i<list.getLength(); i++) {
954             Node item = list.item(i);
955             Node newnode = targetParentNode.getOwnerDocument().importNode(item, false);
956             targetParentNode.appendChild(newnode);
957             cloneOneDeep(item, newnode, allowedChildrenXpath);
958         }
959     }
960
961     /**
962      * This method can clone an object node one deep. So, only the first level children will be copied.
963      *
964      * @param sourcenode The sourcenode from where the flattening should start.
965      * @param targetParentNode The parent on which the results should be appended.
966      * @param allowedChildrenXpath The xpath describing what children may be copied.
967      */

968     public void cloneOneDeep(Node sourcenode, Node targetParentNode, String JavaDoc allowedChildrenXpath) {
969         NodeList list = Utils.selectNodeList(sourcenode, allowedChildrenXpath);
970         Utils.appendNodeList(list, targetParentNode);
971     }
972
973     /**
974      * This method compares two xml nodes and checks them for identity.
975      *
976      * @param node1 The first node to check
977      * @param node2 Compare with thisone.
978      * @return True if they are different, False if they are similar.
979      */

980     public boolean isDifferent(Node node1, Node node2) {
981         // only checks textnodes and childnumbers
982
boolean res = false;
983         if (node1.getChildNodes().getLength() != node2.getChildNodes().getLength()) {
984             // ander aantal kindjes!
985
return true;
986         }
987         // andere getnodetype?
988
if (node1.getNodeType() != node2.getNodeType()) {
989             return true;
990         }
991         if ((node1.getNodeType() == Node.TEXT_NODE) || (node1.getNodeType() == Node.CDATA_SECTION_NODE)) {
992             String JavaDoc s1 = node1.getNodeValue();
993             String JavaDoc s2 = node2.getNodeValue();
994             if (!s1.equals(s2)) return true;
995         }
996         // check kids
997
NodeList kids = node1.getChildNodes();
998         NodeList kids2 = node2.getChildNodes();
999
1000        for (int i=0; i<kids.getLength(); i++) {
1001            if (isDifferent(kids.item(i), kids2.item(i))) {
1002                return true;
1003            }
1004        }
1005        return false;
1006    }
1007
1008    /**
1009     * This method checks if relationfields are changed. It does NOT check the source-destination numbers, only the other fields,
1010     * like the pos field in a posrel relation.
1011     *
1012     * @param origrel The original relation
1013     * @param rel The new relation
1014     * @return True if the relations are different, false if they are the same.
1015     */

1016    private boolean checkRelationFieldsChanged(Node origrel, Node rel) throws WizardException{
1017        NodeList origflds = Utils.selectNodeList(origrel, "field");
1018        NodeList newflds = Utils.selectNodeList(rel, "field");
1019        Document tmp = Utils.parseXML("<tmp><n1><r/></n1><n2><r/></n2></tmp>");
1020
1021        Node n1 = Utils.selectSingleNode(tmp, "/tmp/n1/r");
1022        Node n2 = Utils.selectSingleNode(tmp, "/tmp/n2/r");
1023
1024        Utils.appendNodeList(origflds,n1);
1025        Utils.appendNodeList(newflds,n2);
1026
1027        return isDifferent(n1,n2);
1028    }
1029
1030    /**
1031     * this method converts a <relation /> node into an <object /> node.
1032     * source/destination attributes are removed, and rnumber, snumber, dnumber fields also.
1033     * It is mainly used by the putData commands to store extra relation fields values.
1034     * @param rel The relation which should be converted.
1035     */

1036    private void convertRelationIntoObject(Node rel) {
1037
1038        Node obj = rel.getOwnerDocument().createElement("object");
1039
1040        // copy attributes, except...
1041
List except = new Vector();
1042        except.add("destination");
1043        except.add("source");
1044        except.add("role");
1045        except.add("status"); // we don't need status anymore also!
1046
Utils.copyAllAttributes(rel, obj, except);
1047
1048        // copy fields, except... (uses RELATIONFIELDS_XPATH)
1049
NodeList flds = Utils.selectNodeList(rel, "field");
1050
1051        Utils.appendNodeList(flds, obj);
1052
1053        // remove rel, place obj instead
1054
rel.getParentNode().replaceChild(obj, rel);
1055    }
1056}
1057
Popular Tags