View Javadoc

1   package com.explosion.expfmodules.wizard.standard.load;
2   
3   
4   /* =============================================================================
5    *       
6    *     Copyright 2004 Stephen Cowx
7    *
8    *     Licensed under the Apache License, Version 2.0 (the "License");
9    *     you may not use this file except in compliance with the License.
10   *     You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   *     Unless required by applicable law or agreed to in writing, software
15   *     distributed under the License is distributed on an "AS IS" BASIS,
16   *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   *     See the License for the specific language governing permissions and
18   *     limitations under the License.
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         /* Get the data items */
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             /* Extract various information about the Data Item */
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             /* Construct an initial PreferenceDataItem */
232             PreferenceDataItem item = createPreferenceDataItem(preferenceStore, xmlDataItem, name, typeName, defaultValue);
233             
234             /* Long name */
235             item.getPreference().setLongName(label);
236             
237             /* mandatory */
238             item.setMandatory(GeneralUtils.getLenientBoolean(mandatory));
239             
240             /* Sort order number */
241             item.setSortOrderNumber(sortOrderNumber);
242             
243             /* Visible */
244             if (visible != null)  {  item.getPreference().setVisible(GeneralUtils.getLenientBoolean(visible));  }
245             
246             /* default */
247             if (defaultValue == null) { defaultValue = ""; }
248             
249             /* Attributes */
250             Map attributes = getAttributes(xmlDataItem);
251             item.setAttributes(attributes);
252             
253             /* Add it to the list of data items */
254             dataItemsList.add(item);
255             dataItemsMap.put(item.getPreference().getUniqueIdentifier(), item);
256         }
257         
258         //Yes, this is a little bit clunky
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         /* Find out if it is a collection */
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         /* Check to see if this step exists */
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         /* Get the data items */
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 	            /* Extract various information about the Data Item */
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         /* Extract the values for this data item */
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         /* Extract the values for this data item */
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 }