View Javadoc

1   package com.sri.emo.wizard.completion;
2   
3   import com.jcorporate.expresso.core.controller.Transition;
4   import com.jcorporate.expresso.core.db.DBException;
5   import com.jcorporate.expresso.core.dbobj.ValidValue;
6   import com.sri.common.dbobj.ObjectNotFoundException;
7   import com.sri.emo.controller.CompletionWizardAction;
8   import com.sri.emo.dbobj.*;
9   import com.sri.emo.wizard.Wizard;
10  import com.sri.emo.wizard.WizardException;
11  import com.sri.emo.wizard.completion.model.CompletionBean;
12  import com.sri.emo.wizard.completion.model.CompletionPartsBean;
13  import com.sri.emo.wizard.completion.model.FieldCompletion;
14  import com.sri.emo.wizard.defaults.EmoWizardMetadata;
15  import com.sri.emo.wizard.defaults.EmoWizardPage;
16  import com.sri.emo.wizard.expressoimpl.WizardController;
17  
18  import java.io.Serializable;
19  import java.util.*;
20  
21  /***
22   * Constructs Completion Wizards.
23   *
24   * @author Michael Rimov
25   * @version 1.0
26   */
27  public class EmoCompletionFactory extends CompletionBuilder {
28  
29  
30      /***
31       * A sorted Set of special ids.
32       */
33      private SortedSet preMultiAttributeStepIds = new TreeSet();
34  
35      /***
36       * Initial prestep id must be lower than any special steps in the EmoCompletionWizard
37       */
38      private static final int INITIAL_PRESTEP_ID = -3;
39  
40      /***
41       * Constructs a given wizard.
42       *
43       * @param completionRepository the CompletionRepository to draw the various
44       *                             wizards from.
45       */
46      public EmoCompletionFactory(final CompletionRepository completionRepository) {
47          super(completionRepository);
48      }
49  
50      /***
51       * Override of base class to change controller
52       *
53       * @param id int
54       * @return Transition
55       */
56      protected Transition commonTransitionConstruction(final int id) {
57          Transition t = new Transition();
58          t.setControllerObject(CompletionWizardAction.class);
59          t.addParam(WizardController.WIZ_PARAMETER_ID, Integer.toString(id));
60          return t;
61      }
62  
63  
64      /***
65       * Constructs the wizard itself.
66       *
67       * @param definition the WizDefinition dbobject for this wizard.
68       * @param steps      the List of steps that were constructed previously in
69       *                   the constructSteps function.
70       * @return fully constructed Wizard instance.
71       * @throws com.jcorporate.expresso.core.db.DBException
72       *          upon database exception error.
73       */
74      protected Wizard constructWizard(final WizDefinition definition, final List steps) throws DBException {
75          EmoCompletionWizard cwiz = (EmoCompletionWizard) super.constructWizard(definition, steps);
76  
77          CompletionBean bean = null;
78          try {
79              bean = repository.findById(cwiz.getId());
80          } catch (ObjectNotFoundException e) {
81              throw new DBException("Could not find underlying wizard of id: "
82                      + definition.getFieldInt(WizDefinition.FLD_ID), e);
83          }
84  
85          //Set turing constitution and 'dehydration' as well.
86          cwiz.setCompletionBean(bean);
87  
88          return cwiz;
89      }
90  
91      /***
92       * Constructs a new wizard instance and returns it.
93       *
94       * @return Wizard instance
95       * @throws WizardException upon error building it.
96       */
97      public Wizard buildWizard() throws WizardException {
98          try {
99  
100             WizDefinition wizard = this.getWizDefinition();
101 
102             List steps = constuctCompletionSteps();
103 
104             return constructWizard(wizard, steps);
105         } catch (DBException ex) {
106             throw new WizardException("Error building wizard: " + this.getWizardId(), ex);
107         }
108     }
109 
110 
111     /***
112      * Constructs the steps of the wizard.  Override to customize your
113      * step construction.
114      *
115      * @return List of Wizard Steps
116      * @throws DBException if there is an error querying the underlying data object.
117      */
118     private List constuctCompletionSteps() throws DBException {
119         List pages = new ArrayList(getDefinition().getCompletionParts().size() + 3);
120 
121         pages.add(constructTitlePage());
122         pages.addAll(constructPartsPages());
123         pages.add(constructCompletionPage());
124 
125         return pages;
126     }
127 
128     /***
129      * Probably the most involved portion of the builder -- go through the Parts
130      * and build the various entry pages.
131      *
132      * @return List
133      */
134     private List constructPartsPages() {
135         List results = new ArrayList();
136         for (Iterator i = getDefinition().getCompletionParts().iterator(); i.hasNext();) {
137             CompletionPartsBean partsbean = (CompletionPartsBean) i.next();
138 
139             if (FieldCompletion.WIZARD.equals(partsbean.getFieldCompletion())) {
140                 try {
141                     Part stepPart = partsbean.getPart();
142                     String id = stepPart.getId();
143 
144                     EmoWizardMetadata pageMetadata;
145 
146                     //TODO: Fix me into clean separation of logic
147                     if (stepPart.isSharedNodeAttrib()) {
148                         RelationPageMetadata relationMetadata = new RelationPageMetadata();
149                         relationMetadata.setMaxEntries(partsbean.getMaxEntries());
150                         relationMetadata.setMinEntries(partsbean.getMinEntries());
151                         relationMetadata.setNodeId(getDefinition().getTargetId().toString());
152                         relationMetadata.setNodeType(stepPart.getPartType());
153                         relationMetadata.setPartNodeRelation(stepPart.getNodeRelation());
154                         relationMetadata.setEntry(true);
155                         relationMetadata.setEntryRequired(false);
156                         relationMetadata.setViewId("MultiCheckbox");
157 
158 
159                         pageMetadata = relationMetadata;
160 
161                     } else if (stepPart.areMultipleAttributesAllowed()) {
162                         //TODO: Create MultiAttributePage.
163                         pageMetadata = new EmoWizardMetadata();
164                     } else {
165                         pageMetadata = new EmoWizardMetadata();
166                     }
167 
168 
169                     ValidValue[] selectionMenu = null;
170 
171                     if (stepPart.hasPicklist()) {
172                         //
173                         //Take Parts picklist and add 'please make selection'
174                         //
175                         ValidValue[] tempSelectionMenu = stepPart.getPickListValidValues();
176                         selectionMenu = new ValidValue[tempSelectionMenu.length + 1];
177                         selectionMenu[0] = new ValidValue("", "(No Selection Made)");
178                         System.arraycopy(tempSelectionMenu, 0, selectionMenu, 1, tempSelectionMenu.length);
179 
180                         pageMetadata.setEntryRequired(false);
181                         pageMetadata.setEntry(true);
182                     } else if (stepPart.isHaveCustomHandler()) {
183                         // if part has custom component like 'settings', that custom component can have many embedded pages
184                         // todo use portlet display supplied by custom part handler
185 //                        IPartHandler handler = partsbean.getPart().getCustomHandler();
186 //                        handler.
187 
188                         pageMetadata = this.constructCustomHandlerPageMetadata(partsbean, id);
189                     } else if (stepPart.areMultipleAttributesAllowed() && !stepPart.isSharedNodeAttrib()) {
190                         //If min != max
191                         pageMetadata = this.constructMultiAttributePageMetadata(partsbean, id);
192 
193                         //If the following isn't true, then we don't have a how many
194                         //parts page.
195                         if (!minEqualsMax(partsbean)) {
196 
197                             //Multiple attributes.  There are two options here.  1 for related nodes
198                             //another for multiple attribute nodes.
199 
200                             //Construct the multiple entry.  This requires a two step process:
201                             //The first is 'how many entries are you going to make'
202                             //The second is 'ok, enter the data'
203 
204                             //We have a multi-text entry attribute page.  Create a new 'how many are you going to enter' page.
205                             Integer howmanyPartsId = generatePreMultiAttributePageId();
206                             EmoWizardMetadata howManyEntriesMetadata = constructHowManyEntriesPageMetadata(partsbean,
207                                     howmanyPartsId.toString());
208 
209                             //Construct the 'how many entries' page.
210                             EmoWizardPage preMultiEntryPage = new EmoWizardPage(howmanyPartsId, howManyEntriesMetadata,
211                                     null);
212 
213                             //Set the default value to how many entries are in the target definition.
214                             Attribute[] currentNumberAttributes = getDefinition().getCurrentNode().getAttributes(
215                                     partsbean.getPart().getPartType());
216                             preMultiEntryPage.setData(Integer.toString(currentNumberAttributes.length));
217                             results.add(preMultiEntryPage);
218                         }
219 
220 
221                     } else {
222                         //Standard textbox.
223                         pageMetadata.setEntryRequired(false);
224                     }
225 
226                     setCommonPartsParameters(partsbean, id, pageMetadata);
227 
228 
229                     EmoWizardPage wizardPage = constructWizardPage(partsbean, id, pageMetadata, selectionMenu);
230 
231                     results.add(wizardPage);
232                 } catch (Exception e) {
233                     e.printStackTrace();
234                     throw new RuntimeException(e);
235                 }
236             }
237         }
238 
239         return results;
240     }
241 
242     /***
243      * Constructs the actual wizard page.
244      *
245      * @param partsbean     CompletionPartsBean the current part for the page.
246      * @param id            finalString the page id.
247      * @param pageMetadata  EmoWizardMetadata the wizard metadata that has been
248      *                      constructed earlier in the process.
249      * @param selectionMenu ValidValue[] the selection menu (may be null)
250      * @return EmoWizardPage or derivative.
251      * @throws DBException upon database query error.
252      * @throws Exception   upon custom part handler error.
253      */
254     private EmoWizardPage constructWizardPage(final CompletionPartsBean partsbean,
255                                               final String id,
256                                               final EmoWizardMetadata pageMetadata,
257                                               final ValidValue[] selectionMenu) throws DBException, Exception {
258         EmoWizardPage wizardPage;
259         Part stepPart = partsbean.getPart();
260         Node wizardTarget = getDefinition().getCurrentNode();
261         Attribute[] attributes = wizardTarget.getAttributes(stepPart.getPartType());
262 
263         //TODO: Extract clean logic here.
264         if (stepPart.isHaveCustomHandler()) {
265             wizardPage = constructCustomHandlerPage(id, pageMetadata);
266         } else if (stepPart.isSharedNodeAttrib()) {
267             wizardPage = new RelationWizardPage(new Integer(id), pageMetadata);
268 //            Node[] relatedNodes = wizardTarget.getRelatedNodes(stepPart.getNodeRelation(),stepPart.getPartType());
269 //            HashMap dataToPut = new HashMap(relatedNodes.length);
270 //            for (int i = 0; i < relatedNodes.length; i++) {
271 //                dataToPut.put(RelationWizardPage.PARAM_PREFIX + "_" + i, relatedNodes[i].getNodeId());
272 //            }
273 
274 //            if (dataToPut.size() > 0) {
275 //                wizardPage.setData(dataToPut);
276 //            }
277         } else if (stepPart.areMultipleAttributesAllowed()) {
278 
279             wizardPage = new MultiEntryWizardPage(new Integer(id), pageMetadata);
280             if (minEqualsMax(partsbean)) {
281                 ((MultiEntryWizardPage) wizardPage).setNumEntries(partsbean.getMinEntries().intValue());
282             }
283 
284             List dataToSet = new ArrayList(attributes.length);
285             for (int i = 0; i < attributes.length; i++) {
286                 dataToSet.add(attributes[i].getAttribValueRaw());
287             }
288 
289             if (dataToSet.size() > 0) {
290                 // todo we will still get errors from empty boxes if this data set is incomplete
291                 wizardPage.setData((Serializable) dataToSet);
292             }
293         } else {
294             wizardPage = new EmoWizardPage(new Integer(id), pageMetadata, selectionMenu);
295             if (attributes.length == 0) {
296                 wizardPage.setData(null);
297             } else {
298                 assert attributes.length == 0 || attributes.length == 1:
299                         "Expected only zero or one attributes returned for single attribute";
300                 wizardPage.setData(attributes[0].getAttribValueRaw());
301             }
302         }
303         return wizardPage;
304     }
305 
306 
307     /***
308      * Special case. If min equals max values in multi-entry pages.
309      *
310      * @param partsbean CompletionPartsBean the current part.
311      * @return boolean true if min equals max.
312      */
313     private boolean minEqualsMax(final CompletionPartsBean partsbean) {
314         if (partsbean.getMinEntries() != null && partsbean.getMaxEntries() != null) {
315             if (partsbean.getMinEntries().intValue() == partsbean.getMaxEntries().intValue()) {
316                 return true;
317             }
318         }
319         return false;
320     }
321 
322 
323     private void setCommonPartsParameters(final CompletionPartsBean partsbean, final String id,
324                                           final EmoWizardMetadata pageMetadata) throws DBException {
325 
326         pageMetadata.setCancelLink(cloneTransition(cancel, id));
327         pageMetadata.setNextLink(cloneTransition(next, id));
328         pageMetadata.setPreviousLink(cloneTransition(back, id));
329         pageMetadata.setTitle(partsbean.getPart().getPartLabel());
330         pageMetadata.setDirective(partsbean.getDirective());
331         pageMetadata.setHelpText(partsbean.getHelpText());
332 
333         //The Wizard has metadata entry.
334         pageMetadata.setEntry(true);
335 
336     }
337 
338     /***
339      * Constructs 'how many entries' page metadata.  This is coupled with the
340      * multi-attribute pages and is ordered prior to those pages.
341      *
342      * @param partsbean CompletionPartsBean the current part bean.
343      * @param id        String page id.
344      * @return EmoWizardMetadata the emo wizard metadata previously constructed
345      * @throws DBException upon error.
346      */
347     private EmoWizardMetadata constructHowManyEntriesPageMetadata(final CompletionPartsBean partsbean,
348                                                                   final String id) throws DBException {
349 
350         EmoWizardMetadata howManyEntriesMetadata = new EmoWizardMetadata();
351         howManyEntriesMetadata.setEntryRequired(false);
352         howManyEntriesMetadata.setEntry(true);
353         howManyEntriesMetadata.setCancelLink(cloneTransition(cancel, id));
354         howManyEntriesMetadata.setNextLink(cloneTransition(next, id));
355         howManyEntriesMetadata.setPreviousLink(cloneTransition(back, id));
356         howManyEntriesMetadata.setTitle("How many " + partsbean.getPart().getPartLabel());
357         howManyEntriesMetadata.setDirective(buildMultiAttributeIntroDirective(partsbean));
358         howManyEntriesMetadata.setViewId("HowManyAttributes");
359         return howManyEntriesMetadata;
360     }
361 
362 
363     /***
364      * Custructs metadata based on custom handlers.
365      *
366      * @param partsbean CompletionPartsBean
367      * @param pageId    String
368      * @return EmoWizardMetadata
369      * @throws DBException
370      */
371     private EmoWizardMetadata constructCustomHandlerPageMetadata(CompletionPartsBean partsbean, String pageId) throws
372             DBException {
373         CustomPartHandlerMetadata customHandlerMetadata = new CustomPartHandlerMetadata();
374         customHandlerMetadata.setCancelLink(cloneTransition(cancel, pageId));
375         customHandlerMetadata.setNextLink(cloneTransition(next, pageId));
376         customHandlerMetadata.setPreviousLink(cloneTransition(back, pageId));
377         customHandlerMetadata.setTitle(partsbean.getPart().getPartLabel());
378 
379         String directive = "Please enter settings. To learn more about the " +
380                 "choices, click on the label of a setting, and after reading more," +
381                 " navigate back to here with the browser's 'back' button. ";
382 
383         customHandlerMetadata.setHelpText(directive);
384         customHandlerMetadata.setDirective(directive);
385         partsbean.setDirective(directive);
386 
387 //      partsbean has access to the part, so use it to retrieve and remember custom handler class
388         String handlerClass = partsbean.getPart().getField(Part.SPECIAL_HANDLER);
389         customHandlerMetadata.setCustomHandlerClassName(handlerClass);
390 
391         return customHandlerMetadata;
392     }
393 
394     /***
395      * Construct a custom handler page.
396      *
397      * @param id           String id of page in wizard
398      * @param pageMetadata EmoWizardMetadata
399      * @return EmoWizardPage
400      * @throws Exception upon error.
401      * @todo Finish Me.
402      */
403     private EmoWizardPage constructCustomHandlerPage(String id,
404                                                      EmoWizardMetadata pageMetadata) throws Exception {
405 
406         /***
407          * @todo populate default page from custom handler.
408          */
409 
410         // store menus in metadata, default values in page
411         CustomPartHandlerMetadata customHandlerMetadata = (CustomPartHandlerMetadata) pageMetadata;
412         IPartHandler handler = customHandlerMetadata.getCustomHandler();
413         Integer templateId = getDefinition().getTargetId();
414 
415         if (handler instanceof AbstractSettingHandler) {
416             if (customHandlerMetadata.getInputList() == null) {
417                 AbstractSettingHandler settingHandler = (AbstractSettingHandler) handler;
418                 List list = settingHandler.getInputs(templateId.toString());
419                 customHandlerMetadata.setInputList(list);
420                 customHandlerMetadata.setSettingPrototype(settingHandler.getSettingPrototype());
421             }
422         } else {
423             throw new Exception("unimplemented custom part: " + handler.getClass().getName());
424         }
425 
426         return (EmoWizardPage) new CustomPartHandlerPage(new Integer(id), pageMetadata);
427     }
428 
429 
430     private EmoWizardMetadata constructMultiAttributePageMetadata(CompletionPartsBean partsbean, String id) throws
431             DBException {
432         MultiEntryMetadata enterMultiple = new MultiEntryMetadata();
433         enterMultiple.setEntryRequired(false);
434         enterMultiple.setEntry(true);
435         enterMultiple.setCancelLink(cloneTransition(cancel, id));
436         enterMultiple.setNextLink(cloneTransition(next, id));
437         enterMultiple.setPreviousLink(cloneTransition(back, id));
438         enterMultiple.setTitle(partsbean.getPart().getPartLabel());
439         enterMultiple.setDirective(buildMultiAttributeIntroDirective(partsbean));
440         enterMultiple.setViewId("HowManyAttributes");
441         enterMultiple.setMinEntries(partsbean.getMinEntries());
442         enterMultiple.setMaxEntries(partsbean.getMaxEntries());
443 
444         //Now tweak the final page.
445         if (partsbean.getHelpText() == null || partsbean.getHelpText().length() == 0) {
446             enterMultiple.setHelpText("Page 2 of 2: Please enter values.");
447         } else {
448             enterMultiple.setHelpText("Page 2 of 2: " + partsbean.getHelpText());
449         }
450 
451         enterMultiple.setViewId("MultiAttributeWizardEntry");
452 
453         return enterMultiple;
454 
455     }
456 
457     /***
458      * Generates a unique page id that differentiates it from the parts pages.
459      *
460      * @return Integer
461      */
462     private Integer generatePreMultiAttributePageId() {
463         if (preMultiAttributeStepIds.size() == 0) {
464             Integer returnValue = new Integer(INITIAL_PRESTEP_ID);
465             preMultiAttributeStepIds.add(returnValue);
466             return returnValue;
467         } else {
468             Integer lowestInt = (Integer) preMultiAttributeStepIds.first();
469             Integer nextLowest = new Integer(lowestInt.intValue() - 1);
470             preMultiAttributeStepIds.add(nextLowest);
471             return nextLowest;
472         }
473     }
474 
475     /***
476      * Builds the directive for the 'multiple attribute' selection intro screens.
477      * The text creation logic is a little convoluted so since it depends on whether
478      * min or max is defined.
479      *
480      * @param partsbean CompletionPartsBean
481      * @return String the resulting string.
482      * @throws DBException upon Part query error.
483      */
484     private String buildMultiAttributeIntroDirective(CompletionPartsBean partsbean) throws DBException {
485         StringBuffer buffer = new StringBuffer(128);
486         // @todo move layout to view tier
487         buffer.append("Page 1 of 2: How many ");
488         buffer.append(partsbean.getPart().getPartLabel());
489         buffer.append(" would you like to enter?");
490         boolean addedMin = false;
491         if (partsbean.getMinEntries() != null) {
492             addedMin = true;
493             buffer.append(" You must enter a minimum value of ");
494             buffer.append(partsbean.getMinEntries());
495         }
496 
497         boolean addedMax = false;
498         if (partsbean.getMaxEntries() != null) {
499             addedMax = true;
500             if (addedMin) {
501                 buffer.append(" and a maximum value of ");
502             } else {
503                 buffer.append("  You must enter a maximum value of");
504             }
505             buffer.append(partsbean.getMaxEntries());
506         }
507 
508         if (addedMin || addedMax) {
509             buffer.append(".");
510         }
511 
512         return buffer.toString();
513     }
514 
515     /***
516      * Construct the introduction page that is the first thing the user sees
517      * when the wizard is run.
518      *
519      * @return EmoWizardPage
520      */
521     private EmoWizardPage constructTitlePage() {
522         EmoWizardMetadata pageMetadata = new EmoWizardMetadata();
523         pageMetadata.setCancelLink(cloneTransition(cancel, EmoCompletionWizard.TITLE_PAGE_ID));
524         pageMetadata.setNextLink(cloneTransition(next, EmoCompletionWizard.TITLE_PAGE_ID));
525         pageMetadata.setTitle(getDefinition().getWizardTitle());
526         pageMetadata.setDirective(getDefinition().getSummary());
527         pageMetadata.setEntry(false);
528 
529         return new EmoWizardPage(new Integer(EmoCompletionWizard.TITLE_PAGE_ID), pageMetadata,
530                 null);
531     }
532 
533     /***
534      * Construct the final page for the wizard that is displayed before the user
535      * clicks the finish button.
536      *
537      * @return EmoWizardPage
538      * @throws DBException upon error querying the underlying data objects.
539      */
540     private EmoWizardPage constructCompletionPage() throws DBException {
541 
542         CompletionBean cb = this.getDefinition();
543         EmoWizardMetadata pageMetadata = new EmoWizardMetadata();
544         pageMetadata.setCancelLink(cloneTransition(cancel, EmoCompletionWizard.FINAL_PAGE_ID));
545         pageMetadata.setFinishLink(cloneTransition(finish, EmoCompletionWizard.FINAL_PAGE_ID));
546         pageMetadata.setPreviousLink(cloneTransition(back, EmoCompletionWizard.FINAL_PAGE_ID));
547 
548         // @todo move layout to view tier
549         String nodeTitleId = " <strong>&quot;" + cb.getCurrentNode().getNodeTitleRaw()
550                 + "&quot;</strong> Template (" + cb.getCurrentNode().getNodeId() + ") ";
551 
552         pageMetadata.setTitle("Finishing completion wizard for " + cb.getCurrentNode().getNodeTitle());
553         pageMetadata.setHelpText(nodeTitleId);
554         pageMetadata.setDirective(
555                 "The values you entered are summarized below. Click 'next' to save those values into" + nodeTitleId + "and finish the wizard.");
556         pageMetadata.setEntry(false);
557         pageMetadata.setViewId("Confirm");
558         return new EmoWizardPage(new Integer(EmoCompletionWizard.FINAL_PAGE_ID), pageMetadata,
559                 null);
560     }
561 
562 
563 }