1 package com.explosion.expfmodules.wizard.standard.load;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import java.io.Reader;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29
30 import org.dom4j.Document;
31 import org.dom4j.DocumentException;
32 import org.dom4j.Element;
33 import org.dom4j.io.SAXReader;
34
35 import com.explosion.expfmodules.wizard.Condition;
36 import com.explosion.expfmodules.wizard.Step;
37 import com.explosion.expfmodules.wizard.StepAction;
38 import com.explosion.expfmodules.wizard.StepView;
39 import com.explosion.expfmodules.wizard.Wizard;
40 import com.explosion.expfmodules.wizard.standard.PreferenceDataItem;
41 import com.explosion.expfmodules.wizard.standard.StandardStep;
42 import com.explosion.expfmodules.wizard.standard.StandardStepDefinition;
43 import com.explosion.expfmodules.wizard.standard.StandardWizard;
44 import com.explosion.utilities.GeneralUtils;
45 import com.explosion.utilities.preferences.Preference;
46
47
48 /***
49 * @author Steve.Cowx
50 *
51 * This class loads a wizard definition from a file and
52 * instantiates and initialises a wizard object
53 * from the definition it has loaded
54 */
55 public class WizardDefinitionLoader {
56
57
58
59
60
61 private static final String VISIBLE = "Visible";
62 private static final String MANDATORY = "Mandatory";
63 private static final String ACTIONCLASS = "ActionClass";
64 private static final String VIEWCLASS = "ViewClass";
65 private static final String NAME_VALUE = "Value";
66 private static final String NAME_VALUES = "Values";
67 private static final String NAME_DATAITEM = "DataItem";
68 private static final String NAME_DEFAULTVALUE = "DefaultValue";
69 private static final String NAME_LABEL = "Label";
70 private static final String NAME_SORTORDERNUMBER = "SortOrderNumber";
71 private static final String NAME_TYPE = "Type";
72 private static final String NAME_DATAITEMS = "DataItems";
73 private static final String NAME_STEP = "Step";
74 private static final String NAME_STEPS = "OutboundSteps";
75 private static final String NAME_ICON = "Icon";
76 private static final String NAME_NOTES = "Notes";
77 private static final String NAME_ID = "Id";
78 private static final String NAME_HELP = "Help";
79 private static final String NAME_STEPDEFINITION = "StepDefinition";
80 private static final String NAME_STEPDEFINITIONS = "StepDefinitions";
81 private static final String NAME_VERSION = "Version";
82 private static final String NAME_NAME = "Name";
83 private static final String NAME_WIZARD = "Wizard";
84
85 private static org.apache.log4j.Logger log = org.apache.log4j.LogManager.getLogger(WizardDefinitionLoader.class);
86 private StandardWizard wizard;
87 private Map stepDefinitions = new HashMap();
88
89 /***
90 * Loads and returns a Wizard object from the stream privided
91 * @param reader
92 * @return
93 * @throws Exception
94 */
95 public Wizard getWizard(Reader reader) throws Exception
96 {
97 Document document = getDocument(reader);
98 Wizard wizard = parseDocument(document);
99 return wizard;
100 }
101
102
103 /***
104 * Loads a document from a stream
105 *
106 * @throw a org.dom4j.DocumentException occurs whenever the buildprocess fails.
107 */
108 private Document getDocument(Reader reader) throws DocumentException {
109 SAXReader xmlReader = new SAXReader();
110 Document doc = xmlReader.read(reader);
111 return doc;
112 }
113
114 /***
115 * Parses the document provided and instantiates a wizard from what it finds.
116 * @param document
117 * @return
118 * @throws Exception
119 */
120 private Wizard parseDocument(Document document) throws Exception
121 {
122 wizard = new StandardWizard();
123 List steps = new ArrayList();
124 Element root = document.getRootElement();
125 if (root.getName().equalsIgnoreCase(NAME_WIZARD))
126 {
127 processRoot(root);
128 }
129
130 return wizard;
131 }
132
133 /***
134 * This method processes the root element
135 * @param element
136 * @throws Exception
137 */
138 private void processRoot(Element element) throws Exception
139 {
140 wizard.setName(getValueOfFirstChildWithName(NAME_NAME, element, true));
141 wizard.setVersion(getValueOfFirstChildWithName(NAME_VERSION, element, true));
142
143 loadStepDefinitions(element, wizard);
144 wizard.setStepDefinitions(stepDefinitions);
145
146 wizard.setFirstStep(this.getFirstStep(element));
147 }
148
149 /***
150 * This method parses the StepDefinitions seciton of the Wizard document
151 * and returns a List of StepDefinition objects
152 * @param rootElement
153 * @return
154 * @throws Exception
155 */
156 private void loadStepDefinitions(Element rootElement, Wizard wizard) throws Exception
157 {
158 Element steps = rootElement.element(NAME_STEPDEFINITIONS);
159 if (steps == null)
160 throw new ExpectedElementNotFoundException("Expected to find element with name 'StepDefinitions' as a child of '"+rootElement.getPath()+"' but did not.");
161
162 for (Iterator it = steps.elementIterator(NAME_STEPDEFINITION); it.hasNext();)
163 {
164 Element xmlStep = (Element) it.next();
165 StandardStepDefinition step = new StandardStepDefinition();
166
167 step.setName(getValueOfFirstChildWithName(NAME_NAME, xmlStep, true));
168 step.setHelp(getValueOfFirstChildWithName(NAME_HELP, xmlStep, false));
169 step.setId(getValueOfFirstChildWithName(NAME_ID,xmlStep,true));
170 step.setNotes(getValueOfFirstChildWithName(NAME_NOTES, xmlStep, false));
171 step.setNotes(getValueOfFirstChildWithName(NAME_NOTES, xmlStep, false));
172 String icon = getValueOfFirstChildWithName(NAME_ICON, xmlStep, false);
173 if (icon != null && icon.trim().length() > 0)
174 step.setIcon(icon);
175
176 String viewCLass = getValueOfFirstChildWithName(VIEWCLASS, xmlStep, false);
177 if (viewCLass != null && viewCLass.trim().length() > 0)
178 step.setView((StepView) Class.forName(viewCLass).newInstance());
179
180 String actionClass = getValueOfFirstChildWithName(ACTIONCLASS, xmlStep, false);
181 if (actionClass != null && actionClass.trim().length() > 0)
182 step.setAction((StepAction) Class.forName(actionClass).newInstance());
183
184 Object[] dataItems = getDataItems( xmlStep);
185 if (dataItems != null)
186 {
187 step.setDataItems((Map)dataItems[0]);
188 }
189
190 step.setWizard(wizard);
191
192 if (stepDefinitions.get(step.getId()) != null)
193 {
194 throw new StepAlreadyExistsException("A step with the ID "+step.getId()+" has already been defined in this wizard.");
195 }
196 stepDefinitions.put(step.getId(),step);
197 }
198 }
199
200 /***
201 * @param rootElement
202 * @param steps
203 * @param xmlStep
204 * @throws ExpectedElementNotFoundException
205 * @throws InvalidDataForElementException
206 * @throws Exception
207 */
208 private Object[] getDataItems( Element xmlStep) throws ExpectedElementNotFoundException, InvalidDataForElementException, Exception
209 {
210
211 Element dataItems = xmlStep.element(NAME_DATAITEMS);
212 if (dataItems == null)
213 return null;
214
215 Map dataItemsMap = new HashMap();
216 Map preferenceStore = new HashMap();
217 List dataItemsList = new ArrayList();
218 for (Iterator ditems = dataItems.elementIterator(NAME_DATAITEM); ditems.hasNext();)
219 {
220 Element xmlDataItem = (Element) ditems.next();
221
222
223 String name = getValueOfFirstChildWithName(NAME_NAME, xmlDataItem, true);
224 String typeName = getValueOfFirstChildWithName(NAME_TYPE, xmlDataItem, true);
225 String label = getValueOfFirstChildWithName(NAME_LABEL, xmlDataItem, true);
226 String mandatory = getValueOfFirstChildWithName(MANDATORY, xmlDataItem, false);
227 String visible = getValueOfFirstChildWithName(VISIBLE, xmlDataItem, false);
228 int sortOrderNumber = getIntValue(xmlDataItem, NAME_SORTORDERNUMBER);
229 String defaultValue = getValueOfFirstChildWithName(NAME_DEFAULTVALUE, xmlDataItem, false);
230
231
232 PreferenceDataItem item = createPreferenceDataItem(preferenceStore, xmlDataItem, name, typeName, defaultValue);
233
234
235 item.getPreference().setLongName(label);
236
237
238 item.setMandatory(GeneralUtils.getLenientBoolean(mandatory));
239
240
241 item.setSortOrderNumber(sortOrderNumber);
242
243
244 if (visible != null) { item.getPreference().setVisible(GeneralUtils.getLenientBoolean(visible)); }
245
246
247 if (defaultValue == null) { defaultValue = ""; }
248
249
250 Map attributes = getAttributes(xmlDataItem);
251 item.setAttributes(attributes);
252
253
254 dataItemsList.add(item);
255 dataItemsMap.put(item.getPreference().getUniqueIdentifier(), item);
256 }
257
258
259 Object[] lists = new Object[2];
260 lists[0] = dataItemsMap;
261 lists[1] = dataItemsList;
262
263 return lists;
264 }
265
266
267 /***
268 * Constructs a Preference Data Item
269 * @param preferenceStore
270 * @param xmlDataItem
271 * @param name
272 * @param typeName
273 * @param defaultValue
274 * @return
275 * @throws InvalidDataItemTypeException
276 * @throws Exception
277 */
278 private PreferenceDataItem createPreferenceDataItem(Map preferenceStore, Element xmlDataItem, String name, String typeName, String defaultValue) throws InvalidDataItemTypeException, Exception
279 {
280 String collectionTypeName = null;
281
282
283 int indexOfDash = typeName.indexOf("-");
284 if (indexOfDash > 0)
285 {
286 collectionTypeName = typeName.substring(indexOfDash+1);
287 typeName = typeName.substring(0,indexOfDash);
288 }
289
290 int collectionType = -1;
291 int type = Preference.getTypeForName(typeName);
292 if (type < 0)
293 throw new InvalidDataItemTypeException("'"+typeName+"' is not a valid type for data items " +
294 "in this wizard. Please check value of element 'Type' in " + xmlDataItem.getPath() +
295 ". Valid types are string, file, directory, int, float, color, choice, int choice, boolean, font, encrypted, collection.");
296
297 if (collectionTypeName != null)
298 {
299 collectionType = Preference.getTypeForName(collectionTypeName);
300 if (type < 0)
301 throw new InvalidDataItemTypeException("'"+typeName+"' is not a valid type for data items " +
302 "in this wizard. Please check value of element 'Type' in " + xmlDataItem.getPath() +
303 ". Valid types are string, file, directory, int, float, color, choice, int choice, boolean, font, encrypted, collection.");
304 }
305
306 Element valuesElements;
307 List values = getValues(xmlDataItem);
308 PreferenceDataItem item = null;
309
310 if (type == Preference.PROPERTY_TYPE_COLLECTION)
311 {
312 item = new PreferenceDataItem(name, type, collectionType, defaultValue, values, preferenceStore);
313 }
314 else
315 {
316 item = new PreferenceDataItem(name, type, defaultValue, values, preferenceStore);
317 }
318 return item;
319 }
320
321
322 /***
323 * Returns the first step in this wizard with refrences to all of the pther steps leading out from it
324 * @param rootElement
325 * @return
326 * @throws StepDefinitionNotFoundException
327 * @throws ExpectedElementNotFoundException
328 * @throws InvalidDataForElementException
329 * @throws InvalidConditionClassException
330 * @throws StepAlreadyExistsException
331 */
332 private Step getFirstStep(Element rootElement) throws StepDefinitionNotFoundException, ExpectedElementNotFoundException, InvalidDataForElementException, InvalidConditionClassException
333 {
334 Element firstStepElement = rootElement.element(NAME_STEP);
335 if (firstStepElement == null)
336 throw new ExpectedElementNotFoundException("Expected to find element with name 'Step' as a child of '"+rootElement.getPath()+"' but did not.");
337
338 StandardStep step = createStep(firstStepElement);
339 return step;
340 }
341
342 /***
343 * Recursive method which finds all of the outbound routes of a step
344 * @param rootElement
345 * @return
346 * @throws ExpectedElementNotFoundException
347 * @throws InvalidDataForElementException
348 * @throws InvalidConditionClassException
349 * @throws Exception
350 */
351 private List getSteps(Element stepElement) throws StepDefinitionNotFoundException, ExpectedElementNotFoundException, InvalidDataForElementException, InvalidConditionClassException
352 {
353 Element steps = stepElement.element(NAME_STEPS);
354 if (steps != null)
355 {
356 List list = new ArrayList();
357 for (Iterator it = steps.elementIterator(NAME_STEP); it.hasNext();)
358 {
359 list.add(createStep((Element)it.next()));
360 }
361 return list;
362 }
363 return null;
364 }
365
366 /***
367 * @param element
368 * @return
369 * @throws ExpectedElementNotFoundException
370 * @throws InvalidDataForElementException
371 * @throws StepDefinitionNotFoundException
372 * @throws InvalidConditionClassException
373 */
374 private StandardStep createStep(Element element) throws ExpectedElementNotFoundException, InvalidDataForElementException, StepDefinitionNotFoundException, InvalidConditionClassException
375 {
376 String stepId = getValueOfFirstChildWithName("StepDefinitionId",element,true);
377
378
379 StandardStepDefinition stepDefinition = (StandardStepDefinition) stepDefinitions.get(stepId);
380 if (stepDefinition == null)
381 throw new StepDefinitionNotFoundException("Invalid reference to a StepDefinition from a step. StepDefinition with id "+stepId+" does not exist.");
382
383 StandardStep step = new StandardStep();
384 step.setStepDefinition(stepDefinition);
385 step.setOutboundSteps(getSteps(element));
386 step.setCondition(getCondition(element));
387 return step;
388 }
389
390 /***
391 * This method parses out the condition for a particaular step.
392 * @param xmlStep
393 * @return
394 * @throws ExpectedElementNotFoundException
395 * @throws InvalidDataForElementException
396 * @throws Exception
397 */
398 private Condition getCondition( Element xmlStep) throws ExpectedElementNotFoundException, InvalidDataForElementException, InvalidConditionClassException
399 {
400
401 Element conditionElement = xmlStep.element("Condition");
402 if (conditionElement == null)
403 return null;
404
405 String conditionClassName = getValueOfFirstChildWithName("ClassName", conditionElement, true);
406 Condition condition;
407 try
408 {
409 condition = (Condition) Class.forName(conditionClassName).newInstance();
410 }
411 catch (Exception e)
412 {
413 throw new InvalidConditionClassException("Invalid condition class '"+conditionClassName+"'.");
414 }
415
416 Element dataItemsElement = conditionElement.element("DataItems");
417 if (dataItemsElement != null)
418 {
419 Map dataItemsMap = new HashMap();
420
421 for (Iterator itemsIterator = dataItemsElement.elementIterator("DataItem"); itemsIterator.hasNext();)
422 {
423 Element dataItem = (Element) itemsIterator.next();
424
425
426 String dataItemName = getValueOfFirstChildWithName("Name", dataItem, true);
427 String dataItemValue = getValueOfFirstChildWithName("Value", dataItem, true);
428 dataItemsMap.put(dataItemName, dataItemValue);
429 }
430 condition.setDataItems(dataItemsMap);
431 }
432 return condition;
433 }
434
435 /***
436 * Returns the values nested inside this data item
437 * @param steps
438 * @param ditems
439 * @param xmlDataItem
440 */
441 private List getValues(Element xmlDataItem)
442 {
443
444 Element valuesElements = xmlDataItem.element(NAME_VALUES);
445 List values = null;
446 if (valuesElements != null)
447 {
448 values = new ArrayList();
449 for (Iterator vals = valuesElements.elementIterator(NAME_VALUE); vals.hasNext();)
450 {
451 Element val = (Element) vals.next();
452 values.add(val.getTextTrim());
453 }
454 }
455 return values;
456 }
457
458 /***
459 * Returns the values nested inside this data item
460 * @param steps
461 * @param ditems
462 * @param xmlDataItem
463 * @throws ExpectedElementNotFoundException
464 */
465 private Map getAttributes(Element xmlDataItem) throws ExpectedElementNotFoundException
466 {
467
468 Element attributesElement = xmlDataItem.element("Attributes");
469 Map attributes = null;
470 if (attributesElement != null)
471 {
472 attributes = new HashMap();
473 for (Iterator vals = attributesElement.elementIterator("Attribute"); vals.hasNext();)
474 {
475 Element attributeElement = (Element) vals.next();
476 String name = this.getValueOfFirstChildWithName("Name",attributeElement,true);
477 String value = this.getValueOfFirstChildWithName("Value",attributeElement,true);
478 attributes.put(name, value);
479 }
480 }
481 return attributes;
482 }
483
484 /***
485 * @param rootElement
486 * @param xmlStep
487 * @param step
488 * @throws ExpectedElementNotFoundException
489 */
490 private int getIntValue(Element element, String name) throws ExpectedElementNotFoundException, InvalidDataForElementException
491 {
492 try
493 {
494 return new Integer(getValueOfFirstChildWithName(name, element, true)).intValue();
495 }
496 catch (NumberFormatException e)
497 {
498 throw new InvalidDataForElementException("Invalid number for '"+name+"' for element " + element.getPath(),e );
499 }
500 }
501
502 /***
503 * This method returns a string value representing the text for the element with the given name which is a child of the
504 * element provided.
505 * @param String the name of the child element sought
506 * @param The parent element
507 * @param If true and Element does not have a child with name name then an exceptio is thrown. If false and the same is true, then null is returned.
508 * @return String
509 * @throws ExpectedElementNotFoundException
510 */
511 private String getValueOfFirstChildWithName(String name, Element element, boolean expectedChild) throws ExpectedElementNotFoundException
512 {
513 String text = element.elementTextTrim(name);
514 if (expectedChild && text == null)
515 throw new ExpectedElementNotFoundException("Expected to find element with name '"+name+"' as a child of '"+element.getPath()+"' but did not.");
516 else
517 return text;
518 }
519
520 }