KickJava   Java API By Example, From Geeks To Geeks.

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


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 org.mmbase.bridge.util.Queries;
14 import org.mmbase.storage.search.RelationStep; // just for the search-constants.
15

16 import org.mmbase.cache.Cache;
17
18 import org.mmbase.applications.dove.*;
19
20 import org.mmbase.util.ResourceWatcher;
21 import org.mmbase.util.ResourceLoader;
22 import org.mmbase.util.logging.*;
23 import org.mmbase.util.xml.URIResolver;
24 import org.mmbase.util.XMLEntityResolver;
25
26 import org.w3c.dom.*;
27
28 import java.net.URL JavaDoc;
29 import java.io.Writer JavaDoc;
30
31 import java.util.*;
32
33 import javax.servlet.ServletRequest JavaDoc;
34
35 import javax.xml.transform.TransformerException JavaDoc;
36
37
38 /**
39  * EditWizard
40  * @javadoc
41  * @author Kars Veling
42  * @author Michiel Meeuwissen
43  * @author Pierre van Rooden
44  * @author Hillebrand Gelderblom
45  * @since MMBase-1.6
46  * @version $Id: Wizard.java,v 1.149 2006/08/14 07:54:35 pierre Exp $
47  *
48  */

49 public class Wizard implements org.mmbase.util.SizeMeasurable {
50     private static final Logger log = Logging.getLoggerInstance(Wizard.class);
51     public static final String JavaDoc PUBLIC_ID_EDITWIZARD_1_0 = "-//MMBase//DTD editwizard 1.0//EN";
52     public static final String JavaDoc PUBLIC_ID_EDITWIZARD_1_0_FAULT = "-//MMBase/DTD editwizard 1.0//EN";
53     public static final String JavaDoc DTD_EDITWIZARD_1_0 = "wizard-schema_1_0.dtd";
54
55
56     static {
57         XMLEntityResolver.registerPublicID(PUBLIC_ID_EDITWIZARD_1_0, DTD_EDITWIZARD_1_0, Wizard.class);
58         XMLEntityResolver.registerPublicID(PUBLIC_ID_EDITWIZARD_1_0_FAULT, DTD_EDITWIZARD_1_0, Wizard.class);
59     }
60
61     // File -> Document (resolved includes/shortcuts)
62
private static WizardSchemaCache wizardSchemaCache;
63     private static NodeCache nodeCache;
64
65     static {
66         wizardSchemaCache = new WizardSchemaCache();
67         wizardSchemaCache.putCache();
68         nodeCache = new NodeCache();
69         nodeCache.putCache();
70     }
71
72     /**
73      * The cloud used to connect to MMBase
74      */

75     private Cloud cloud;
76
77     // This object will be used the revolve URI's, for example those of XSL's and XML's.
78
private URIResolver uriResolver = null;
79
80     // the editwizard context path
81
private String JavaDoc context = null;
82
83     // schema / session data
84
private String JavaDoc name;
85
86     // the result objectnumber (the number of the object after a commit)
87
// this value is only assigned after COMMIT is called - otherwise it is null
88
private String JavaDoc objectNumber;
89
90     // the wizard (file) name. Eg.: samples/jumpers will choose the file $path/samples/jumpers.xml
91
private String JavaDoc wizardName;
92
93     // the (possibly temporary) number of the main object
94
private String JavaDoc dataId;
95
96     // stores the current formid
97
private String JavaDoc currentFormId;
98
99     // filename of the stylesheet which should be used to make the html form.
100
private URL JavaDoc wizardStylesheetFile;
101     private String JavaDoc sessionId;
102     private String JavaDoc sessionKey = "editwizard";
103     private String JavaDoc referrer = "";
104     private String JavaDoc templatesDir = null;
105     private String JavaDoc timezone;
106
107     /**
108      * public xmldom's: the schema, the data and the originaldata is stored.
109      *
110      * @scope private
111      */

112     private Document schema;
113     private Document data;
114     private Document originalData;
115
116     // not yet committed uploads are stored in these hashmaps
117
private Map binaries = new HashMap();
118     private Map binaryNames = new HashMap();
119     private Map binaryPaths = new HashMap();
120
121     // in the wizards, variables can be used. Values of the variables are stored here.
122
private Map variables = new HashMap();
123
124     // the constraints received from mmbase are stored + cached in this xmldom
125
private Document constraints;
126
127     // Seconds.
128
private long listQueryTimeOut = 60 * 60;
129
130     // the database connector handles communition with mmbase. the instance is stored here.
131
private WizardDatabaseConnector databaseConnector;
132
133     /**
134      * This boolean tells the jsp that the wizard may be closed, as far as he is concerned.
135      */

136     private boolean mayBeClosed = false;
137
138     /**
139      * This boolean tells the jsp that a new (sub) wizard should be started
140      */

141     private boolean startWizard = false;
142
143     /**
144      * The command to use dor starting a new (sub) wizard
145      * Only set when startwizard is true
146      */

147     private WizardCommand startWizardCmd = null;
148
149     /**
150      * This boolean tells the jsp that the wizard was committed, and changes may have been made
151      */

152     private boolean committed = false;
153
154
155     /**
156      *
157      */

158     private String JavaDoc popupId = "";
159     private boolean debug = false;
160
161     /**
162      * Constructor. Setup initial variables and connects to mmbase to load the data structure.
163      *
164      * @deprecated use Wizard(String, URIResolver, Config.WizardConfig, Cloud)
165      * @param context the editwizard context path
166      * @param uri the URIResolver with which the wizard schema's and the xsl's will be loaded
167      * @param wizardname name of teh wizard
168      * @param dataid the objectnumber
169      * @param cloud the Cloud to use
170      */

171     public Wizard(String JavaDoc context, URIResolver uri, String JavaDoc wizardname, String JavaDoc dataid, Cloud cloud) throws WizardException {
172         Config.WizardConfig wizardConfig = new Config.WizardConfig();
173         wizardConfig.objectNumber = dataid;
174         wizardConfig.wizard = wizardname;
175         initialize(context, uri, wizardConfig, cloud);
176     }
177
178     /**
179      * Constructor. Setup initial variables and connects to mmbase to load the data structure.
180      *
181      * @param context the editwizard context path
182      * @param uri the URIResolver with which the wizard schema's and the xsl's will be loaded
183      * @param wizardConfig the class containing the configuration parameters (i.e. wizard name and objectnumber)
184      * @param cloud the Cloud to use
185      */

186     public Wizard(String JavaDoc context, URIResolver uri,
187                   Config.WizardConfig wizardConfig, Cloud cloud) throws WizardException {
188         initialize(context, uri, wizardConfig, cloud);
189     }
190
191     public int getByteSize() {
192         return getByteSize(new org.mmbase.util.SizeOf());
193     }
194
195     public int getByteSize(org.mmbase.util.SizeOf sizeof) {
196         return sizeof.sizeof(cloud) + sizeof.sizeof(uriResolver) +
197             sizeof.sizeof(schema) + sizeof.sizeof(data) +
198             sizeof.sizeof(originalData) + sizeof.sizeof(binaries) +
199             sizeof.sizeof(binaryNames) + sizeof.sizeof(binaryPaths) +
200             sizeof.sizeof(constraints);
201     }
202
203     private void initialize(String JavaDoc c, URIResolver uri, Config.WizardConfig wizardConfig, Cloud cloud) throws WizardException {
204         context = c;
205         uriResolver = uri;
206         constraints = Utils.parseXML("<constraints/>");
207
208         // initialize database connector
209
databaseConnector = new WizardDatabaseConnector();
210         databaseConnector.setUserInfo(cloud);
211
212         // set cloud
213
this.cloud = cloud;
214
215         // add username to variables
216
variables.put("username", cloud.getUser().getIdentifier());
217
218         // actually load the wizard
219
loadWizard(wizardConfig);
220     }
221
222     public void setSessionId(String JavaDoc s) {
223         sessionId = s;
224     }
225
226     public void setSessionKey(String JavaDoc s) {
227         sessionKey = s;
228     }
229
230     public void setReferrer(String JavaDoc s) {
231         referrer = s;
232     }
233
234     public void setTemplatesDir(String JavaDoc f) {
235         templatesDir = f;
236     }
237
238     public void setTimezone(String JavaDoc s) {
239         timezone = s;
240     }
241
242     public String JavaDoc getObjectNumber() {
243         return objectNumber;
244     }
245
246     public String JavaDoc getDataId() {
247         return dataId;
248     }
249
250     public Document getData() {
251         return data;
252     }
253
254     public Document getSchema() {
255         return schema;
256     }
257
258     public Document getPreForm() throws WizardException {
259         return getPreForm(wizardName);
260     }
261
262     public Document getPreForm(String JavaDoc instanceName) throws WizardException {
263         Node datastart = Utils.selectSingleNode(data, "/data/*");
264
265         return createPreHtml(schema.getDocumentElement(), currentFormId,
266                              datastart, instanceName);
267     }
268
269
270     /**
271      * Returns whether the wizard may be closed
272      */

273     public boolean committed() {
274         return committed;
275     }
276
277     /**
278      * Returns whether the wizard may be closed
279      */

280     public boolean mayBeClosed() {
281         return mayBeClosed;
282     }
283
284     /**
285      * Returns whether a sub wizard should be started
286      */

287     public boolean startWizard() {
288         return startWizard;
289     }
290
291     /**
292      * Returns true if the specified operation is valid for the node with the specified objectnumber.
293      * The operation is valid if the node has the given property set to true.
294      * To maintain backwards compatible, if the property is not given, the default value is true.
295      * @param objectNumber teh number of teh ndoe to check
296      * @param operation a valid operation, i.e. maywrite or maydelete
297      * @throws WizardException if the object cannot be retrieved
298      */

299     protected boolean checkNode(String JavaDoc objectNumber, String JavaDoc operation) throws WizardException {
300         Object JavaDoc nodeObj = nodeCache.get(objectNumber);
301
302         if (nodeObj == null) {
303             NodeList nodes = Utils.selectNodeList(data, ".//*[@number='" + objectNumber + "']");
304
305             if ((nodes != null) && (nodes.getLength() > 0)) {
306                 nodeObj = nodes.item(0);
307             } else {
308                 if (objectNumber == null || objectNumber.equals("")) {
309                     log.warn("Checking security for objectNumber '" + objectNumber + "' "); // + Logging.stackTrace(5));
310
// MM: This happened to me when using sub-wizards (mmexamples)
311
// And this code made it work at least, but still wondering why it came here in the first place.
312
return true;
313                 }
314                 // node is from outside the datacloud...
315
// get it through dove... should we add it, and if so where?
316
nodeObj = databaseConnector.getDataNode(null, objectNumber, null);
317             }
318
319             nodeCache.put(objectNumber, nodeObj);
320             log.debug("Node loaded: " + nodeObj);
321         } else {
322             log.debug("Node found in cache: " + nodeObj);
323         }
324
325         Node node = (Node) nodeObj;
326
327         return (node != null) &&
328             Utils.getAttribute(node, operation, "true").equals("true");
329     }
330
331     /**
332      * Returns true if the node with the specified objectnumber can be edited
333      * @param objectNumber number of the object to check
334      * @throws WizardException if the object cannot be retrieved
335      */

336     protected boolean mayEditNode(String JavaDoc objectNumber) throws WizardException {
337         return checkNode(objectNumber, "maywrite");
338     }
339
340     /**
341      * Returns true if the node with the specified objectnumber can be deleted
342      * @param objectNumber number of the object to check
343      * @throws WizardException if the object cannot be retrieved
344      */

345     protected boolean mayDeleteNode(String JavaDoc objectNumber)
346         throws WizardException {
347         return checkNode(objectNumber, "maydelete");
348     }
349
350     /**
351      * Returns the subwizard start command
352      */

353     public WizardCommand getStartWizardCommand() {
354         return startWizardCmd;
355     }
356
357     /**
358      * Stores configuration variables as attributes in the variabless set.
359      * @param wizardConfig the config with the parameters
360      */

361     protected void storeConfigurationAttributes(Config.WizardConfig wizardConfig) {
362         variables.put("wizardname", wizardName);
363
364         // set attributes from config
365
// this sets: origin, context, debug, objectnumber, and wizard
366
variables.putAll(wizardConfig.getAttributes());
367     }
368
369     /**
370      * Loads the wizard schema, and a work document, and fills it with initial data.
371      *
372      * @param wizardConfig the class containing the configuration parameters (i.e. wizard name and objectnumber)
373      */

374     protected void loadWizard(Config.WizardConfig wizardConfig) throws WizardException {
375         if (wizardConfig.wizard == null) {
376             throw new WizardException("Wizardname may not be null");
377         }
378
379         wizardName = wizardConfig.wizard;
380         popupId = wizardConfig.popupId;
381         dataId = wizardConfig.objectNumber;
382         debug = wizardConfig.debug;
383
384         URL JavaDoc wizardSchemaFile;
385         try {
386             wizardSchemaFile = uriResolver.resolveToURL(wizardName + ".xml", null);
387         } catch (Exception JavaDoc e) {
388             throw new WizardException(e);
389         }
390         if (wizardSchemaFile == null) {
391             throw new WizardException("Could not resolve wizard " + wizardName + ".xml with " + uriResolver);
392         }
393         try {
394             wizardStylesheetFile = uriResolver.resolveToURL(Config.wizardStyleSheet, null);
395         } catch (Exception JavaDoc e) {
396             throw new WizardException(e);
397         }
398
399         if (wizardStylesheetFile == null) {
400             throw new WizardException("Could not resolve XSL '" + Config.wizardStyleSheet + "' with " + uriResolver);
401         }
402         // store variables so that these can be used in the wizard schema
403
storeConfigurationAttributes(wizardConfig);
404
405         // load wizard schema
406
loadSchema(wizardSchemaFile); // expanded filename of the wizard
407

408
409         // If the given dataid=new, we have to create a new object first, given
410
// by the object definition in the schema.
411
// If dataid equals null, we don't need to do anything. Wizard will not be used to show or save data;
412
// just to load schema information.
413
if (dataId != null) {
414             // setup original data
415
originalData = Utils.emptyDocument();
416
417             if (dataId.equals("new")) {
418                 log.debug("Creating new xml");
419
420                 // Get the definition and create a copy of the object-definition.
421
Node objectdef = Utils.selectSingleNode(schema, "./wizard-schema/action[@type='create']");
422
423                 if (objectdef == null) {
424                     throw new WizardException("You tried to start a create action in the wizard, but no create action was defined in the wizard schema. Please supply a <action type='create' /> section in the wizard.");
425                 }
426
427                 objectdef = objectdef.cloneNode(true);
428                 log.debug("Going to creating a new object " + objectdef.getNodeName() + " type " + Utils.getAttribute(objectdef, "type"));
429
430                 // We have to add the object to the data, so first determine to which parent it belongs.
431
data = Utils.parseXML("<data />");
432
433                 Node parent = data.getDocumentElement();
434
435                 // Ask the database to create that object, ultimately to get the new id.
436
Node newobject = databaseConnector.createObject(data, parent, objectdef, variables);
437
438                 if (newobject == null) {
439                     throw new WizardException("Could not create new object. Did you forget to add an 'object' subtag?");
440                 }
441
442                 parent.appendChild(newobject);
443                 databaseConnector.tagDataNodes(data);
444                 dataId = Utils.getAttribute(newobject, "number");
445
446                 if (log.isDebugEnabled()) {
447                     log.debug("Created object " + newobject.getNodeName() + " type " + Utils.getAttribute(newobject, "type") + ", id " + dataId);
448                 }
449             } else {
450                 loadData();
451             }
452         }
453
454         // initialize an editor session
455
if (currentFormId == null) {
456             currentFormId = determineNextForm("first");
457         }
458     }
459
460     /**
461      * @since MMBase-1.7
462      */

463     protected void loadData() throws WizardException {
464         // - load data.
465
// - tags the datanodes
466
data = databaseConnector.load(schema.getDocumentElement(), dataId);
467
468         if (data == null) {
469             throw new WizardException("The requested object could not be loaded from MMBase. ObjectNumber:" + dataId +
470                                       ". Does the object exists and do you have enough rights to load this object.");
471         }
472         // setup original data
473
originalData = Utils.emptyDocument();
474
475
476         // store original data, so that the put routines will know what to save/change/add/delete
477
originalData.appendChild(originalData.importNode(data.getDocumentElement().cloneNode(true), true));
478     }
479
480     /**
481      * Processes an incoming request (usually passed on by a jsp code).
482      * First, all given values are stored in the current datatree,
483      * Second, all given commands are processed sequentially.
484      *
485      * @param req the ServletRequest contains the name-value pairs received through the http connection
486      */

487     public void processRequest(ServletRequest JavaDoc req) throws WizardException {
488         String JavaDoc curform = req.getParameter("curform");
489
490         if ((curform != null) && !curform.equals("")) {
491             currentFormId = curform;
492         }
493
494         storeValues(req);
495         processCommands(req);
496     }
497
498     /**
499      * Constructs and writes final form-html to the given out writer.
500      * You can specify an instancename, so that the wizard is able to start another wizard in the
501      * <em>same</em> session. The jsp pages and in the html the instancenames are used to keep track
502      * of one and another.
503      *
504      * @param out The writer where the output (html) should be written to.
505      * @param instanceName name of the current instance
506      */

507     public void writeHtmlForm(Writer JavaDoc out, String JavaDoc instanceName) throws WizardException, TransformerException JavaDoc {
508         writeHtmlForm(out, instanceName, null);
509     }
510
511     /**
512      * Constructs and writes final form-html to the given out writer.
513      * You can specify an instancename, so that the wizard is able to start another wizard in the
514      * <em>same</em> session. The jsp pages and in the html the instancenames are used to keep track
515      * of one and another.
516      *
517      * @param out The writer where the output (html) should be written to.
518      * @param instanceName name of the current instance
519      * @param externParams sending parameters to the stylesheet which are not
520      * from the editwizards itself
521      */

522     public void writeHtmlForm(Writer JavaDoc out, String JavaDoc instanceName, Map externParams)
523         throws WizardException, TransformerException JavaDoc {
524         if (log.isDebugEnabled()) {
525             log.debug("writeHtmlForm for " + instanceName);
526         }
527
528         Node datastart = Utils.selectSingleNode(data, "/data/*");
529
530         // Build the preHtml version of the form.
531
Document preForm = getPreForm(instanceName);
532         Validator.validate(preForm, schema);
533
534         Map params = new HashMap(variables);
535         params.put("ew_context", context);
536
537         // params.put("ew_imgdb", org.mmbase.module.builders.AbstractImages.getImageServletPath(context));
538
params.put("sessionid", sessionId);
539         params.put("sessionkey", sessionKey);
540         params.put("referrer", referrer);
541         params.put("referrer_encoded", java.net.URLEncoder.encode(referrer));
542         params.put("language", cloud.getLocale().getLanguage());
543         params.put("timezone", timezone);
544         params.put("cloud", cloud);
545
546         if (templatesDir != null) {
547             params.put("templatedir", templatesDir);
548         }
549
550         if (externParams != null && !externParams.isEmpty()) {
551             params.putAll(externParams);
552         }
553
554         Utils.transformNode(preForm, wizardStylesheetFile, uriResolver, out, params);
555     }
556
557     /**
558      * Internal method which is used to store the passed values. this method is called by processRequest.
559      *
560      * @see #processRequest
561      */

562     private void storeValues(ServletRequest JavaDoc req) throws WizardException {
563         Enumeration list = req.getParameterNames();
564         log.debug("Synchronizing editor data, using the request");
565
566         String JavaDoc formEncoding = req.getCharacterEncoding();
567         boolean hasEncoding = formEncoding != null;
568         if (!hasEncoding) {
569             log.debug("Request did not mention encoding, supposing UTF-8, as JSP's are");
570             formEncoding = "UTF-8";
571         } else {
572             log.debug("found encoding in the request: " + formEncoding);
573         }
574
575         while (list.hasMoreElements()) {
576             String JavaDoc name = (String JavaDoc) list.nextElement();
577
578             if (name.startsWith("internal_")) {
579                 log.debug("Ignoring parameter " + name);
580             } else {
581                 log.debug("Processing parameter " + name);
582
583                 String JavaDoc[] ids = processFormName(name);
584                 if (log.isDebugEnabled()) {
585                     log.debug("found ids: " + ((ids == null) ? "null" : (" " + java.util.Arrays.asList(ids))));
586                 }
587                 if (ids != null) {
588                     String JavaDoc result;
589                     if (!hasEncoding) {
590                         try {
591                            result = new String JavaDoc(req.getParameter(name).getBytes("ISO-8859-1"), formEncoding);
592                            if (log.isDebugEnabled()) {
593                                log.debug("Found in post '" + req.getParameter(name) + "' -> '" + result + "'");
594                            }
595                         } catch (java.io.UnsupportedEncodingException JavaDoc e) {
596                             log.warn(e.toString());
597                             result = req.getParameter(name);
598                         }
599                     } else { // the request encoding was known, so, I think we can suppose that the Parameter value was interpreted correctly.
600
result = req.getParameter(name);
601                     }
602
603                     if (result.equals("date")) {
604                         result = buildDate(req, name);
605                     }
606                     if (result.equals("time")) {
607                         result = buildTime(req, name);
608                     }
609                     if (result.equals("datetime")) {
610                         result = buildDateTime(req, name);
611                     }
612                     if (result.equals("duration")) {
613                         result = buildDuration(req, name);
614                     }
615
616                     storeValue(ids[0], ids[1], result);
617                 }
618             }
619         }
620     }
621
622     /**
623      * @return Calendar with timezone parameter
624      */

625     private Calendar getCalendar() {
626         if (timezone != null) {
627             TimeZone tz = TimeZone.getTimeZone(timezone);
628             if (tz.getID().equals(timezone)) {
629                 return Calendar.getInstance(tz);
630             }
631             else {
632                 return Calendar.getInstance();
633             }
634         }
635         else {
636             return Calendar.getInstance();
637         }
638     }
639
640     private String JavaDoc buildDate(ServletRequest JavaDoc req, String JavaDoc name) {
641         try {
642             int day = Integer.parseInt(req.getParameter("internal_" + name + "_day"));
643             int month = Integer.parseInt(req.getParameter("internal_" + name + "_month"));
644             int year = Integer.parseInt(req.getParameter("internal_" + name + "_year"));
645
646             Calendar cal = getCalendar();
647             cal.set(year, month - 1, day, 0, 0, 0);
648             return "" + cal.getTimeInMillis() / 1000;
649         } catch (RuntimeException JavaDoc e) { //NumberFormat NullPointer
650
log.debug("Failed to parse date for " + name + " " + e.getMessage());
651             return "";
652         }
653     }
654
655     private String JavaDoc buildTime(ServletRequest JavaDoc req, String JavaDoc name) {
656         try {
657             int hours = Integer.parseInt(req.getParameter("internal_" + name + "_hours"));
658             int minutes = Integer.parseInt(req.getParameter("internal_" + name + "_minutes"));
659
660             Calendar cal = getCalendar();
661             cal.set(1970, 0, 1, hours, minutes, 0);
662             return "" + cal.getTimeInMillis() / 1000;
663         } catch (RuntimeException JavaDoc e) { //NumberFormat NullPointer
664
log.debug("Failed to parse time for " + name + " "
665                     + e.getMessage());
666             return "";
667         }
668     }
669
670     private String JavaDoc buildDateTime(ServletRequest JavaDoc req, String JavaDoc name) {
671         try {
672             int day = Integer.parseInt(req.getParameter("internal_" + name + "_day"));
673             int month = Integer.parseInt(req.getParameter("internal_" + name + "_month"));
674             int year = Integer.parseInt(req.getParameter("internal_" + name + "_year"));
675             int hours = Integer.parseInt(req.getParameter("internal_" + name + "_hours"));
676             int minutes = Integer.parseInt(req.getParameter("internal_" + name + "_minutes"));
677
678             Calendar cal = getCalendar();
679             cal.set(year, month - 1, day, hours, minutes, 0);
680             return "" + cal.getTimeInMillis() / 1000;
681         } catch (RuntimeException JavaDoc e) { //NumberFormat NullPointer
682
log.debug("Failed to parse datetime for " + name + " "
683                     + e.getMessage());
684             return "";
685         }
686     }
687
688     private String JavaDoc buildDuration(ServletRequest JavaDoc req, String JavaDoc name) {
689         try {
690             int hours = Integer.parseInt(req.getParameter("internal_" + name + "_hours"));
691             int minutes = Integer.parseInt(req.getParameter("internal_" + name + "_minutes"));
692             int seconds = Integer.parseInt(req.getParameter("internal_" + name + "_seconds"));
693
694             Calendar cal = getCalendar();
695             cal.set(1970, 0, 1, hours, minutes, seconds);
696             return "" + cal.getTimeInMillis() / 1000;
697         } catch (RuntimeException JavaDoc e) { //NumberFormat NullPointer
698
log.debug("Failed to parse duration for " + name + " " + e.getMessage());
699             return "";
700         }
701     }
702
703
704
705     /**
706      * This method is used to determine what form is the sequential next, previous, first etc.
707      * You can use the parameter to indicate what you want to know:
708      *
709      * @param direction indicates what you wanna know. Possibilities:
710      * <code>
711      * - first (default)
712      * - last
713      * - previous
714      * - next
715      * </code>
716      */

717     public String JavaDoc determineNextForm(String JavaDoc direction) {
718         String JavaDoc stepDirection = direction;
719
720         if (stepDirection == null) {
721             stepDirection = "first";
722         }
723
724         // Determine if there are steps defined.
725
// If so, use the step elements to determine previous and next forms.
726
// If not, use the form-schema elements to determine previous and next forms.
727
// Assume that steps are defined for the moment.
728
String JavaDoc nextformid = "FORM_NOT_FOUND";
729         Node laststep = Utils.selectSingleNode(schema,
730                                                "//steps/step[@form-schema='" + currentFormId + "']");
731         Node nextstep = null;
732
733         // If the last step doesn't exist, get the first step.
734
// If the last step exists, determine the next step.
735
if ((laststep == null) || stepDirection.equals("first")) {
736             nextstep = Utils.selectSingleNode(schema, "//steps/step");
737             nextformid = Utils.getAttribute(nextstep, "form-schema");
738         } else {
739             if (stepDirection.equals("previous")) {
740                 nextstep = Utils.selectSingleNode(laststep, "./preceding-sibling::step");
741             } else if (stepDirection.equals("last")) {
742                 nextstep = Utils.selectSingleNode(laststep, "../step[position()=last()]");
743             } else {
744                 nextstep = Utils.selectSingleNode(laststep, "./following-sibling::step");
745             }
746
747             if (nextstep == null) {
748                 nextformid = "WIZARD_OUT_OF_BOUNDS";
749             } else {
750                 nextformid = Utils.getAttribute(nextstep, "form-schema");
751             }
752         }
753
754         return nextformid;
755     }
756
757     /**
758      * This method generates the pre-html. See the full-spec method for more details.
759      *
760      * @see #createPreHtml
761      */

762     public Document createPreHtml(String JavaDoc instanceName) throws WizardException {
763         Node datastart = Utils.selectSingleNode(data, "/data/*");
764
765         return createPreHtml(schema.getDocumentElement(), "1", datastart,
766                              instanceName);
767     }
768
769     /**
770      * This method generated the pre-html.
771      *
772      * Pre-html is a temporarily datatree (xml of course) which is used to generate the final html from.
773      * Is uses the wizardschema, the current formid, the current datatree and the instancename to generate the pre-html.
774      *
775      * Mainly, the pre-html contains all data needed to make a nice htmlform. The XSL's use the pre-html to generate html (thats why pre-html)
776      *
777      * @param wizardSchema the main node of the schema to be used. Includes should already be resolved.
778      * @param formid The id of the current form
779      * @param data The main node of the data tree to be used
780      * @param instanceName The instancename of this wizard
781      */

782     public Document createPreHtml(Node wizardSchema, String JavaDoc formid, Node data,
783                                   String JavaDoc instanceName) throws WizardException {
784         if (log.isDebugEnabled()) {
785             log.debug("Create preHTML of " + instanceName);
786         }
787
788         // intialize preHTML wizard
789
Document preHtml = Utils.parseXML("<wizard instance=\"" + instanceName + "\" />");
790         Node wizardnode = preHtml.getDocumentElement();
791
792         // copy all global wizard nodes.
793
NodeList globals = Utils.selectNodeList(wizardSchema, "title|subtitle|description");
794         Utils.appendNodeList(globals, wizardnode);
795
796         // find the current step and, if appliccable, the next and previous ones.
797
Utils.createAndAppendNode(wizardnode, "curform", formid);
798
799         Node step = Utils.selectSingleNode(wizardSchema, "./steps/step[@form-schema='" + formid + "']");
800
801         if (step != null) {
802             // Yes. we have step information. Let's add info about that.
803
String JavaDoc otherformid = "";
804             Node prevstep = Utils.selectSingleNode(step, "./preceding-sibling::step[1]");
805
806             if (prevstep != null) {
807                 otherformid = Utils.getAttribute(prevstep, "form-schema");
808             }
809
810             Utils.createAndAppendNode(wizardnode, "prevform", otherformid);
811
812             otherformid = "";
813
814             Node nextstep = Utils.selectSingleNode(step, "./following-sibling::step[1]");
815
816             if (nextstep != null) {
817                 otherformid = Utils.getAttribute(nextstep, "form-schema");
818             }
819
820             Utils.createAndAppendNode(wizardnode, "nextform", otherformid);
821         }
822
823         // process all forms
824
NodeList formlist = Utils.selectNodeList(schema, "/*/form-schema");
825
826         if (formlist.getLength() == 0) { // this can be done by dtd-checking!
827
throw new WizardException("No form-schema was found in the xml. Make sure at least one form-schema node is present.");
828         }
829
830         for (int f = 0; f < formlist.getLength(); f++) {
831             Node form = formlist.item(f);
832
833             // Make prehtml form.
834
Node prehtmlform = preHtml.createElement("form");
835             Utils.copyAllAttributes(form, prehtmlform);
836             wizardnode.appendChild(prehtmlform);
837
838             // Add the title, description.
839
NodeList props = Utils.selectNodeList(form, "title|subtitle|description");
840             Utils.appendNodeList(props, prehtmlform);
841
842             // check all fields and do the thingies
843
createPreHtmlForm(prehtmlform, form, data);
844         }
845
846         // now, resolve optionlist values:
847
// - The schema contains the list definitions, from which the values are copied.
848
// - Each list may have a query attached, which is performed before the copying.
849
NodeList optionlists = Utils.selectNodeList(wizardnode, ".//field/optionlist");
850
851         for (int i = 0; i < optionlists.getLength(); i++) {
852             Node optionlist = optionlists.item(i);
853
854             String JavaDoc listname = Utils.getAttribute(optionlist, "select");
855
856             // import and create list if select was set - otherwise, this optionlist is defined 'inline'
857
if (listname != null && !listname.equals("")) {
858                 log.debug("Handling optionlist: " + i + ": " + listname);
859
860                 Node list = Utils.selectSingleNode(wizardSchema, "/*/lists/optionlist[@name='" + listname + "']");
861
862                 if (list == null) {
863                     // Not found in definition. Put an error in the list and proceed with the
864
// next list.
865
log.debug("Not found! Proceeding with next list.");
866
867                     Element option = optionlist.getOwnerDocument().createElement("option");
868                     option.setAttribute("id", "-");
869                     Utils.storeText(option,
870                                     "Error: optionlist '" + listname + "' not found");
871                     optionlist.appendChild(option);
872
873                     continue;
874                 }
875
876                 // Test if this list has a query and get the time-out related values.
877
Node query = Utils.selectSingleNode(list, "query");
878                 long currentTime = new Date().getTime();
879                 long queryTimeOut = 1000 * Long.parseLong(Utils.getAttribute(list,
880                                                                              "query-timeout", String.valueOf(this.listQueryTimeOut)));
881                 long lastExecuted = currentTime - queryTimeOut - 1;
882
883                 if (query != null) {
884                     String JavaDoc lastExecutedString = Utils.getAttribute(query, "last-executed", "never");
885
886                     if (!lastExecutedString.equals("never")) {
887                         lastExecuted = Long.parseLong(lastExecutedString);
888                     }
889                 }
890
891                 // Execute the query if it's there and only if it has timed out.
892
if ((query != null) && ((currentTime - lastExecuted) > queryTimeOut)) {
893                     log.debug("Performing query for optionlist '" + listname +
894                               "'. Cur time " + currentTime + " last executed " + lastExecuted +
895                               " timeout " + queryTimeOut + " > " + (currentTime - lastExecuted));
896
897                     Node queryresult = null;
898
899                     try {
900                         // replace {$origin} and such
901
String JavaDoc newWhere = Utils.fillInParams(Utils.getAttribute(query, "where"), variables);
902                         Utils.setAttribute(query, "where", newWhere);
903                         queryresult = databaseConnector.getList(query);
904                         queryresult = Utils.selectSingleNode(queryresult, "/getlist/query");
905                     } catch (Exception JavaDoc e) {
906                         // Bad luck, tell the user and try the next list.
907
log.debug("Error during query, proceeding with next list: " + e.toString());
908
909                         Element option = optionlist.getOwnerDocument().createElement("option");
910                         option.setAttribute("id", "-");
911                         Utils.storeText(option, "Error: query for '" + listname + "' failed");
912                         optionlist.appendChild(option);
913
914                         continue;
915                     }
916
917                     // Remind the current time.
918
Utils.setAttribute(query, "last-executed", String.valueOf(currentTime));
919
920                     // Remove any already existing options.
921
NodeList olditems = Utils.selectNodeList(list, "option");
922
923                     for (int itemindex = 0; itemindex < olditems.getLength();
924                          itemindex++) {
925                         list.removeChild(olditems.item(itemindex));
926                     }
927
928                     // Loop through the queryresult and add the included objects by creating
929
// an option element for each one. The id and content of the option
930
// element are taken from the object by performing the xpaths on the object,
931
// that are given by the list definition.
932
NodeList items = Utils.selectNodeList(queryresult, "*");
933                     String JavaDoc idPath = Utils.getAttribute(list, "optionid", "@number");
934                     String JavaDoc contentPath = Utils.getAttribute(list, "optioncontent", "field");
935
936                     for (int itemindex = 0; itemindex < items.getLength();
937                          itemindex++) {
938                         Node item = items.item(itemindex);
939                         String JavaDoc optionId = Utils.transformAttribute(item, idPath, true);
940                         String JavaDoc optionContent = Utils.transformAttribute(item,
941                                                                         contentPath, true);
942                         Element option = list.getOwnerDocument().createElement("option");
943                         option.setAttribute("id", optionId);
944                         Utils.storeText(option, optionContent);
945                         list.appendChild(option);
946                     }
947                 }
948
949                 // Now copy the items of the list definition to the preHTML list.
950
NodeList items = Utils.selectNodeList(list, "option");
951                 Utils.appendNodeList(items, optionlist);
952
953             }
954
955             // set selected=true for option which is currently selected
956
String JavaDoc selectedValue = Utils.selectSingleNodeText(optionlist,
957                                                               "../value/text()", ""); //.getNodeValue();
958
log.debug("Trying to preselect the list at value: " + selectedValue);
959
960             Node selectedoption = Utils.selectSingleNode(optionlist,
961                                                          "option[@id='" + selectedValue + "']");
962
963             if (selectedoption != null) {
964                 // found! Let's set it selected.
965
Utils.setAttribute(selectedoption, "selected", "true");
966             }
967         }
968
969         // Okee, we are ready. Let's return what we've been working on so hard.
970
return preHtml;
971     }
972
973     /**
974      * This method is used by the #createPreHtml method to generate a pre-html form.
975      *
976      * @param form The node of the pre-html form which is to be generated
977      * @param formdef the node of the wizardschema form definition
978      * @param dataContext Points to the datacontext node which should be used for this form.
979      */

980     public void createPreHtmlForm(Node form, Node formdef, Node dataContext)
981         throws WizardException {
982         if (log.isDebugEnabled()) {
983             log.trace("Creating preHTMLForm for form:" + form + " / formdef:" + formdef + " / data:" + dataContext);
984         }
985
986         // select all fields on first level
987
NodeList fields = Utils.selectNodeList(formdef, "fieldset|field|list|command");
988
989         // process all possible fields
990
// - Parse the fdatapath attribute to obtain the corresponding data fields.
991
// - Create a form field for each found data field.
992
for (int i = 0; i < fields.getLength(); i++) {
993             Node field = fields.item(i);
994
995             // let's see what we should do here
996
String JavaDoc nodeName = field.getNodeName();
997
998             // a field set
999
if (nodeName.equals("fieldset")) {
1000                Node newfieldset = form.getOwnerDocument().createElement("fieldset");
1001                Utils.copyAllAttributes(field, newfieldset);
1002
1003                NodeList itemprops = Utils.selectNodeList(field, "prompt");
1004                Utils.appendNodeList(itemprops, newfieldset);
1005
1006                // place newfieldset in pre-html form
1007
form.appendChild(newfieldset);
1008                createPreHtmlForm(newfieldset, field, dataContext);
1009            } else {
1010                Node fieldDataNode = null;
1011                String JavaDoc xpath = Utils.getAttribute(field, "fdatapath", null);
1012
1013                if (xpath == null) {
1014                    String JavaDoc ftype = Utils.getAttribute(field, "ftype", null);
1015
1016                    if (!("startwizard".equals(ftype) || "wizard".equals(ftype))) {
1017                        throw new WizardException("A field tag should contain one of the following attributes: fdatapath or name");
1018                    }
1019                } else {
1020                    if (log.isDebugEnabled()) {
1021                        log.debug("Doing '" + xpath + "'");
1022                        log.debug("on " + Utils.getXML(dataContext));
1023                    }
1024
1025                    // A normal field.
1026
if (nodeName.equals("field")) {
1027                        // xpath is found.
1028
fieldDataNode = Utils.selectSingleNode(dataContext, xpath);
1029
1030                        if (fieldDataNode != null) {
1031                            // create normal formfield.
1032
int endFieldContextXpath = xpath.lastIndexOf("/") > -1 ? xpath.lastIndexOf('/') : 0;
1033                            String JavaDoc fieldContextXPath = xpath.substring(0, endFieldContextXpath);
1034                            String JavaDoc mayWriteXPath = "".equals(fieldContextXPath) ? "@maywrite" : fieldContextXPath + "/@maywrite";
1035                            Utils.setAttribute(field, "maywrite", Utils.selectSingleNodeText(dataContext, mayWriteXPath, "true"));
1036                            mergeConstraints(field, fieldDataNode);
1037                            createFormField(form, field, fieldDataNode);
1038                        } else {
1039                            String JavaDoc ftype = Utils.getAttribute(field, "ftype");
1040
1041                            if ("function".equals(ftype)) {
1042                                log.debug("Not an data node, setting number attribute, because it cannot be found with fdatapath");
1043
1044                                //set number attribute in field, then you can use it in wizard.xsl
1045
Utils.setAttribute(field, "number", Utils.selectSingleNodeText(dataContext, "object/@number", ""));
1046                                Utils.setAttribute(field, "maywrite", Utils.selectSingleNodeText(dataContext, "object/@maywrite", "true"));
1047
1048                                // create the formfield (should be using the current data node ???)
1049
createFormField(form, field, fieldDataNode);
1050                            } else if ("startwizard".equals(ftype) || "wizard".equals(ftype)) {
1051                                log.debug("A startwizard!");
1052                                // create the formfield using the current data node
1053
createFormField(form, field, dataContext);
1054                            } else {
1055                                // throw an exception, but ONLY if the datapath was created from a 'name' attribute
1056
// (only in that case can we be sure that the path is faulty - in otehr cases
1057
// the path can be valid but point to a related object that is not present)
1058
String JavaDoc fname = Utils.getAttribute(field, "name", null);
1059
1060                                if (fname != null) {
1061                                    throw new WizardException("Perhaps the field with name '" + fname + "' does not exist?");
1062                                }
1063                            }
1064                        }
1065                    }
1066                    // A list "field". Needs special processing.
1067
if (nodeName.equals("list")) {
1068                        NodeList fieldInstances = Utils.selectNodeList(dataContext, xpath);
1069
1070                        if (fieldInstances == null) {
1071                            throw new WizardException("The xpath: " + xpath +
1072                                                      " is not valid. Note: this xpath maybe generated from a <field name='fieldname'> tag. Make sure you use simple valid fieldnames use valid xpath syntax.");
1073                        }
1074                        createFormList(form, field, fieldInstances, dataContext);
1075                    }
1076                }
1077            }
1078        }
1079    }
1080
1081    /**
1082     * This method loads the schema using the properties of the wizard. It loads the wizard using #wizardSchemaFilename,
1083     * resolves the includes, and 'tags' all datanodes. (Places temp. ids in the schema).
1084     *
1085     */

1086    private void loadSchema(URL JavaDoc wizardSchemaFile) throws WizardException {
1087        schema = wizardSchemaCache.getDocument(wizardSchemaFile);
1088
1089        if (schema == null) {
1090            schema = Utils.loadXMLFile(wizardSchemaFile);
1091
1092            List dependencies = resolveIncludes(schema.getDocumentElement());
1093            resolveShortcuts(schema.getDocumentElement(), true);
1094
1095            wizardSchemaCache.put(wizardSchemaFile, schema, dependencies);
1096
1097            log.debug("Schema loaded (and resolved): " + wizardSchemaFile);
1098        } else {
1099            log.debug("Schema found in cache: " + wizardSchemaFile);
1100        }
1101
1102        // tag schema nodes
1103
NodeList fields = Utils.selectNodeList(schema, "//field|//list|//item");
1104        Utils.tagNodeList(fields, "fid", "f", 1);
1105    }
1106
1107    /**
1108     * This method resolves the includes (and extends) in a wizard. It searches for include="" attributes, and searches for extends="" attributes.
1109     * Include means: the file is loaded (uses the path and assumes it references from the basepath param, and the referenced node
1110     * is placed 'over' the existing node. Attributes are copied also. Any content in the original node is removed.
1111     * Extends means: same as include, but now, the original content is not thrown away, and the nodes are placed after the included node.<br />
1112     * Note: this does not work with teh wizard-schema, and only ADDS objects when overriding (it does not replace them)
1113     *
1114     * This method is a recursive one. Included files are also scanned again for includes.
1115     *
1116     * @param node The node from where to start searching for include and extends attributes.
1117     * @returns A list of included files.
1118     *
1119     */

1120    private List resolveIncludes(Node node) throws WizardException {
1121        List result = new ArrayList();
1122
1123        // Resolve references to elements in other wizards. This can be by inclusion
1124
// or extension.
1125
NodeList externalReferences = Utils.selectNodeList(node,
1126                                                           "//*[@include or @extends]");
1127        Document targetdoc = node.getOwnerDocument();
1128
1129        if (externalReferences != null) {
1130            for (int i = 0; i < externalReferences.getLength(); i++) {
1131                Node referer = externalReferences.item(i);
1132                boolean inherits = !Utils.getAttribute(referer, "extends", "")
1133                    .equals("");
1134                String JavaDoc includeUrl = Utils.getAttribute(referer, "include");
1135
1136                if (inherits) {
1137                    includeUrl = Utils.getAttribute(referer, "extends");
1138                }
1139
1140                try {
1141                    // Resolve the filename and form-schema id.
1142
String JavaDoc url = includeUrl;
1143                    String JavaDoc externalId = "not applicable";
1144                    int hash = includeUrl.indexOf('#');
1145
1146                    if (hash != -1) {
1147                        url = includeUrl.substring(0, includeUrl.indexOf('#'));
1148                        externalId = includeUrl.substring(includeUrl.indexOf('#') +
1149                                                          1);
1150                    }
1151
1152                    URL JavaDoc file;
1153                    try {
1154                        file = uriResolver.resolveToURL(url, null);
1155                    } catch (Exception JavaDoc e) {
1156                        throw new WizardException(e);
1157                    }
1158                    result.add(file);
1159
1160                    // Load the external file.
1161
Document externalDocument = Utils.loadXMLFile(file);
1162
1163                    if (externalDocument == null) {
1164                        throw new WizardException("Could not load and parse included file. Filename:" + file);
1165                    }
1166
1167                    // Add a copy of the external part to our schema here, to replace the
1168
// referer itself.
1169
Node externalPart = null;
1170
1171                    if (hash == -1) {
1172                        // Load the entire file.
1173
externalPart = externalDocument.getDocumentElement();
1174                    } else if (externalId.startsWith("xpointer(")) {
1175                        // Load only part of the file, using an xpointer.
1176
String JavaDoc xpath = externalId.substring(9, externalId.length() - 1);
1177                        externalPart = Utils.selectSingleNode(externalDocument, xpath);
1178                    } else {
1179                        // Load only the node with the given id.
1180
externalPart = Utils.selectSingleNode(externalDocument,
1181                                                              "//node()[@id='" + externalId + "']");
1182                    }
1183
1184                    // recurse!
1185
result.addAll(resolveIncludes(externalPart));
1186
1187                    // place loaded external part in parent...
1188
Node parent = referer.getParentNode();
1189                    externalPart = parent.insertBefore(targetdoc.importNode(externalPart, true), referer);
1190
1191                    // If the old node had some attributes, copy them to the included one.
1192
Utils.copyAllAttributes(referer, externalPart);
1193
1194                    //
1195
if (inherits) {
1196                        NodeList overriders = Utils.selectNodeList(referer, "node()");
1197
1198                        for (int k = 0; k < overriders.getLength(); k++) {
1199                            // 'inherit' old nodes. Do not clone, as we are essentially 'moving' these
1200
externalPart.appendChild(overriders.item(k));
1201                        }
1202                    }
1203
1204                    // Remove the refering node.
1205
parent.removeChild(referer);
1206                } catch (RuntimeException JavaDoc e) {
1207                    log.error(Logging.stackTrace(e));
1208                    throw new WizardException("Error resolving external part '" +
1209                                              includeUrl + "'");
1210                }
1211            }
1212        }
1213
1214        return result;
1215    }
1216
1217    /**
1218     * Resolves shortcuts placed in the schema.
1219     * eg.: if a user just entered <field name="firstname" /> it will be replaced by <field fdatapath="field[@name='firstname']" />
1220     *
1221     * later, other simplifying code could be placed here, so that for more simple fdatapath's more simple commands can be used.
1222     * (maybe we should avoid using xpath in total for normal use of the editwizards?)
1223     *
1224     * @param schemanode The schemanode from were to start searching
1225     * @param recurse Set to true if you want to let the process search in-depth through the entire tree, false if you just want it to search the first-level children
1226     */

1227    private void resolveShortcuts(Node schemaNode, boolean recurse) throws WizardException {
1228        String JavaDoc xpath;
1229
1230        if (recurse) {
1231            xpath = ".//field|.//list";
1232        } else {
1233            xpath = "field|list";
1234        }
1235
1236        NodeList children = Utils.selectNodeList(schemaNode, xpath);
1237
1238        if (children == null) {
1239            throw new RuntimeException JavaDoc("could not perform xpath:" + xpath + " for schemanode:\n" + schemaNode);
1240        }
1241
1242        Node node;
1243
1244        for (int i = 0; i < children.getLength(); i++) {
1245            resolveShortcut(children.item(i));
1246        }
1247
1248        // if no <steps /> node exist, a default node is created with all form schema's in found order
1249
if (Utils.selectSingleNode(schemaNode, "steps") == null) {
1250            Node stepsNode = schemaNode.getOwnerDocument().createElement("steps");
1251            NodeList forms = Utils.selectNodeList(schemaNode, "form-schema");
1252
1253            for (int i = 0; i < forms.getLength(); i++) {
1254                Node formStep = schemaNode.getOwnerDocument().createElement("step");
1255                String JavaDoc formId = Utils.getAttribute(forms.item(i), "id", null);
1256
1257                if (formId == null) {
1258                    formId = "tempformid_" + i;
1259                    Utils.setAttribute(forms.item(i), "id", formId);
1260                }
1261
1262                Utils.setAttribute(formStep, "form-schema", formId);
1263                stepsNode.appendChild(formStep);
1264            }
1265
1266            // TODO: according to the dtd, schemanode should be inserted before any form-schema nodes
1267
schemaNode.appendChild(stepsNode);
1268        }
1269    }
1270
1271    /**
1272     * Resolves possible shortcut for this given single node. (@see #resolveShortcuts for more information)
1273     *
1274     * @param node The node to resolve
1275     */

1276    private void resolveShortcut(Node singleNode) throws WizardException {
1277        // transforms <field name="firstname"/> into <field fdatapath="field[@name='firstname']" />
1278
String JavaDoc nodeName = singleNode.getNodeName();
1279
1280        if (nodeName.equals("field")) {
1281            // field nodes
1282
String JavaDoc name = Utils.getAttribute(singleNode, "name", null);
1283            String JavaDoc fdatapath = Utils.getAttribute(singleNode, "fdatapath", null);
1284            String JavaDoc ftype = Utils.getAttribute(singleNode, "ftype", null);
1285
1286            if (fdatapath == null) {
1287                // if no name, select the current node
1288
if (name == null) {
1289                    if ("startwizard".equals(ftype) || "wizard".equals(ftype)) {
1290                        fdatapath = ".";
1291                    }
1292                } else {
1293                    if ("number".equals(name)) {
1294                        Utils.setAttribute(singleNode, "ftype", "data");
1295
1296                        // the number field may of course never be edited
1297
fdatapath = "@number";
1298                    } else {
1299                        fdatapath = "field[@name='" + name + "']";
1300                    }
1301
1302                    // normal field or a field inside a list node?
1303
Node parentNode = singleNode.getParentNode();
1304                    String JavaDoc parentname = parentNode.getNodeName();
1305
1306                    // skip fieldset
1307
if (parentname.equals("fieldset")) {
1308                        parentname = parentNode.getParentNode().getNodeName();
1309                    }
1310
1311                    if (parentname.equals("item")) {
1312                        fdatapath = "object/" + fdatapath;
1313                    }
1314                }
1315
1316                Utils.setAttribute(singleNode, "fdatapath", fdatapath);
1317            }
1318        } else if (nodeName.equals("list")) {
1319            // List nodes
1320
String JavaDoc role = Utils.getAttribute(singleNode, "role", null); //"insrel");
1321
String JavaDoc destination = Utils.getAttribute(singleNode, "destinationtype", null);
1322
1323            // legacy: use destination if destinationtype not given
1324
if (destination == null) {
1325                destination = Utils.getAttribute(singleNode, "destination", null);
1326            }
1327
1328            String JavaDoc searchString = Utils.getAttribute(singleNode, "searchdir", null);
1329
1330            StringBuffer JavaDoc fdatapath = null;
1331            String JavaDoc tmp = Utils.getAttribute(singleNode, "fdatapath", null);
1332            if (tmp != null) fdatapath = new StringBuffer JavaDoc(tmp);
1333
1334            if (fdatapath != null) {
1335                if (searchString != null || role != null || destination != null) {
1336                    log.warn("When 'datapath' is geven, it does not make sense to specify the 'searchdir', role' or 'destinationtype' attributes. These attributes are ignored.");
1337                }
1338            } else {
1339                // determine role
1340
fdatapath = new StringBuffer JavaDoc();
1341
1342                if (role != null) {
1343                    fdatapath.append("@role='").append(role).append('\'');
1344                }
1345
1346                // determine destination type
1347
if (destination != null) {
1348                    if (fdatapath.length() != 0) {
1349                        fdatapath.append(" and ");
1350                    }
1351                    // should also include types that inherit...
1352

1353                    Node con = getConstraints(destination);
1354                    // and then ???
1355
NodeList descendants = null;
1356                    if (con!=null) descendants = Utils.selectNodeList(con,"descendants/descendant");
1357
1358                    if (descendants == null || descendants.getLength() == 0) {
1359                        fdatapath.append("object/@type='").append(destination).append('\'');
1360                    } else {
1361
1362                        fdatapath.append("(object/@type='").append(destination).append('\'');
1363
1364                        for (int desci = 0; desci < descendants.getLength();desci++) {
1365                            Node descendant = descendants.item(desci);
1366                            String JavaDoc descendantName = Utils.getAttribute(descendant, "type", null);
1367                            fdatapath.append(" or object/@type='").append(descendantName).append('\'');
1368                        }
1369
1370                        fdatapath.append(')');
1371
1372                    }
1373                }
1374
1375                // determine searchdir
1376
int searchDir = RelationStep.DIRECTIONS_BOTH;
1377
1378                if (searchString != null) {
1379                    searchDir = Queries.getRelationStepDirection(searchString);
1380                }
1381
1382                if (searchDir == RelationStep.DIRECTIONS_SOURCE) {
1383                    if (fdatapath.length() != 0) {
1384                        fdatapath.append(" and ");
1385                    }
1386
1387                    fdatapath.append("@source=object/@number");
1388                } else if (searchDir == RelationStep.DIRECTIONS_DESTINATION) {
1389                    if (fdatapath.length() != 0) {
1390                        fdatapath.append(" and ");
1391                    }
1392
1393                    fdatapath.append("@destination=object/@number");
1394                }
1395
1396                fdatapath.insert(0, "relation[");
1397                fdatapath.append(']');
1398
1399                // normal list or a list inside a list?
1400
if (singleNode.getParentNode().getNodeName().equals("item")) {
1401                    fdatapath.insert(0, "object/");
1402                }
1403                Utils.setAttribute(singleNode, "fdatapath", fdatapath.toString());
1404            }
1405        }
1406    }
1407
1408    private void expandAttribute(Node node, String JavaDoc name, String JavaDoc defaultvalue) {
1409        String JavaDoc value = Utils.transformAttribute(data.getDocumentElement(),
1410                                                Utils.getAttribute(node, name, null), false, variables);
1411
1412        if (value == null) {
1413            value = defaultvalue;
1414        }
1415
1416        if (value != null) {
1417            Utils.setAttribute(node, name, value);
1418        }
1419    }
1420
1421    /**
1422     * Creates a form item (each of which may consist of several single form fields)
1423     * for each given datanode.
1424     */

1425    private void createFormList(Node form, Node fieldlist, NodeList datalist,
1426                                Node parentdatanode) throws WizardException {
1427        // copy all attributes from fielddefinition to new pre-html field definition
1428
log.debug("creating form list");
1429
1430        Node newlist = fieldlist.cloneNode(false);
1431        newlist = form.getOwnerDocument().importNode(newlist, false);
1432        Utils.copyAllAttributes(fieldlist, newlist);
1433
1434        // place parent object number as attribute number
1435
if (parentdatanode != null) {
1436            Utils.setAttribute(newlist, "number", Utils.getAttribute(parentdatanode, "number"));
1437        }
1438
1439        // Add the title, description.
1440
NodeList props = Utils.selectNodeList(fieldlist,
1441                                              "title|description|action|command");
1442
1443        Utils.appendNodeList(props, newlist);
1444
1445        // Add the title, description from the item as attributes
1446
NodeList itemTitle = Utils.selectNodeList(fieldlist, "item/title|item/description");
1447
1448        for (int i=0; i<itemTitle.getLength(); i++) {
1449            Utils.setAttribute(newlist, "item" + itemTitle.item(i).getNodeName(), Utils.getText(itemTitle.item(i)));
1450        }
1451
1452        // expand attribute 'startnodes' for search command
1453
Node command = Utils.selectSingleNode(newlist, "command[@name='search']");
1454
1455
1456        if (command != null) {
1457            expandAttribute(command, "startnodes", null);
1458            // expand constraints attribute on search action
1459
String JavaDoc cAttribute = Utils.getAttribute(command, "constraints");
1460            if(cAttribute != null && !cAttribute.equals("")){
1461                expandAttribute(command, "constraints", dataId);
1462            }
1463        }
1464
1465        // expand attribute 'objectnumber' en 'origin' for editwizard command
1466
//command = Utils.selectSingleNode(newlist, "command[@name='startwizard']");
1467
NodeList commands = Utils.selectNodeList(newlist, "command[@name='startwizard']");
1468        if (commands != null) {
1469            for (int i=0; i<commands.getLength(); i++) {
1470                command = commands.item(i);
1471                if (command!=null) {
1472                    expandAttribute(command,"objectnumber","new");
1473                    expandAttribute(command,"origin",dataId);
1474                    expandAttribute(command,"wizardname",null);
1475                }
1476            }
1477        }
1478
1479        String JavaDoc hiddenCommands = "|" +
1480            Utils.getAttribute(fieldlist, "hidecommand") + "|";
1481
1482        // place newfield in pre-html form
1483
form.appendChild(newlist);
1484
1485        // calculate minoccurs and maxoccurs
1486
int minoccurs = Integer.parseInt(Utils.getAttribute(fieldlist,
1487                                                            "minoccurs", "0"));
1488        int nrOfItems = datalist.getLength();
1489
1490        int maxoccurs = -1;
1491        String JavaDoc maxstr = Utils.getAttribute(fieldlist, "maxoccurs", "*");
1492
1493        if (!maxstr.equals("*")) {
1494            maxoccurs = Integer.parseInt(maxstr);
1495        }
1496
1497        String JavaDoc orderby = Utils.getAttribute(fieldlist, "orderby", null);
1498
1499        if ((orderby != null) && (orderby.indexOf("@") == -1)) {
1500            orderby = "object/field[@name='" + orderby + "']";
1501        }
1502
1503        String JavaDoc ordertype = Utils.getAttribute(fieldlist, "ordertype", "string");
1504
1505        // set the orderby attribute for all the nodes
1506
List tempstorage = new ArrayList(datalist.getLength());
1507
1508        for (int dataIndex = 0; dataIndex < datalist.getLength(); dataIndex++) {
1509            Element datacontext = (Element) datalist.item(dataIndex);
1510
1511            if (orderby != null) {
1512                String JavaDoc orderByValue = Utils.selectSingleNodeText(datacontext,
1513                                                                 orderby, "");
1514
1515                // make sure of type
1516
if (ordertype.equals("number")) {
1517                    double orderDbl;
1518
1519                    try {
1520                        orderDbl = Double.parseDouble(orderByValue);
1521                    } catch (Exception JavaDoc e) {
1522                        log.error("fieldvalue " + orderByValue + " is not numeric");
1523                        orderDbl = -1;
1524                    }
1525
1526                    orderByValue = "" + orderDbl;
1527                }
1528
1529                // sets orderby
1530
datacontext.setAttribute("orderby", orderByValue);
1531            }
1532
1533            // clears firstitem
1534
datacontext.setAttribute("firstitem", "false");
1535
1536            // clears lastitem
1537
datacontext.setAttribute("lastitem", "false");
1538            tempstorage.add(datacontext);
1539        }
1540
1541        // sort list
1542
if (orderby != null) {
1543            Collections.sort(tempstorage, new OrderByComparator(ordertype));
1544        }
1545
1546        // and make form
1547
int listsize = tempstorage.size();
1548
1549        for (int dataindex = 0; dataindex < listsize; dataindex++) {
1550            Element datacontext = (Element) tempstorage.get(dataindex);
1551
1552            // Select the form item
1553
Node item = Utils.selectSingleNode(fieldlist, "item");
1554
1555            if (item == null) {
1556                item = Utils.selectSingleNode(fieldlist, "item");
1557
1558                if (item == null) {
1559                    throw new WizardException("Could not find item in a list of " +
1560                                              wizardName);
1561                }
1562
1563                if (log.isDebugEnabled()) {
1564                    log.debug("found an item " + item.toString());
1565                }
1566            }
1567
1568            Node newitem = item.cloneNode(false);
1569            newitem = form.getOwnerDocument().importNode(newitem, false);
1570            newlist.appendChild(newitem);
1571
1572            // Copy all attributes from data to new pre-html field def (mainly needed for the did).
1573
Utils.copyAllAttributes(datacontext, newitem);
1574            Utils.copyAllAttributes(item, newitem);
1575
1576            // Add the title, description.
1577
NodeList itemprops = Utils.selectNodeList(item, "title|description");
1578            Utils.appendNodeList(itemprops, newitem);
1579
1580            // and now, do the recursive trick! All our fields inside need to be processed.
1581
createPreHtmlForm(newitem, item, datacontext);
1582
1583            // finally, see if we need to place some commands here
1584
if ( /* nrOfItems > minoccurs && you should be able to replace!*/
1585                hiddenCommands.indexOf("|delete-item|") == -1) {
1586                addSingleCommand(newitem, "delete-item", datacontext);
1587            }
1588
1589            if (orderby != null) {
1590                if ((dataindex > 0) && (hiddenCommands.indexOf("|move-up|") == -1)) {
1591                    addSingleCommand(newitem, "move-up", datacontext,
1592                                     (Node) tempstorage.get(dataindex - 1));
1593                }
1594
1595                if (((dataindex + 1) < listsize) &&
1596                    (hiddenCommands.indexOf("|move-down|") == -1)) {
1597                    addSingleCommand(newitem, "move-down", datacontext,
1598                                     (Node) tempstorage.get(dataindex + 1));
1599                }
1600            }
1601
1602            if (dataindex == 0) {
1603                datacontext.setAttribute("firstitem", "true");
1604            }
1605
1606            if (dataindex == (tempstorage.size() - 1)) {
1607                datacontext.setAttribute("lastitem", "true");
1608            }
1609        }
1610
1611        // should the 'save' button be inactive because of this list?
1612
// works likes this:
1613
// If the minoccurs or maxoccurs condiditions are not satisfied, in the 'wizard.xml'
1614
// to the form the 'invalidlist' attribute is filled with the name of the guilty list. By wizard.xsl then this value
1615
// is copied to the html.
1616
//
1617
// validator.js/doValidateForm returns invalid as long as this invalid list attribute of the html form is not an
1618
// emptry string.
1619
if (log.isDebugEnabled()) {
1620            log.debug("minoccurs:" + minoccurs + " maxoccurs: " + maxoccurs +
1621                      " items: " + nrOfItems);
1622        }
1623
1624        if (((nrOfItems > maxoccurs) && (maxoccurs != -1)) ||
1625            (nrOfItems < minoccurs)) { // form cannot be valid in that case
1626
((Element) newlist).setAttribute("status", "invalid");
1627
1628            // which list?
1629
String JavaDoc listTitle = Utils.selectSingleNodeText(fieldlist, "title",
1630                                                          "some list");
1631            ((Element) form).setAttribute("invalidlist", listTitle);
1632        } else {
1633            ((Element) newlist).setAttribute("status", "valid");
1634        }
1635
1636        log.debug("can we place an add-button?");
1637
1638        if ((hiddenCommands.indexOf("|add-item|") == -1) &&
1639            ((maxoccurs == -1) || (maxoccurs > nrOfItems)) &&
1640            ( Utils.selectSingleNode(fieldlist, "action[@type='create']") != null ||
1641              Utils.selectSingleNode(fieldlist, "action[@type='add']") != null )) {
1642            String JavaDoc defaultpath = ".";
1643
1644            if (fieldlist.getParentNode().getNodeName().equals("item")) {
1645                // this is a list in a list.
1646
defaultpath = "object";
1647            }
1648
1649            String JavaDoc fparentdatapath = Utils.getAttribute(fieldlist,
1650                                                        "fparentdatapath", defaultpath);
1651            Node chosenparent = Utils.selectSingleNode(parentdatanode,
1652                                                       fparentdatapath);
1653
1654            // try to find out what datanode is the parent of inserts...
1655
if ((datalist.getLength() > 0) && fparentdatapath.equals(".")) {
1656                // we have an example and no fparentdatapath was given. So, create a 'brother'
1657
addSingleCommand(newlist, "add-item",
1658                                 datalist.item(0).getParentNode());
1659            } else {
1660                // no living examples exist. Use the chosenparent
1661
addSingleCommand(newlist, "add-item", chosenparent);
1662            }
1663        }
1664
1665        log.debug("end");
1666    }
1667
1668    /**
1669     * This method generates a form field node in the pre-html.
1670     *
1671     * @param form the pre-html form node
1672     * @param field the form definition field node
1673     * @param dataNode the current context data node. It might be 'null' if the field already contains the 'number' attribute.
1674     */

1675    private void createFormField(Node form, Node field, Node dataNode)
1676        throws WizardException {
1677        if (log.isDebugEnabled()) {
1678            log.debug("Creating form field for " + field + " wizard for obj: " + objectNumber);
1679        }
1680
1681        // copy all attributes from fielddefinition to new pre-html field definition
1682
Node newField = form.getOwnerDocument().createElement("field");
1683
1684        Utils.copyAllAttributes(field, newField);
1685
1686        // place newfield in pre-html form
1687
form.appendChild(newField);
1688
1689        List exceptAttrs = new ArrayList(); // what is this?
1690
exceptAttrs.add("fid");
1691
1692        // copy all attributes from data to new pre-html field def
1693
if ((dataNode != null) && (dataNode.getNodeType() != Node.ATTRIBUTE_NODE)) {
1694            Utils.copyAllAttributes(dataNode, newField, exceptAttrs);
1695        }
1696
1697        String JavaDoc ftype = Utils.getAttribute(newField, "ftype");
1698        String JavaDoc dttype = Utils.getAttribute(newField, "dttype");
1699
1700        // place html form field name (so that we always know about which datanode and fieldnode we are talking)
1701
String JavaDoc htmlFieldName = calculateFormName(newField);
1702        Utils.setAttribute(newField, "fieldname", htmlFieldName);
1703
1704        // place objectNumber as attribute number, if not already was placed there by the copyAllAttributes method.
1705
if ((dataNode != null) && (Utils.getAttribute(dataNode, "number", null) == null)) {
1706            Utils.setAttribute(newField, "number", Utils.getAttribute(dataNode.getParentNode(), "number"));
1707        }
1708
1709        // resolve special attributes
1710
if (ftype.equals("startwizard")) {
1711            String JavaDoc wizardObjectNumber = Utils.getAttribute(newField, "objectnumber", null);
1712
1713            // if no objectnumber is found, assign the number of the current field.
1714
// exception is when the direct parent is a form.
1715
// in that case, we are editting the current object, so instead assign new
1716
// note: this latter does not take into account fieldsets!
1717
if (wizardObjectNumber == null) {
1718                if (form.getNodeName().equals("form")) {
1719                    wizardObjectNumber = "new";
1720                } else {
1721                    wizardObjectNumber = "{object/@number}";
1722                }
1723            }
1724
1725            // evaluate object number
1726
wizardObjectNumber = Utils.transformAttribute(dataNode, wizardObjectNumber);
1727
1728            boolean mayEdit = true;
1729
1730            if ("new".equals(wizardObjectNumber)) {
1731                // test whether this number may be created
1732
// we can't do this now, as we cannot determine the type of node
1733
// unless we load the wizard.
1734
// This may be added in a later stage, when loading of wizard templates is
1735
// moved to a seperate module
1736
mayEdit = true;
1737            } else {
1738                // test whether this number may be edited
1739
mayEdit = mayEditNode(wizardObjectNumber);
1740            }
1741
1742            if (!mayEdit) {
1743                // remove this field from the form
1744
form.removeChild(newField);
1745            }
1746
1747            Utils.setAttribute(newField, "objectnumber", wizardObjectNumber);
1748
1749            String JavaDoc wizardPath = Utils.getAttribute(newField, "wizardname", null);
1750            if (wizardPath != null) {
1751                wizardPath = Utils.transformAttribute(dataNode, wizardPath);
1752                Utils.setAttribute(newField, "wizardname", wizardPath);
1753            }
1754
1755            String JavaDoc wizardOrigin = Utils.getAttribute(newField, "origin", null);
1756
1757            if (wizardOrigin == null) {
1758                wizardOrigin = dataId;
1759            } else {
1760                wizardOrigin = Utils.transformAttribute(dataNode, wizardOrigin);
1761            }
1762
1763            Utils.setAttribute(newField, "origin", wizardOrigin);
1764        } else if (!ftype.equals("function")) {
1765            // check rights - if you can't edit, set ftype to data
1766
if (!mayEditNode(Utils.getAttribute(newField, "number"))) {
1767                ftype = "data";
1768                Utils.getAttribute(newField, "ftype", ftype);
1769            }
1770        }
1771
1772        // binary type needs special processing
1773
if ("binary".equals(dttype)) {
1774            addBinaryData(newField);
1775        }
1776
1777        NodeList list = Utils.selectNodeList(field, "optionlist|prompt|description|action|prefix|postfix");
1778        Utils.appendNodeList(list, newField);
1779
1780        // place value
1781
// by default, theValue is the text of the node.
1782
String JavaDoc theValue = "";
1783
1784        try {
1785            if (dataNode == null) {
1786                if (ftype.equals("function")) {
1787                    theValue = Utils.getAttribute(field, "name");
1788                    log.debug("Found a function field " + theValue);
1789                } else if (ftype.equals("startwizard") || ftype.equals("wizard")) {
1790                    log.debug("found a wizard field");
1791                } else {
1792                    log.debug("Probably a new node");
1793                    throw new WizardException("No datanode given for field " +
1794                                              theValue + " and ftype does not equal 'function' or 'startwizard'(but " + ftype + ")");
1795                }
1796            } else if (dataNode.getNodeType() == Node.ATTRIBUTE_NODE) {
1797                theValue = dataNode.getNodeValue();
1798            } else {
1799                theValue = dataNode.getFirstChild().getNodeValue();
1800            }
1801        } catch (RuntimeException JavaDoc e) {
1802            log.error(Logging.stackTrace(e));
1803        }
1804
1805        // if this is a relation, we want the value of the dnumber field
1806
if (ftype.equals("relation")) {
1807            theValue = Utils.getAttribute(newField, "destination");
1808        }
1809
1810        if (theValue == null) {
1811            theValue = "";
1812        }
1813
1814        Node value = form.getOwnerDocument().createElement("value");
1815        Utils.storeText(value, theValue);
1816        newField.appendChild(value);
1817    }
1818
1819    private void addSingleCommand(Node field, String JavaDoc commandname, Node datanode) {
1820        addSingleCommand(field, commandname, datanode, null);
1821    }
1822
1823    private void addSingleCommand(Node field, String JavaDoc commandname, Node datanode,
1824                                  Node otherdatanode) {
1825        String JavaDoc otherdid = "";
1826
1827        if (otherdatanode != null) {
1828            otherdid = Utils.getAttribute(otherdatanode, "did");
1829        }
1830
1831        Element command = field.getOwnerDocument().createElement("command");
1832        command.setAttribute("name", commandname);
1833        command.setAttribute("cmd", "cmd/" + commandname + "/" + Utils.getAttribute(field, "fid") + "/" +
1834                             Utils.getAttribute(datanode, "did") + "/" + otherdid + "/");
1835        command.setAttribute("value", Utils.getAttribute(datanode, "did"));
1836        field.appendChild(command);
1837    }
1838
1839    /**
1840     * Returns the proper form-name (for <input name="xx" />).
1841     *
1842     * @param preHtmlFormField This is the prehtml node where the field data should be
1843     * @return A string with the proper html field-name.
1844     */

1845    private String JavaDoc calculateFormName(Node preHtmlFormField) {
1846        try {
1847            String JavaDoc fid = Utils.getAttribute(preHtmlFormField, "fid");
1848            String JavaDoc did = Utils.getAttribute(preHtmlFormField, "did");
1849
1850            return "field/" + fid + "/" + did;
1851        } catch (RuntimeException JavaDoc e) {
1852            return "field/fid_or_did_missed_a_tag";
1853        }
1854    }
1855
1856    /**
1857     * This method de-encodes a html field-name (@see #calculateFormName) and returns an Array with the decoded values.
1858     * @return The array with id's. First id in the array is the data-id (did), which indicates what datanode is pointed to,
1859     * second id is the fid (field-id) which points to the proper fieldnode in the wizarddefinition.
1860     */

1861    private String JavaDoc[] processFormName(String JavaDoc formName) {
1862        String JavaDoc[] res = { "", "" };
1863
1864        boolean isafield = (formName.indexOf("field/") > -1);
1865        int nr1 = formName.indexOf("/") + 1;
1866        int nr2 = formName.indexOf("/", nr1) + 1;
1867
1868        if ((nr1 < 1) || (nr2 < 1) || !isafield) {
1869            // not good. no 2 slashes found
1870
return null;
1871        }
1872
1873        String JavaDoc fid = formName.substring(nr1, nr2 - 1);
1874        String JavaDoc did = formName.substring(nr2);
1875        res[0] = did;
1876        res[1] = fid;
1877
1878        return res;
1879    }
1880
1881    /**
1882     * Puts the given value in the right datanode (given by did), depending on the type
1883     * of the form field.
1884     *
1885     * - text,line: the value is stored as text in the datanode.
1886     * - relation: the value is assumed to be the destination number (dnumber) of the relation.
1887     *
1888     * @param did The data id where the value should be stored
1889     * @param fid The wizarddefinition field id what applies to this data
1890     * @param value The (String) value what should be stored in the data.
1891     */

1892    private void storeValue(String JavaDoc did, String JavaDoc fid, String JavaDoc value) throws WizardException {
1893        if (log.isDebugEnabled()) {
1894            log.debug("String value " + value + " in " + did + " for field " + fid);
1895            log.trace("Using data: " +
1896                      Utils.getSerializedXML(Utils.selectSingleNode(schema,
1897                                                                    ".//*[@fid='" + fid + "']")));
1898        }
1899
1900        String JavaDoc xpath = ".//*[@fid='" + fid + "']/@dttype";
1901        Node dttypeNode = Utils.selectSingleNode(schema, xpath);
1902
1903        if (dttypeNode == null) {
1904            String JavaDoc msg = "No node with fid=" + fid + " could be found";
1905
1906            if (schema != null) {
1907                msg += "\nxpath was:" + xpath + " on:\n" + schema.getDocumentElement();
1908            }
1909
1910            throw new WizardException(msg);
1911        }
1912
1913        String JavaDoc dttype = dttypeNode.getNodeValue();
1914        xpath = ".//*[@did='" + did + "']";
1915
1916        Node datanode = Utils.selectSingleNode(data, xpath);
1917
1918        if (datanode == null) {
1919            String JavaDoc msg = "Unable to store value for field with dttype " + dttype + ". fid=" + fid + ", did=" + did + ", value=" + value + ", wizard:" + wizardName;
1920
1921            if (data != null) {
1922                msg += "\nxpath was:" + xpath + " on:\n" + data.getDocumentElement();
1923            }
1924
1925            log.warn(msg);
1926
1927            return;
1928        }
1929
1930        // everything seems to be ok
1931
if (dttype.equals("binary")) {
1932            // binaries are stored differently
1933
if (getBinary(did) != null) {
1934                Utils.setAttribute(datanode, "href", did);
1935                Utils.storeText(datanode, getBinaryName(did));
1936            }
1937        } else { // default behavior: store content as text
1938
Utils.storeText(datanode, value);
1939        }
1940    }
1941
1942    /**
1943     * Puts the given value in the right field (given by name) of the right node (given by did)
1944     * Assumes a text field.
1945     *
1946     * @param did The data id of the node
1947     * @param fieldName The name of the field
1948     * @param value The (String) value what should be stored in the data.
1949     */

1950    public void storeFieldValue(String JavaDoc did, String JavaDoc fieldName, String JavaDoc value) throws WizardException {
1951        if (log.isDebugEnabled()) {
1952            log.debug("String value " + value + " in " + did + " for field " + fieldName);
1953        }
1954
1955        String JavaDoc xpath = ".//*[@did='" + did + "']";
1956        Node objectNode = Utils.selectSingleNode(data, xpath);
1957
1958        xpath = "./field[@name='" + fieldName + "']";
1959        Node fieldNode = Utils. selectSingleNode(objectNode, xpath);
1960
1961        if (fieldNode == null) {
1962            throw new WizardException("Unable to store value for field with name " + fieldName + " for node with did=" + did + ", value=" + value + ", wizard:" + wizardName);
1963        }
1964        Utils.storeText(fieldNode, value);
1965    }
1966
1967    /**
1968     * Obtains the value form the right field (given by name) of the right node (given by did).
1969     * Assumes a text field.
1970     *
1971     * @param did The data id of the node
1972     * @param fieldName The name of the field
1973     */

1974    public String JavaDoc retrieveFieldValue(String JavaDoc did, String JavaDoc fieldName) throws WizardException {
1975        if (log.isDebugEnabled()) {
1976            log.debug("Get value in " + did + " for field " + fieldName);
1977        }
1978
1979        String JavaDoc xpath = ".//*[@did='" + did + "']";
1980        Node objectNode = Utils.selectSingleNode(data, xpath);
1981
1982        xpath = "./field[@name='" + fieldName + "']";
1983        Node fieldNode = Utils. selectSingleNode(objectNode, xpath);
1984
1985        if (fieldNode == null) {
1986            throw new WizardException("Unable to store value for field with name " + fieldName + " for node with did=" + did + ", wizard:" + wizardName);
1987        }
1988
1989        return Utils.getText(fieldNode);
1990    }
1991
1992
1993    /**
1994     * This method processes the commands sent over http.
1995     *
1996     * @param req The ServletRequest where the commands (name/value pairs) reside.
1997     */

1998    private void processCommands(ServletRequest JavaDoc req) throws WizardException {
1999        log.debug("processing commands");
2000        mayBeClosed = false;
2001        startWizard = false;
2002        startWizardCmd = null;
2003
2004        boolean found = false;
2005        String JavaDoc commandName = "";
2006
2007        List errors = new ArrayList();
2008        Enumeration list = req.getParameterNames();
2009
2010        while (list.hasMoreElements()) {
2011            commandName = (String JavaDoc) list.nextElement();
2012
2013            if ((commandName.indexOf("cmd/") == 0) && !commandName.endsWith(".y")) {
2014                if (log.isDebugEnabled()) {
2015                    log.debug("found a command " + commandName);
2016                }
2017
2018                // this is a command.
2019
String JavaDoc commandValue = req.getParameter(commandName);
2020                WizardCommand wc = new WizardCommand(commandName, commandValue);
2021                processCommand(wc);
2022            } else {
2023                if (log.isDebugEnabled()) {
2024                    log.trace("ignoring non-command " + commandName);
2025                }
2026            }
2027        }
2028
2029    }
2030
2031    /**
2032     * This method is usually called by #processCommands and processes one command.
2033     * Possible wizard commands are:
2034     * <ul>
2035     * <li>delete-item</li>
2036     * <li>update-item</li>
2037     * <li>add-item</li>
2038     * <li>move-up</li>
2039     * <li>move-down</li>
2040     * <li>start-wizard</li>
2041     * <li>goto-form</li>
2042     * <li>cancel</li>
2043     * <li>commit</li>
2044     * </ul>
2045     * @param cmd The command to be processed
2046     *
2047     */

2048    public void processCommand(WizardCommand cmd) throws WizardException {
2049        log.debug("Processing command " + cmd);
2050        // processes the given command
2051
switch (cmd.getType()) {
2052        case WizardCommand.DELETE_ITEM: {
2053            // delete item!
2054
// The command parameters is the did of the node to delete.
2055
// note that a fid parameter is expected in the command syntax but ignored
2056
String JavaDoc did = cmd.getDid();
2057            Node dataNode = Utils.selectSingleNode(data, ".//*[@did='" + did + "']");
2058
2059            if (dataNode != null) {
2060                // Step one: determine what should eb deleted
2061
// if an <action name="delete"> exists, and it has an <object> child,
2062
// the object of the relatiosn should be deleted along with the relation
2063
// if there is no delete action defined, or object is not a child,
2064
// only the relation is deleted
2065
String JavaDoc fid = cmd.getFid();
2066                Node itemNode = Utils.selectSingleNode(schema, ".//*[@fid='" + fid + "']");
2067                Node listNode = itemNode.getParentNode();
2068                Node objectDef = Utils.selectSingleNode(listNode, "action[@type='delete']/object");
2069
2070                Node dataObjectNode = dataNode;
2071
2072                if (objectDef != null) {
2073                    dataObjectNode = Utils.selectSingleNode(dataNode, "object");
2074                }
2075
2076                // all child objects of the object to be deleted are added to a repository.
2077
// these objects are not accessed for editing purposes any more,
2078
// but do continue to exist in the tree
2079
// This prevents the included objects from being deleted (deletion of
2080
// ojects is detected by comparing objects that exist in the original data tree with those
2081
// in the result data tree)
2082
Node newRepos = data.createElement("repos");
2083                NodeList insideObjects = Utils.selectNodeList(dataObjectNode, "object|relation");
2084                Utils.appendNodeList(insideObjects, newRepos);
2085
2086                //place repos
2087
dataNode.getParentNode().appendChild(newRepos);
2088
2089                //remove relation and inside objects
2090
dataNode.getParentNode().removeChild(dataNode);
2091            }
2092
2093            break;
2094        }
2095
2096        case WizardCommand.UPDATE_ITEM: {
2097            // update an item - replaces all fields of the item with updated values
2098
// retrieved from MMbase
2099
// The command parameters is a value indicating the number of the node(s) to update.
2100
String JavaDoc value = cmd.getValue();
2101            NodeList nodesToUpdate = Utils.selectNodeList(data, ".//*[@number='" + value + "']");
2102            NodeList originalNodesToUpdate = Utils.selectNodeList(originalData, ".//*[@number='" + value + "']");
2103
2104            if ((nodesToUpdate != null) || (originalNodesToUpdate != null)) {
2105                Node updatedNode = null;
2106
2107                try {
2108                    updatedNode = databaseConnector.getDataNode(null, value, null);
2109                } catch (Exception JavaDoc e) {
2110                    // hm?
2111
break;
2112                }
2113
2114                NodeList updatedFields = Utils.selectNodeList(updatedNode, "./field");
2115
2116                Map fieldValues = new HashMap();
2117
2118                for (int j = 0; j < updatedFields.getLength(); j++) {
2119                    Node fieldNode = updatedFields.item(j);
2120                    String JavaDoc fieldName = Utils.getAttribute(fieldNode, "name");
2121                    String JavaDoc fieldValue = Utils.getText(fieldNode);
2122                    fieldValues.put(fieldName, fieldValue);
2123                }
2124
2125                NodeList insideObjects = Utils.selectNodeList(updatedNode, "*");
2126
2127                for (int i = 0; i < nodesToUpdate.getLength(); i++) {
2128                    Node dataNode = nodesToUpdate.item(i);
2129                    NodeList fieldsToUpdate = Utils.selectNodeList(dataNode, "./field");
2130
2131                    for (int j = 0; j < fieldsToUpdate.getLength(); j++) {
2132                        Node fieldNode = fieldsToUpdate.item(j);
2133                        String JavaDoc fieldName = Utils.getAttribute(fieldNode, "name");
2134                        String JavaDoc fieldValue = (String JavaDoc) fieldValues.get(fieldName);
2135                        Utils.storeText(fieldNode, fieldValue);
2136                    }
2137                }
2138
2139                for (int i = 0; i < originalNodesToUpdate.getLength(); i++) {
2140                    Node dataNode = originalNodesToUpdate.item(i);
2141                    NodeList fieldsToUpdate = Utils.selectNodeList(dataNode, "./field");
2142
2143                    for (int j = 0; j < fieldsToUpdate.getLength(); j++) {
2144                        Node fieldNode = fieldsToUpdate.item(j);
2145                        String JavaDoc fieldName = Utils.getAttribute(fieldNode, "name");
2146                        String JavaDoc fieldValue = (String JavaDoc) fieldValues.get(fieldName);
2147                        Utils.storeText(fieldNode, fieldValue);
2148                    }
2149                }
2150            }
2151
2152            break;
2153        }
2154
2155        case WizardCommand.MOVE_UP:
2156        case WizardCommand.MOVE_DOWN: {
2157            // This is in fact a SWAP action (swapping the order-by fieldname), not really move up or down.
2158
// The command parameters are the fid of the list in which the item falls (determines order),
2159
// and the did's of the nodes that are to be swapped.
2160
String JavaDoc fid = cmd.getFid();
2161            String JavaDoc did = cmd.getDid();
2162            String JavaDoc otherdid = cmd.getParameter(2);
2163
2164            // Step one: get the fieldname to swap
2165
// this fieldname is determined by checking the 'orderby' attribute in a list
2166
// If there is no orderby attribute, you can't swap (there is no order defined),
2167
// so nothing happens.
2168
Node parentnode = Utils.selectSingleNode(schema,
2169                                                     ".//*[@fid='" + fid + "']");
2170            String JavaDoc orderby = Utils.getAttribute(parentnode.getParentNode(),
2171                                                "orderby");
2172
2173            // step 2: select the nodes and their fieldfs (provide dthey have them)
2174
// and swap the values.
2175
// when the list is sorted again the order of the nodes will be changed
2176
if (orderby != null) {
2177                // if orderby is only a field name, create an xpath
2178
if (orderby.indexOf('@') == -1) {
2179                    orderby = "object/field[@name='" + orderby + "']";
2180                }
2181
2182                log.debug("swap " + did + " and " + otherdid + " on " + orderby);
2183
2184                Node datanode = Utils.selectSingleNode(data,
2185                                                       ".//*[@did='" + did + "']/" + orderby);
2186
2187                if (datanode != null) {
2188                    // find other datanode
2189
Node othernode = Utils.selectSingleNode(data,
2190                                                            ".//*[@did='" + otherdid + "']/" + orderby);
2191
2192                    // now we gotta swap the value of them nodes.. (must be strings).
2193
if (othernode != null) {
2194                        String JavaDoc datavalue = Utils.getText(datanode);
2195                        String JavaDoc othervalue = Utils.getText(othernode);
2196                        Utils.storeText(othernode, datavalue);
2197                        Utils.storeText(datanode, othervalue);
2198                    }
2199                }
2200            }
2201
2202            break;
2203        }
2204
2205        case WizardCommand.START_WIZARD: {
2206            // this involves a redirect and is handled by the jsp pages
2207
startWizard = true;
2208            startWizardCmd = cmd;
2209
2210            break;
2211        }
2212
2213        case WizardCommand.GOTO_FORM: {
2214            // The command parameters is the did of the form to jump to.
2215
// note that a fid parameter is expected in the command syntax but ignored
2216
currentFormId = cmd.getDid();
2217
2218            break;
2219        }
2220
2221        case WizardCommand.ADD_ITEM: {
2222            // The command parameters are the fid of the list in which the item need be added,
2223
// the did of the object under which it should be added (the parent node),
2224
// and a second id, indicating the object id to add.
2225
// The second id can be passed either as a paremeter (the 'otherdid' parameter), in
2226
// which case it involves a newly created item, OR as a value, in which case it is an
2227
// enumerated list of did's, the result of a search.
2228
//
2229
String JavaDoc fid = cmd.getFid();
2230            String JavaDoc did = cmd.getDid();
2231            String JavaDoc value = cmd.getValue();
2232
2233            if (log.isDebugEnabled()) {
2234                log.debug("Adding item fid: " + fid + " did: " + did + " value: " + value);
2235            }
2236
2237            if ((value != null) && !value.equals("")) {
2238                log.debug("no value");
2239
2240                int createOrder = 1;
2241                StringTokenizer ids = new StringTokenizer(value, "|");
2242
2243                while (ids.hasMoreElements()) {
2244                    Node newObject = addListItem(fid, did, ids.nextToken(), false, createOrder);
2245                    createOrder++;
2246                }
2247            } else {
2248                String JavaDoc otherdid = cmd.getParameter(2);
2249
2250                if (otherdid.equals("")) {
2251                    otherdid = null;
2252                }
2253
2254                Node newObject = addListItem(fid, did, otherdid, true, 1);
2255            }
2256
2257            break;
2258        }
2259
2260        case WizardCommand.CANCEL: {
2261            // This command takes no parameters.
2262
mayBeClosed = true;
2263
2264            break;
2265        }
2266        case WizardCommand.SAVE : {
2267            log.debug("Wizard " + objectNumber + " will be saved (but not closed)");
2268        }
2269
2270        case WizardCommand.COMMIT: {
2271            log.debug("Committing wizard " + objectNumber);
2272
2273            // This command takes no parameters.
2274
if (log.isDebugEnabled()) {
2275                log.debug("orig: " + Utils.stringFormatted(originalData));
2276                log.debug("new orig: " + Utils.stringFormatted(data));
2277            }
2278
2279            Element results = databaseConnector.put(originalData, data, binaries);
2280
2281            // find the (new) objectNumber and store it.
2282
String JavaDoc oldNumber = Utils.selectSingleNodeText(data, ".//object/@number", null);
2283
2284            // select the 'most outer' object.
2285
if (log.isDebugEnabled()) {
2286                log.trace("results : " + results);
2287                log.debug("found old number " + oldNumber);
2288            }
2289
2290            // in the result set the new objects are just siblings, so the new 'wizard number' must be found with this
2291
// xpath
2292
String JavaDoc newNumber = Utils.selectSingleNodeText(results, ".//object[@oldnumber='" + oldNumber + "']/@number", null);
2293
2294            if (log.isDebugEnabled()) {
2295                log.debug("found new wizard number " + newNumber);
2296            }
2297
2298            if (newNumber != null) {
2299                objectNumber = newNumber;
2300            }
2301
2302            committed = true;
2303            mayBeClosed = (cmd.getType() == WizardCommand.COMMIT);
2304
2305            if (!mayBeClosed) {
2306
2307                // if we continue editing the xml's documents should be fixed for the new situation.
2308
if (log.isDebugEnabled()) {
2309                    log.trace("Using data was " + Utils.getSerializedXML(originalData));
2310                    log.trace("is " + Utils.getSerializedXML(data));
2311                }
2312
2313                // copy data to original data.
2314
// makes sense but we don't get the new object numbers like that.
2315

2316
2317                if (newNumber != null) {
2318                    dataId = newNumber;
2319                }
2320
2321                // try reload:
2322
loadData();
2323
2324
2325            }
2326
2327            break;
2328        }
2329        default: {
2330            log.warn("Received an unknown wizard command '" + cmd.getValue() + "'");
2331        }
2332        }
2333    }
2334
2335    /**
2336     * This method adds a listitem. It is used by the #processCommand method to add new items to a list. (Usually when the
2337     * add-item command is fired.)
2338     * Note: this method can only add new relations and their destinations!.
2339     * For creating new objects, use WizardDatabaseConnector.createObject.
2340     *
2341     * @param listId the id of the proper list definition node, the list that issued the add command
2342     * @param subDataId The did (dataid) of the anchor (parent) where the new node should be created
2343     * @param destinationId The new destination
2344     * @param createOrder ordernr under which this item is added ()i.e. when adding more than one item to a
2345     * list using one add-item command). The first ordernr in a list is 1
2346     * @return The new relation.
2347     */

2348    private Node addListItem(String JavaDoc listId, String JavaDoc subDataId, String JavaDoc destinationId,
2349                             boolean isCreate, int createOrder) throws WizardException {
2350        log.debug("Adding list item");
2351
2352        // Determine which list issued the add-item command, so we can get the create code from there.
2353
Node listNode = Utils.selectSingleNode(schema, ".//list[@fid='" + listId + "']");
2354        Node relationDefinition = null;
2355
2356        // action=add is for search command
2357
if (!isCreate) {
2358            relationDefinition = Utils.selectSingleNode(listNode, "action[@type='add']/relation");
2359        }
2360
2361        // action=create is for create command
2362
// (this should be an 'else', but is supported for 'search' for old xsls)
2363
if (relationDefinition == null) {
2364            relationDefinition = Utils.selectSingleNode(listNode, "action[@type='create']/relation");
2365        }
2366
2367
2368        if (relationDefinition == null) { // still null?
2369
throw new WizardException("Could not find action (add or create) to add a item to list with id " + listId);
2370        }
2371
2372        relationDefinition = relationDefinition.cloneNode(true); // why is this necessary?
2373

2374        if (log.isDebugEnabled()) {
2375            log.debug("Creating object " + relationDefinition.getNodeName() + " type " + Utils.getAttribute(relationDefinition, "type"));
2376        }
2377
2378        // Put the value from the command in that object-definition.
2379
if (destinationId != null) {
2380            Utils.setAttribute(relationDefinition, "destination", destinationId);
2381        }
2382
2383        // We have to add the object to the data, so first determine to which parent it belongs.
2384
Node parent = Utils.selectSingleNode(data, ".//*[@did='" + subDataId + "']");
2385
2386
2387
2388        // Ask the database to create that object, and return it.
2389
Node newRelation = databaseConnector.createObject(data, parent, relationDefinition, variables, createOrder);
2390
2391        // reload the data, there may be sub-list-data to be reloaded.
2392
if (destinationId != null) {
2393            Node newRelatedNode = Utils.selectSingleNode(newRelation, "object");
2394            if (newRelatedNode != null) {
2395                String JavaDoc relatedType = Utils.selectSingleNodeText(newRelatedNode, "@type", null);
2396
2397                Node loadAction = Utils.selectSingleNode(schema.getDocumentElement(), "action[@type='load']/relation[@destination='" + relatedType + "']/object");
2398
2399                if (loadAction != null) {
2400                    Collection newSubRelations = databaseConnector.loadRelations(newRelatedNode, destinationId, loadAction);
2401                    // newly loaded objects must be marked as 'already-existing'.
2402

2403                    Iterator i = newSubRelations.iterator();
2404                    while (i.hasNext()) {
2405                        Node newSubRelation = (Node) i.next();
2406                        Utils.setAttribute(newSubRelation, "already-exists", "true");
2407                        NodeList newSubObjects = Utils.selectNodeList(newSubRelation, ".//object");
2408
2409                        for (int j = 0; j < newSubObjects.getLength(); j++) {
2410                            Node newSubObject = newSubObjects.item(j);
2411                            Utils.setAttribute(newSubObject, "already-exists", "true");
2412                        }
2413
2414                        NodeList newSubSubRelations = Utils.selectNodeList(newSubRelation, ".//relation");
2415                        for (int k = 0; k < newSubSubRelations.getLength(); k++) {
2416                            Node newSubSubRelation = newSubSubRelations.item(k);
2417                            Utils.setAttribute(newSubSubRelation, "already-exists", "true");
2418                        }
2419                    }
2420                } else {
2421                    log.debug("Nothing found to load");
2422                }
2423            } else {
2424
2425                throw new WizardException("Could not find relatednode " + Utils.getXML(newRelation));
2426            }
2427        }
2428        return newRelation;
2429
2430    }
2431
2432    /**
2433     * With this method you can store a binary in the wizard.
2434     *
2435     * @param did This is the dataid what points to in what field the binary should be stored, once commited.
2436     * @param bytes This is a bytearray with the data to be stored.
2437     * @param name This is the name which will be used to show what file is uploaded.
2438     * @param path The (local) path of the file placed.
2439     */

2440    public void setBinary(String JavaDoc did, byte[] bytes, String JavaDoc name, String JavaDoc path) {
2441        setBinary(did, bytes, name, path, null);
2442    }
2443
2444    /**
2445     *
2446     * @param type Content-type of the byte (or null). If not null, then the fields 'mimetype',
2447     * 'size' and 'filename' are filled as well.
2448     * @since MMBase-1.7.2
2449     */

2450    public void setBinary(String JavaDoc did, byte[] bytes, String JavaDoc name, String JavaDoc path, String JavaDoc type) {
2451        binaries.put(did, bytes);
2452        binaryNames.put(did, name);
2453        binaryPaths.put(did, path);
2454
2455        if (type != null) {
2456            Node mimetypeField = Utils.selectSingleNode(data, "//object[field/@did = '" + did + "']/field[@name='mimetype']");
2457            if (mimetypeField != null) {
2458                Utils.storeText(mimetypeField, type);
2459            }
2460            Node sizeField = Utils.selectSingleNode(data, "//object[field/@did = '" + did + "']/field[@name='size']");
2461            if (sizeField != null && bytes != null) {
2462                Utils.storeText(sizeField, "" + bytes.length);
2463            }
2464            Node fileNameField = Utils.selectSingleNode(data, "//object[field/@did = '" + did + "']/field[@name='filename']");
2465            if (fileNameField != null && name != null) {
2466                Utils.storeText(fileNameField, name);
2467            }
2468        }
2469
2470    }
2471
2472
2473
2474    /**
2475     * This method allows you to retrieve the data of a temporarily stored binary.
2476     *
2477     * @param did The dataid of the binary you want.
2478     * @return the binary data, if found.
2479     */

2480    public byte[] getBinary(String JavaDoc did) {
2481        return (byte[]) binaries.get(did);
2482    }
2483
2484    /**
2485     * With this method you can retrieve the binaryname of a placed binary.
2486     *
2487     * @param did The dataid of the binary you want.
2488     * @return The name as set when #setBinary was used.
2489     */

2490    public String JavaDoc getBinaryName(String JavaDoc did) {
2491        return (String JavaDoc) binaryNames.get(did);
2492    }
2493
2494    /**
2495     * With this method you can retrieve the binarypath of a placed binary.
2496     *
2497     * @param did The dataid of the binary you want.
2498     * @return The path as set when #setBinary was used.
2499     */

2500    public String JavaDoc getBinaryPath(String JavaDoc did) {
2501        return (String JavaDoc) binaryPaths.get(did);
2502    }
2503
2504    /**
2505     * This method stores binary data in the data, so that the information can be used by the html.
2506     * This method is called from the form creating code.
2507     *
2508     * @param fieldnode the fieldnode where the binary data information should be stored.
2509     */

2510    public void addBinaryData(Node fieldnode) {
2511        // add's information about the possible placed binaries in the fieldnode.
2512
// assumes this field is an binary-field
2513
String JavaDoc did = Utils.getAttribute(fieldnode, "did", null);
2514
2515        if (did != null) {
2516            byte[] binary = getBinary(did);
2517
2518            if (binary != null) {
2519                // upload
2520
Node binarynode = fieldnode.getOwnerDocument().createElement("upload");
2521                Utils.setAttribute(binarynode, "uploaded", "true");
2522                Utils.setAttribute(binarynode, "size", binary.length + "");
2523                Utils.setAttribute(binarynode, "name", getBinaryName(did));
2524
2525                String JavaDoc path = getBinaryPath(did);
2526                Utils.createAndAppendNode(binarynode, "path", path);
2527                fieldnode.appendChild(binarynode);
2528            }
2529        }
2530    }
2531
2532    /**
2533     * This method is used to merge MMBase specific constraints with the values placed in the Wizard definition.
2534     * For now, it checks requiredness and datatypes.
2535     *
2536     * It connects to the Dove, gets the constraints (from cache or not) and merges the values.
2537     *
2538     * @param fieldDef the fielddefinition as placed in the wizardschema (==definition)
2539     * @param fieldNode The fieldnode points to the datanode. (This is needed to find out what datatype this field is about).
2540     */

2541    public void mergeConstraints(Node fieldDef, Node fieldNode) {
2542        // load all constraints + merge them with the settings in the schema definition
2543
//
2544
if (fieldNode == null) {
2545            log.warn("Tried mergeContraints on fieldNode which is null. FielDef: " +
2546                     Utils.getXML(fieldDef));
2547        }
2548
2549        String JavaDoc objectType = Utils.getAttribute(fieldNode.getParentNode(), "type",
2550                                               null);
2551        String JavaDoc fieldName = Utils.getAttribute(fieldNode, "name", null);
2552
2553        if ((objectType == null) || (fieldName == null)) {
2554            if (log.isDebugEnabled()) {
2555                log.debug("wizard.mergeConstraints: objecttype or fieldname could not be retrieved for this field. Field:");
2556                log.debug(Utils.getXML(fieldNode));
2557            }
2558
2559            return;
2560        }
2561
2562        Node con = getConstraints(objectType, fieldName);
2563
2564        if (con == null) {
2565            return; // no constraints found. so forget it.
2566
}
2567
2568        String JavaDoc xmlSchemaType = null;
2569        String JavaDoc guiType = Utils.selectSingleNodeText(con, Dove.GUITYPE, "string/line");
2570        int pos = guiType.indexOf("/");
2571
2572        if (pos != -1) {
2573            xmlSchemaType = guiType.substring(0, pos);
2574            guiType = guiType.substring(pos + 1);
2575        }
2576
2577        String JavaDoc required = Utils.selectSingleNodeText(con, Dove.REQUIRED, "false");
2578        String JavaDoc guiName = Utils.selectSingleNodeText(con, Dove.GUINAME, "");
2579        String JavaDoc description = Utils.selectSingleNodeText(con, Dove.DESCRIPTION, "");
2580        String JavaDoc maxLength = Utils.selectSingleNodeText(con, Dove.MAXLENGTH, "-1");
2581
2582        // dttype?
2583
String JavaDoc ftype = Utils.getAttribute(fieldDef, "ftype", null);
2584        String JavaDoc dttype = Utils.getAttribute(fieldDef, "dttype", null);
2585        Node prompt = Utils.selectSingleNode(fieldDef, "prompt");
2586        Node descriptionTag = Utils.selectSingleNode(fieldDef, "description");
2587
2588        if (dttype == null) {
2589            // import xmlSchemaType (dttype)
2590
// note :
2591
// Dove currently returns the following XML Schema base types (and their possible constraints):
2592
// - string (minLength, maxLength)
2593
// - float
2594
// - double
2595
// - date
2596
// - time
2597
//
2598
// Dove returns the following XML Schema derived types:
2599
// - int
2600
// - long
2601
//
2602
// Dove returns the following Non-XML Schema conformant types:
2603
// - binary (minLength, maxLength)
2604
// - datetime
2605
// - boolean
2606
//
2607
// The 'binary' type can be defined as :
2608
// <xsd:simpleType name="binary">
2609
// <xsd:restriction base='anyURI' />
2610
// </xsd:simpleType>
2611
//
2612
// The 'datetime' type can be defined as :
2613
// <xsd:simpleType name="datetime">
2614
// <xsd:restriction base='dateTime' />
2615
// </xsd:simpleType>
2616
//
2617
// Finally, Dove may send other, non-standard, types, depending on the formation of guitype in the builder xml.
2618
//
2619
dttype = xmlSchemaType;
2620
2621            if (log.isDebugEnabled()) {
2622                log.debug("dttype was null, setting to " + xmlSchemaType);
2623            }
2624        }
2625
2626        if (ftype == null) {
2627            // import guitype or ftype
2628
// this is a qualifier, not a real type
2629
//
2630
ftype = guiType;
2631        }
2632
2633        // backward compatibility.
2634
// switch old 'upload' to 'binary'
2635
// The old format used the following convention:
2636
// ftype="upload" + dttype="image" -> upload an image
2637
// ftype="upload" + dttype="upload" -> upload a file
2638
// ftype="image" -> display an image
2639
// The new format usesd 'binary' a s a dftytype,a nd 'image' or 'file' as a ftype,
2640
// as follows:
2641
// ftype="image" + dttype="binary" -> upload an image
2642
// ftype="file" + dttype="binary" -> upload a file
2643
// ftype="image" + dttype="data" -> display an image
2644
// ftype="file" + dttype="data" -> display a link to a file
2645
// code below changes old format wizards to the new format.
2646
if ("upload".equals(ftype)) {
2647            if ("image".equals(dttype)) {
2648                ftype = "image";
2649                dttype = "binary";
2650            } else {
2651                ftype = "file";
2652                dttype = "binary";
2653            }
2654        } else if ("image".equals(ftype)) {
2655            // check if dttype is binary, else set to data
2656
if (!"binary".equals(dttype)) {
2657                dttype = "data";
2658            }
2659        }
2660
2661        // in the old format, ftype was date, while dttype was date,datetime, or time
2662
// In the new format, this is reversed (dttype contains the base datatype,
2663
// ftype the format in which to enter it)
2664
// since wizards only understand formats 'date', 'time', 'duartion' and 'datetime',
2665
// 'ftype' values of new date guitypes (such as new datatypes) need to be converted to
2666
// datetime
2667
if (!"data".equals(ftype)) {
2668            if ("date".equals(dttype) || "time".equals(dttype)) {
2669                ftype = dttype;
2670                dttype = "datetime";
2671            } else if ("datetime".equals(dttype) &&
2672                       (!"date".equals(ftype) && !"time".equals(ftype) && !"duration".equals(ftype))) {
2673                ftype = "datetime";
2674            }
2675        }
2676
2677        // in the old format, 'html' could also be assigned to dttype
2678
// in the new format this is an ftype (the dttype is string)
2679
if ("html".equals(dttype)) {
2680            ftype = "html";
2681            dttype = "string";
2682        }
2683
2684        // add guiname as prompt
2685
if (prompt == null) {
2686            Utils.createAndAppendNode(fieldDef, "prompt", guiName);
2687        }
2688
2689        // add description as helptext
2690
if (descriptionTag == null) {
2691            Utils.createAndAppendNode(fieldDef, "description", description);
2692        }
2693
2694        // process requiredness
2695
//
2696
String JavaDoc dtrequired = Utils.getAttribute(fieldDef, "dtrequired", null);
2697
2698        if (dtrequired == null) {
2699            // if unknown, determine requiredness according to MMBase
2700
dtrequired = required;
2701        }
2702
2703        // fix for old format type 'wizard'
2704
if ("wizard".equals(ftype)) {
2705            ftype = "startwizard";
2706        }
2707
2708        // store new attributes in fielddef
2709
Utils.setAttribute(fieldDef, "ftype", ftype);
2710        Utils.setAttribute(fieldDef, "dttype", dttype);
2711
2712        if (dtrequired != null) {
2713            Utils.setAttribute(fieldDef, "dtrequired", dtrequired);
2714        }
2715
2716        // process min/maxlength for strings
2717
if ("string".equals(dttype) || "html".equals(dttype)) {
2718            String JavaDoc dtminlength = Utils.getAttribute(fieldDef, "dtminlength", null);
2719
2720            if (dtminlength == null) {
2721                // manually set minlength if required is true
2722
if ("true".equals(dtrequired)) {
2723                    Utils.setAttribute(fieldDef, "dtminlength", "1");
2724                }
2725            }
2726
2727            String JavaDoc dtmaxlength = Utils.getAttribute(fieldDef, "dtmaxlength", null);
2728
2729            if (dtmaxlength == null) {
2730                int maxlen = -1;
2731
2732                try {
2733                    maxlen = Integer.parseInt(maxLength);
2734                } catch (NumberFormatException JavaDoc e) {
2735                }
2736
2737                // manually set maxlength if given
2738
// ignore sizes smaller than 1
2739
if (maxlen > 0) {
2740                    Utils.setAttribute(fieldDef, "dtmaxlength", "" + maxlen);
2741                }
2742            }
2743        }
2744    }
2745
2746    /**
2747     * This method gets the MMBase constraints.
2748     *
2749     * @param objecttype The name of the object, eg. images, jumpers, urls, news
2750     */

2751    public Node getConstraints(String JavaDoc objecttype) {
2752        return getConstraints(objecttype, null);
2753    }
2754
2755    /**
2756     * This method gets the MMBase constraints. It also handles the internal constraints cache.
2757     *
2758     * @param objecttype The name of the object, eg. images, jumpers, urls, news
2759     * @param fieldname The name of the field, eg. title, body, start
2760     */

2761    public Node getConstraints(String JavaDoc objecttype, String JavaDoc fieldname) {
2762        // check if constraints are in repository, if so, return thatone,
2763
// otherwise, retrieve+store+and return the contraints received from the Dove.
2764
Node con = Utils.selectSingleNode(constraints,
2765                                          "/*/getconstraints[@type='" + objecttype + "']");
2766
2767        if (con == null) {
2768            // objecttype not in repository. Load from MMBase.
2769
try {
2770                con = databaseConnector.getConstraints(objecttype);
2771            } catch (Exception JavaDoc e) {
2772                log.error(Logging.stackTrace(e));
2773
2774                return null;
2775            }
2776
2777            if (con == null) {
2778                // something is wrong.
2779
log.debug("wizard.getConstraints: Could not retrieve MMBase constraints for objecttype:" + objecttype);
2780
2781                return null;
2782            }
2783
2784            // store in repository.
2785
con = constraints.importNode(con.cloneNode(true), true);
2786            constraints.getDocumentElement().appendChild(con);
2787        }
2788
2789        // no fieldname supplied? return total info node..
2790
if (fieldname == null) {
2791            return con;
2792        }
2793
2794        // find field declaration
2795
Node fieldcon = Utils.selectSingleNode(con, "fields/field[@name='" + fieldname + "']");
2796
2797        return fieldcon;
2798    }
2799
2800    class OrderByComparator implements Comparator {
2801        boolean compareByNumber = false;
2802
2803        OrderByComparator(String JavaDoc ordertype) {
2804            compareByNumber = ordertype.equals("number");
2805        }
2806
2807        public int compare(Object JavaDoc o1, Object JavaDoc o2) {
2808            Element n1 = (Element) o1;
2809            Element n2 = (Element) o2;
2810
2811            // Determine the orderby values and compare
2812
// store it??
2813
String JavaDoc order1 = n1.getAttribute("orderby");
2814            String JavaDoc order2 = n2.getAttribute("orderby");
2815
2816            //this means it we want evaludate the value as a number
2817
if (compareByNumber) {
2818                try {
2819                    return Double.valueOf(order1).compareTo(Double.valueOf(order2));
2820                } catch (Exception JavaDoc e) {
2821                    log.error("Invalid field values (" + order1 + "/" + order2 + "):" + e);
2822
2823                    return 0;
2824                }
2825            } else {
2826                return order1.compareToIgnoreCase(order2);
2827            }
2828        }
2829    }
2830
2831    /**
2832     * Caches objectNumber to Node.
2833     * @since MMBase-1.6.4
2834     */

2835    static class NodeCache extends Cache {
2836        NodeCache() {
2837            super(100);
2838        }
2839
2840        public String JavaDoc getName() {
2841            return "nodes";
2842        }
2843
2844        public String JavaDoc getDescription() {
2845            return "objectNumber -> Node";
2846        }
2847    }
2848
2849    /**
2850     * Caches File to Editwizard schema Document.
2851     * @since MMBase-1.6.4
2852     */

2853    private static class WizardSchemaCache extends Cache {
2854        WizardSchemaCache() {
2855            super(100);
2856        }
2857
2858        public String JavaDoc getName() {
2859            return "Editwizard schemas";
2860        }
2861
2862        public String JavaDoc getDescription() {
2863            return "File -> Editwizard schema Document (resolved includes/shortcuts)";
2864        }
2865
2866        synchronized public Object JavaDoc put(URL JavaDoc f, Document doc, List dependencies) {
2867            Object JavaDoc retval = super.get(f);
2868
2869            if (retval != null) {
2870                return retval;
2871            }
2872
2873            return super.put(f, new Entry(f, doc, dependencies));
2874        }
2875
2876        synchronized public Object JavaDoc remove(Object JavaDoc key) {
2877            URL JavaDoc file = (URL JavaDoc) key;
2878            Entry entry = (Entry) get(file);
2879
2880            if ((entry != null) && (entry.fileWatcher != null)) {
2881                entry.fileWatcher.exit();
2882            } else {
2883                log.warn("entry: " + entry);
2884            }
2885
2886            return super.remove(key);
2887        }
2888
2889        synchronized public Document getDocument(URL JavaDoc key) {
2890            Entry entry = (Entry) super.get(key);
2891
2892            if (entry == null) {
2893                return null;
2894            }
2895
2896            return entry.doc;
2897        }
2898
2899        private class Entry {
2900            Document doc; // the document.
2901
URL JavaDoc file; //the file belonging to this document (key of cache)
2902

2903            /**
2904             * Cache entries must be invalidated if (one of the) file(s) changes.
2905             */

2906            ResourceWatcher fileWatcher = new ResourceWatcher(ResourceLoader.getWebRoot()) {
2907                    public void onChange(String JavaDoc u) {
2908                        // invalidate this cache entry
2909
WizardSchemaCache.this.remove(Entry.this.file);
2910                        // stop watching files
2911
}
2912                };
2913
2914            Entry(URL JavaDoc f, Document doc, List dependencies) {
2915                this.file = f;
2916                this.doc = doc;
2917                fileWatcher.add(f);
2918
2919                Iterator i = dependencies.iterator();
2920
2921                while (i.hasNext()) {
2922                    URL JavaDoc ff = (URL JavaDoc) i.next();
2923                    fileWatcher.add(ff);
2924                }
2925
2926                fileWatcher.setDelay(10 * 1000); // check every 10 secs
2927
fileWatcher.start();
2928            }
2929        }
2930    }
2931}
2932
Popular Tags