View Javadoc

1   package com.sri.emo.wizard.completion;
2   
3   import com.jcorporate.expresso.core.controller.Input;
4   import com.jcorporate.expresso.core.db.DBException;
5   import com.jcorporate.expresso.core.db.exception.DBRecordNotFoundException;
6   import com.sri.emo.dbobj.Attribute;
7   import com.sri.emo.dbobj.IPartHandler;
8   import com.sri.emo.dbobj.Node;
9   import com.sri.emo.dbobj.Part;
10  import com.sri.emo.wizard.WizardException;
11  import com.sri.emo.wizard.WizardMonitor;
12  import com.sri.emo.wizard.WizardPage;
13  import com.sri.emo.wizard.completion.model.CompletionBean;
14  import com.sri.emo.wizard.completion.model.CompletionPartsBean;
15  import com.sri.emo.wizard.completion.model.FieldCompletion;
16  import com.sri.emo.wizard.defaults.SequentialWizard;
17  
18  import java.io.Serializable;
19  import java.util.*;
20  
21  /***
22   * A completion wizard.
23   * For the completion wizard pages, ids are java.lang.Integer types with values
24   * <= 0 are special pages.  Any values >0 represent a corresponding Part.
25   * Developers should use isPartsPage() for interpretation of this, however.
26   *
27   * @author Michael Rimov
28   */
29  public class EmoCompletionWizard extends SequentialWizard {
30  
31      /***
32  	 * 
33  	 */
34  	private static final long serialVersionUID = 1L;
35  
36  	/***
37       * Constant for the title page id.
38       */
39      public static final String TITLE_PAGE_ID = "-2";
40  
41      /***
42       * Constant for the last page (where 'finish' appears) id.
43       */
44      public static final String FINAL_PAGE_ID = "-1";
45  
46  
47      /***
48       * Underlying completion data bean. This object is not serialized due
49       * to space considerations since it is stateless for the given wizard.
50       * However, for saving state and reconsistuting it via the <tt>Memento</tt>
51       * pattern, then it is set/reset during that phase.
52       */
53      private transient CompletionBean completionBean;
54  
55  
56      /***
57       * Constructs an emo completion wizard.
58       *
59       * @param wizMonitor WizardMonitor
60       * @param steps      WizardPage[]
61       */
62      public EmoCompletionWizard(final WizardMonitor wizMonitor, final WizardPage[] steps) {
63          super(wizMonitor, steps);
64      }
65  
66  
67      /***
68       * Override of Sequential's onNextPage to allow for validation of values
69       * as they're entered.
70       *
71       * @param previousPage     WizardPage
72       * @param nextPage         The next page that will be invoked.  A 'lookahead' so
73       *                         to speak.
74       * @param previousPageData Serializable
75       * @return boolean true if the wizard can proceed, false if the previous
76       *         page needs to be displayed with validation errors.
77       * @throws WizardException upon error.
78       * @throws AssertionError  if the previouspage id is the final page.
79       */
80      protected boolean onNextPage(final WizardPage previousPage, final WizardPage nextPage,
81                                   final Serializable previousPageData) throws WizardException, AssertionError {
82  
83          Integer previousPageId = (Integer) previousPage.getId();
84          assert previousPageId != null;
85  
86          //If we have a part page
87          if (isPartsPage(previousPageId)) {
88              //validate part entry.
89              return validatePartPageData(previousPage, previousPageData);
90              //Or if we have a title page.
91          } else if (TITLE_PAGE_ID.equals(previousPageId.toString())) {
92              //Then return true/
93              return true;
94  
95              //Or if we have the final page.
96          } else if (FINAL_PAGE_ID.equals(previousPageId.toString())) {
97              assert false:"Final page should not have gotten 'onNext' event";
98              throw new WizardException("Internal Error: received 'next' where the previous page "
99                      + "was the final page.  Wizard is incorrectly constructed.");
100         } else {
101 
102             //Consider if the next page is a multi attribute page, then the current
103             //date must be the number of steps.
104             if (nextPage.getMetadata() instanceof MultiEntryMetadata) {
105                 boolean returnValue = validatePreMultiAttributesPage(previousPage, nextPage, previousPageData);
106                 returnValue &= super.onNextPage(previousPage, nextPage, previousPageData);
107                 return returnValue;
108 //                if (returnValue && ((MultiEntryWizardPage)nextPage).getNumEntries() == 0) {
109 //                    return returnValue
110 //                }
111             }
112 
113             //
114             //We don't have to do anything to make everything work.
115             //
116         }
117 
118         //
119         //Super class will return false if the page itself has errors.
120         //
121         return super.onNextPage(previousPage, nextPage, previousPageData);
122     }
123 
124 
125     private boolean validatePreMultiAttributesPage(final WizardPage previousPage, final WizardPage nextPage,
126                                                    final Serializable previousPageData) {
127 //        if (previousPageData == null) {
128 //            previousPage.addError("You must enter a value for this page");
129 //            return false;
130 //        }
131 
132         MultiEntryWizardPage multiNextPage = (MultiEntryWizardPage) nextPage;
133         MultiEntryMetadata nextPageMetadata = (MultiEntryMetadata) nextPage.getMetadata();
134 
135         int numEntries = 0;
136         try {
137 
138             if (previousPageData == null || ((String) previousPageData).trim().length() == 0) {
139                 numEntries = 0;
140             } else {
141                 numEntries = Integer.parseInt((String) previousPageData);
142             }
143         } catch (NumberFormatException ex) {
144             previousPage.addError("You must enter a valid number for this page");
145             return false;
146         }
147 
148         boolean hadError = false;
149 //        if (nextPageMetadata.getMinEntries() != null
150 //                && numEntries < nextPageMetadata.getMinEntries().intValue()) {
151 //            previousPage.addError(
152 //                "Too few. You must enter a value greater than or equal to the minimum number of entries: "
153 //                + nextPageMetadata.getMinEntries());
154 //            hadError = true;
155 //        }
156 
157         if (nextPageMetadata.getMaxEntries() != null
158                 && numEntries > nextPageMetadata.getMaxEntries().intValue()) {
159             previousPage.addError(
160                     "Too many. You must enter a value less than or equal to the maximum number of entries: "
161                             + nextPageMetadata.getMaxEntries());
162             hadError = true;
163         }
164 
165         if (hadError) {
166             return false;
167         }
168 
169         multiNextPage.setNumEntries(numEntries);
170         return true;
171     }
172 
173     /***
174      * Constant for the minimum possible part number.  Anything else is a
175      * 'special page' of some sort.
176      */
177     private static final int MIN_PARTS_NUMBER = 0;
178 
179     /***
180      * Overridden target node id.  This must be explicitly set by the
181      * wizard controller. Otherwise we use the completion bean.
182      */
183     private String targetNodeId;
184 
185     /***
186      * Checks if the given page Id indicates it is a
187      * 'Parts' page.
188      *
189      * @param pageId Integer
190      * @return boolean
191      */
192     protected boolean isPartsPage(final Integer pageId) {
193         assert pageId != null;
194 
195         return pageId.intValue() >= MIN_PARTS_NUMBER;
196     }
197 
198     /***
199      * @param src         WizardPage
200      * @param enteredData Serializable
201      * @return boolean true if the data was validated.
202      * @throws WizardException upon error.
203      */
204     private boolean validatePartPageData(final WizardPage src, final Serializable enteredData) throws WizardException {
205         int partId = ((Integer) src.getId()).intValue();
206 
207         try {
208             Part onePart = new Part();
209             onePart.setPartId(partId);
210             onePart.retrieve();
211             //If single valued, then validate it.
212 //            * @todo Finish here when Part has custom handler.
213             if (onePart.isSingleValued() && enteredData == null) {
214                 src.addError("Please enter a value for this page.");
215                 return false;
216             }
217         } catch (DBRecordNotFoundException ex) {
218             throw new WizardException("Could not find part of id: " + partId
219                     + " perhaps it was deleted by someone else?", ex);
220         } catch (Throwable ex) {
221             throw new WizardException("Error processing page for part id: " + partId, ex);
222         }
223 
224         return true;
225     }
226 
227 
228     /***
229      * This version returns a <tt>Node<tt> instance if one has
230      * been successfully created.
231      * <p>{@inheritDoc}</p>
232      *
233      * @param src             WizardPage the source of the event.
234      * @param data            This class expects a string for the data.
235      * @param additonalParams anything that the underlying wizard needs. The
236      *                        values are set by the Application Controller.
237      * @return An instance of a {@link com.sri.emo.dbobj.Node} object
238      *         that represents the node the Decision Matrix returned or null if
239      *         there was no equivilant data found.
240      * @throws WizardException upon error.
241      */
242     public Object processFinish(final WizardPage src, final Serializable data,
243                                 final Map additonalParams) throws WizardException {
244         super.processFinish(src, data, additonalParams);
245 
246         assert src != null;
247 
248         Node node = null;
249 
250         // only save data if we have 'dynamic target', i.e., specified by param at beginning of
251         // web call, as opposed to 'model' target ID, which was used to design this wizard
252         if (this.isDynamicTarget()) {
253             try {
254                 node = new Node();
255                 node.setNodeId(getTargetNodeId());
256                 node.retrieve();
257 
258                 copyDataIntoTargetNode(node);
259 
260             } catch (DBRecordNotFoundException ex) {
261                 String message = "Could not find node of id: " + getTargetNodeId();
262                 src.addError(message + " with error: " + ex.getMessage());
263                 throw new WizardException(message, ex);
264             } catch (Exception e) {
265                 src.addError(e.getMessage());
266 
267                 if (e instanceof RuntimeException) {
268                     throw (RuntimeException) e;
269                 } else {
270                     throw new WizardException(e);
271                 }
272             }
273         } else {
274             src.addError("Cannot save to source of wizard design; expected a different (cloned?) destination");
275         }
276 
277         return node;
278     }
279 
280 
281     /***
282      * Returns true if the dynamic target has been specified.
283      *
284      * @return boolean
285      */
286     protected boolean isDynamicTarget() {
287         if (this.getCompletionBean().getTargetId().intValue() == Integer.parseInt(this.getTargetNodeId())) {
288             return false;
289         } else {
290             return true;
291         }
292     }
293 
294     /***
295      * Copies the wizard data into the given node.
296      *
297      * @param target Node The node that is set to receive all the data.
298      * @throws Exception upon consturction error, and database errors.
299      */
300     private void copyDataIntoTargetNode(final Node target) throws Exception {
301         CompletionBean completionBean = this.getCompletionBean();
302 
303         for (Iterator partsIterator = completionBean.getCompletionParts().iterator(); partsIterator.hasNext();) {
304             CompletionPartsBean onePartBean = (CompletionPartsBean) partsIterator.next();
305             //Skip parts beans that have no data.
306             if (onePartBean.getFieldCompletion() == FieldCompletion.FIXED) {
307                 continue;
308             }
309 
310             Part currentPart = onePartBean.getPart();
311 
312             //Retrieves the data value for that part.
313             Object wizardData = this.getAllData().get(new Integer(currentPart.getId()));
314 
315             if (currentPart.isHaveCustomHandler()) {
316 
317                 CustomPartHandlerMetadata meta = (CustomPartHandlerMetadata) getCurrentPage().getMetadata();
318                 IPartHandler handler = meta.getCustomHandler();
319                 List inputs = meta.getInputList();
320 
321                 for (Iterator iterator = inputs.iterator(); iterator.hasNext();) {
322                     Input input = (Input) iterator.next();
323                     handler.saveInput(input);
324                 }
325 
326             } else if (currentPart.isOwnedAttribute()) {
327                 //TODO Transaction Me
328                 if (currentPart.isMultipleAllowed()) {
329                     List valueList = (List) wizardData;
330 //                    assert valueList != null:"Got null values from wizard page: " + onePartBean.toString();
331                     if (valueList == null) {
332                         valueList = new ArrayList(0);
333                     }
334 
335                     Attribute[] attributes = target.getAttributes(currentPart.getPartType());
336                     //Don't worry about existing attributes since the wizard ones replace
337                     //whatever is there, so we go from there.
338                     for (int attributeIndex = 0; attributeIndex < attributes.length; attributeIndex++) {
339                         attributes[attributeIndex].delete(true);
340                     }
341 
342                     for (Iterator pageValuesIterator = valueList.iterator(); pageValuesIterator.hasNext();) {
343                         String onePageValue = (String) pageValuesIterator.next();
344 
345                         target.addAttribute(currentPart.getPartType(), onePageValue, "");
346                     }
347 
348                 } else {
349                     Attribute[] attributes = target.getAttributes(currentPart.getPartType());
350                     assert attributes.length == 0 || attributes.length == 1:
351                             "Expected only zero or one attributes returned for single attribute";
352 
353                     if (attributes.length == 0) {
354                         if (wizardData != null) {
355                             target.addAttribute(currentPart.getPartType(), wizardData.toString(), "");
356                         }
357                     } else {
358                         if (wizardData == null) {
359                             target.removeAttribute(attributes[0].getAttribId());
360                         } else {
361                             target.updateAttribute(attributes[0].getAttribId(), wizardData.toString(), "");
362                         }
363                     }
364                 }
365 
366             } else if (currentPart.isSharedNodeAttrib()) {
367                 //TODO Transaction Me
368                 assert wizardData instanceof Set;
369 
370                 Set allRelationsParameterMap = (Set) wizardData;
371                 if (allRelationsParameterMap == null) {
372                     allRelationsParameterMap = new HashSet(0);
373                 }
374                 String[] allRelationIds = (String[]) allRelationsParameterMap.toArray(
375                         new String[allRelationsParameterMap.size()]);
376 
377                 target.updateNodeRelations(currentPart.getPartType(), currentPart.getNodeRelation(), allRelationIds);
378             }
379 
380         }
381     }
382 
383 
384     public String getTargetNodeId() {
385         assert completionBean != null;
386 
387         if (targetNodeId == null) {
388             return this.completionBean.getTargetId().toString();
389         } else {
390             return targetNodeId;
391         }
392     }
393 
394     public Node getTargetNode() throws WizardException {
395         try {
396             Node n = new Node();
397             n.setNodeId(this.getTargetNodeId());
398             n.retrieve();
399             return n;
400         } catch (DBRecordNotFoundException ex) {
401             throw new WizardException("Could no longer find template of id: " + getTargetNodeId()
402                     + " perhaps someone else deleted it?");
403         } catch (DBException ex) {
404             throw new WizardException("Error querying database", ex);
405         }
406     }
407 
408     /***
409      * Sets the underlying completion bean.
410      *
411      * @param completionBean CompletionBean
412      */
413     public void setCompletionBean(final CompletionBean completionBean) {
414         this.completionBean = completionBean;
415     }
416 
417     public void setTargetNodeId(final String targetNodeId) {
418         this.targetNodeId = targetNodeId;
419     }
420 
421     public CompletionBean getCompletionBean() {
422         return completionBean;
423     }
424 
425 
426 }