View Javadoc

1   /* ===================================================================
2    * Copyright 2002-04 SRI International.
3    * Released under the MOZILLA PUBLIC LICENSE Version 1.1
4    * which can be obtained at http://www.mozilla.org/MPL/MPL-1.1.html
5    * This software is distributed on an "AS IS"
6    * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
7    * See the License for the specific language governing rights and
8    * limitations under the License.
9    * =================================================================== */
10  package com.sri.emo.wizard.expressoimpl;
11  
12  import com.jcorporate.expresso.core.controller.*;
13  import com.jcorporate.expresso.core.dbobj.DBObject;
14  import com.sri.common.controller.AbstractComponentController;
15  import com.sri.emo.EmoSchema;
16  import com.sri.emo.wizard.*;
17  import com.sri.emo.wizard.defaults.SequentialWizard;
18  
19  import java.io.Serializable;
20  import java.util.*;
21  
22  
23  /***
24   * Wizard Controller is basically a thin wrapper over the Wizard domain
25   * model which does the actual thinking.  The WizardController may
26   * be overridden in the case of custom onFinish() behavior.
27   *
28   * @author Michael Rimov
29   */
30  public class WizardController extends AbstractComponentController {
31  
32      /***
33  	 * 
34  	 */
35  	private static final long serialVersionUID = 1L;
36  
37  	/***
38       * Start state constant.
39       */
40      public static final String STATE_BEGIN = "Begin";
41  
42      /***
43       * Next State constant.
44       */
45      public static final String NEXT_STATE = "Next";
46  
47      /***
48       * Finish State.
49       */
50      public static final String FINISH_STATE = "Finish";
51  
52      /***
53       * Cancel State.
54       */
55      public static final String CANCEL_STATE = "Cancel";
56  
57      /***
58       * Display Page State.
59       */
60      public static final String DISPLAY_STATE = "Display";
61  
62      /***
63       * Previous State.
64       */
65      public static final String PREVIOUS_STATE = "Previous";
66  
67      /***
68       * State name to allow jumping backwards.
69       */
70      public static final String STATE_JUMP_BACKWARDS = "JumpBackwards";
71  
72  
73      /***
74       * Key for session state for the Wizard.  [More than one wizard
75       * is possible in a session since they are keyed by wizard id.
76       */
77      protected static final String SESSION_STATE_KEY_PREFIX = WizardController.class.getName();
78  
79      /***
80       * Key for the current wizard in the request.
81       */
82      public static final String REQUEST_ID = "Wizard";
83  
84      /***
85       * Constant for the id of the EMO  Wizard that is being executed. Used
86       * in transitions inside the wizard.
87       */
88      public static final String WIZ_PARAMETER_ID = "Emo.WizardId";
89  
90      /***
91       * Constant for what data is sent us.
92       */
93      public static final String WIZ_DATA_ID = "Emo.DataParameter";
94  
95      /***
96       * Where the wizard is stored in the request context when finish() is
97       * called.
98       */
99      public static final String WIZ_RESULT_ID = "WizResult";
100 
101 
102     /***
103      * Parameter for Wizard's finish to get the ExpressoRequest object.
104      */
105     public static final String FINISH_REQUEST = "Request";
106 
107     /***
108      * Parameter for the Wizard's finish method to get the
109      * ExpressoResponse object.
110      */
111     public static final String FINISH_RESPONSE = "Response";
112 
113     /***
114      * Current Page ID Parameter name.
115      */
116     public static final String WIZ_PAGE_PARAMETER = "Emo.CurrentPage";
117 
118     /***
119      * Current page number constant.
120      */
121     public static final String CURRENT_PAGE_NUM = "current";
122 
123     /***
124      * Total Pages constant.
125      */
126     public static final String TOTAL_PAGES = "total";
127 
128 
129     /***
130      * Generic wizard controller.
131      */
132     public WizardController() {
133         super(com.sri.emo.EmoSchema.class);
134 
135         State s = new State("Begin", "Begin a Wizard");
136         s.addParameter(WIZ_PARAMETER_ID, DBObject.INT_MASK);
137         addState(s);
138 
139         s = new State("Next", "Next Page in the Wizard");
140         s.addParameter(WIZ_PARAMETER_ID, DBObject.INT_MASK);
141         addState(s);
142 
143         s = new State("Finish", "Finish the Wizard");
144         s.addParameter(WIZ_PARAMETER_ID, DBObject.INT_MASK);
145         addState(s);
146 
147         s = new State("Cancel", "Cancel the Wizard");
148         s.addParameter(WIZ_PARAMETER_ID, DBObject.INT_MASK);
149 //        s.addRequiredParameter(WIZ_CANCEL_PARAMETER);
150         addState(s);
151 
152         s = new State("Display", "Display a wizard page");
153         s.addParameter(WIZ_PARAMETER_ID, true, DBObject.INT_MASK);
154         s.addParameter(WIZ_PAGE_PARAMETER, true, DBObject.INT_MASK);
155         addState(s);
156 
157         s = new State("Previous", "Previous page in the Wizard");
158         s.addParameter(WIZ_PARAMETER_ID, DBObject.INT_MASK);
159         addState(s);
160 
161         s = new State(STATE_JUMP_BACKWARDS, "Jump Backwards In the Wizard");
162         addState(s);
163 
164         setSchema(com.sri.emo.EmoSchema.class);
165         setInitialState("Begin");
166     }
167 
168     /***
169      * Return the title of this Controller.
170      *
171      * @return java.lang.String The Title of the controller
172      */
173     public String getTitle() {
174         return "WizardController";
175     }
176 
177     /* getTitle() */
178 
179     /***
180      * Location for transition on cancel instances.  Override in custom
181      * controllers to have your own capabilities.
182      *
183      * @return Transition instance.
184      */
185     protected Transition getCancelTransition() {
186         Transition t = new Transition();
187         t.setName("Cancel");
188         t.setLabel("Cancel");
189         t.setControllerObject(WizardController.class);
190         t.setState(DISPLAY_STATE);
191 
192         return t;
193     }
194 
195     /***
196      * Retrieve where a particular wizard is stored in the session.
197      *
198      * @param wizardId    the current id of the wizard.
199      * @param dataContext the datacontext for the wizard.
200      * @return String in the session of the Wizard.
201      */
202     protected String getSessionKey(final String dataContext, final Serializable wizardId) {
203         assert wizardId != null : " wizardId Parameter Cannot be null";
204         assert dataContext != null: " parameter datacontext cannot be null";
205 
206         return SESSION_STATE_KEY_PREFIX + "." + dataContext + "." + wizardId.toString();
207     }
208 
209     /***
210      * Retrieves the current wizard from session.
211      *
212      * @param request the current controller request.
213      * @return Wizard Instance
214      * @throws ControllerException if unable to query the session.
215      */
216     public Wizard getCurrentWizard(final ExpressoRequest request) throws ControllerException {
217         Integer id = new Integer(request.getParameter(WIZ_PARAMETER_ID));
218 
219         //Get the session key.
220         String sessionKey = getSessionKey(request.getDataContext(), id);
221 
222         WizardMementoConverter converter = getMementoConverter();
223 
224         //Retrieve the item from the store.
225         Serializable toRetrieve = (Serializable) request.getSession().getPersistentAttribute(sessionKey);
226         if (toRetrieve == null) {
227             return null;
228         }
229 
230         //Convert to usable type if necessary.
231         if (converter != null) {
232             try {
233                 toRetrieve = converter.fromMemento(toRetrieve);
234             } catch (WizardException ex) {
235                 throw new ControllerException("Error converting wizard to be able to store it in session.", ex);
236             }
237         }
238 
239         return (Wizard) toRetrieve;
240     }
241 
242     /***
243      * Retrieves the current page as set in the displayed page.
244      *
245      * @param request       ExpressoRequest the controller request object.
246      * @param currentWizard Wizard the current wizard.
247      * @return WizardPage the current wizard page as defined by the hidden
248      *         parameters on the form.
249      * @throws ControllerException upon error.
250      */
251     public WizardPage getCurrentPage(final ExpressoRequest request,
252                                      final Wizard currentWizard) throws ControllerException {
253         String currentPageString = request.getParameter(WizardController.WIZ_PAGE_PARAMETER);
254         if (currentPageString == null || currentPageString.length() == 0) {
255             return currentWizard.getCurrentPage();
256         } else {
257             Integer i = null;
258             try {
259                 i = new Integer(currentPageString);
260             } catch (NumberFormatException ex) {
261                 throw new ControllerException("Illegal argument value " + "for parameter '" + WIZ_PAGE_PARAMETER + "'");
262             }
263             return currentWizard.getPageById(i);
264         }
265     }
266 
267     /***
268      * Stores a new wizard to session.
269      *
270      * @param request the <code>ExpressoRequest</code> object
271      * @param toStore the wizard to store.
272      * @param wizId   the integer id of the wizard.
273      * @throws ControllerException upon session related error.
274      */
275     protected void storeWizInSession(final ExpressoRequest request, final Wizard toStore,
276                                      final Integer wizId) throws ControllerException {
277 
278         String sessionId = getSessionKey(request.getDataContext(), wizId);
279 
280         WizardMementoConverter converter = getMementoConverter();
281         Serializable objectToStore = toStore;
282         if (converter != null) {
283             try {
284                 objectToStore = converter.toMemento(toStore);
285             } catch (WizardException ex) {
286                 throw new ControllerException("Error converting wizard to be able to store it in session.", ex);
287             }
288         }
289         request.getSession().setPersistentAttribute(sessionId, objectToStore);
290     }
291 
292 
293     /***
294      * <tt>Template Method</tt>.  Retrieves/Constructs the WizardMementoConverter
295      * appropriate for the current run.
296      *
297      * @return WizardMementoConverter or null if none is defined.
298      */
299     protected WizardMementoConverter getMementoConverter() {
300         return null;
301     }
302 
303     /***
304      * Create a new wizard and store it to session.
305      *
306      * @param request the <code>ExpressoRequest</code> object
307      * @return Wizard instance
308      * @throws WizardException     upon wizard error
309      * @throws ControllerException upon expresso controller related
310      *                             error.
311      */
312     protected Wizard newWizard(final ExpressoRequest request) throws WizardException, ControllerException {
313         Integer id = new Integer(request.getParameter(WIZ_PARAMETER_ID));
314         WizardRepository repository = ((EmoSchema) getSchemaInstance()).getWizardRepository(request.getDataContext());
315 
316         Wizard returnValue = repository.find(id);
317         storeWizInSession(request, returnValue, id);
318 
319         return returnValue;
320     }
321 
322     /***
323      * Runs the begin state.
324      *
325      * @param request  the <code>ExpressoRequest</code> object
326      * @param response the <code>ExpressoResponse</code> object.
327      * @throws ControllerException    upon error
328      * @throws NonHandleableException upon fatal error
329      */
330     protected void runBeginState(final ExpressoRequest request,
331                                  final ExpressoResponse response) throws ControllerException, NonHandleableException {
332         try {
333             Wizard wiz = newWizard(request);
334             wiz.begin();
335             request.setParameter(WIZ_PAGE_PARAMETER, wiz.getCurrentPage().getId().toString());
336             transition(DISPLAY_STATE, request, response);
337         } catch (WizardException ex) {
338             throw new ControllerException("Error starting wizard", ex);
339         }
340     }
341 
342     /***
343      * Runs the cancel state.
344      *
345      * @param request  the <code>ExpressoRequest</code> object
346      * @param response the <code>ExpressoResponse</code> object.
347      * @throws ControllerException    upon error
348      * @throws NonHandleableException upon fatal error
349      */
350     protected void runCancelState(final ExpressoRequest request,
351                                   final ExpressoResponse response) throws ControllerException, NonHandleableException {
352         Wizard wiz = getCurrentWizard(request);
353         if (wiz != null) {
354             request.getSession().removePersistentAttribute(this.getSessionKey(request.getDataContext(),
355                     request.getParameter(WIZ_PARAMETER_ID)));
356         }
357         getCancelTransition().executeTransition(request, response);
358     }
359 
360     /***
361      * Displays the result of the wizard.  This particular implementation
362      * puts the wizard in the request context under the parameter
363      * name of {@link #REQUEST_ID}.  and if the wizard metadata is using
364      * expresso links, then it dumps the resulting Transitions into the
365      * ExpressoResponse object. [Although JSTL can certainly
366      * get the links directly from the {@link #REQUEST_ID} object's metadata:
367      * so choose your style.
368      *
369      * @param request  the <code>ExpressoRequest</code> object
370      * @param response the <code>ExpressoResponse</code> object.
371      * @throws ControllerException    upon error
372      * @throws NonHandleableException upon fatal error
373      */
374     protected void runDisplayState(final ExpressoRequest request,
375                                    final ExpressoResponse response) throws ControllerException, NonHandleableException {
376 
377         //Add the cancel button.
378         response.add(getCancelTransition());
379 
380         Wizard requestedWizard = getCurrentWizard(request);
381         if (requestedWizard == null) {
382             return;
383         }
384 
385         //Save the current wizard in the request stack.
386         request.getSession().setAttribute(REQUEST_ID, requestedWizard);
387 
388         PageMetadata pageMetadata = requestedWizard.getCurrentPage().getMetadata();
389 
390         //
391         //Add next/finish/back links.
392         //
393         Link l = pageMetadata.getNext();
394         if ((l != null) && (l instanceof ExpressoLink)) {
395             response.add(((ExpressoLink) l).getTransition());
396         }
397 
398         l = pageMetadata.getFinish();
399         if ((l != null) && (l instanceof ExpressoLink)) {
400             response.add(((ExpressoLink) l).getTransition());
401         }
402 
403         l = pageMetadata.getPrevious();
404         if ((l != null) && (l instanceof ExpressoLink)) {
405             Transition previous = ((ExpressoLink) l).getTransition();
406             previous.addParam(WizardController.WIZ_DATA_ID, request.getParameter(WizardController.WIZ_DATA_ID));
407             response.add(previous);
408         }
409 
410         //Add the wizard input
411         addWizardInput(response, requestedWizard, pageMetadata);
412 
413         //Set the custom view if needed.
414         if (pageMetadata.getViewId() != null) {
415             response.setStyle(pageMetadata.getViewId());
416         }
417 
418         //Add Page Numbers if available.
419         addPageNumberForSequentialWizards(requestedWizard, response);
420 
421         // this is an interesting hack -- adding an output with a morebot-recognized
422         // error message on it, just in case the JSP is not found, and this message
423         // goes throught the expresso 'default' renderer, which puts Output content on screen
424         response.add(new Output("morebot protection internal output", "Sorry, an error has occured  "
425                 + "Expresso Default Page Has Been Displayed. Check struts configurations."));
426     }
427 
428 
429     /***
430      * Run the finish state.
431      *
432      * @param request  the <code>ExpressoRequest</code> object
433      * @param response the <code>ExpressoResponse</code> object.
434      * @throws ControllerException    upon error
435      * @throws NonHandleableException upon fatal error
436      */
437     protected void runFinishState(final ExpressoRequest request,
438                                   final ExpressoResponse response) throws ControllerException, NonHandleableException {
439 
440         try {
441             Wizard wiz = getCurrentWizard(request);
442             if (wiz == null) {
443                 response.addError("We're sorry, but your session has expired.  Please re-run the wizard");
444                 Transition cancel = getCancelTransition();
445                 cancel.executeTransition(request, response);
446                 return;
447 
448             }
449 
450             String data = request.getParameter(WIZ_DATA_ID);
451 
452             if (this.hasNoDataEntryAndShould(data, wiz.getCurrentPage())) {
453                 this.handleNoEntryErrorInPage(request, response);
454                 return;
455             }
456 
457             WizardPage currentPage = wiz.getCurrentPage();
458             Map finishParameters = new HashMap();
459             this.addOnFinishWizardParameters(request, response, finishParameters);
460             Object result = wiz.processFinish(currentPage, data, finishParameters);
461             if (currentPage.getPageErrors() == null || currentPage.getPageErrors().isEmpty()) {
462                 request.getSession().setAttribute(WIZ_RESULT_ID, result);
463                 releaseWizard(request, wiz);
464                 afterFinishState(request, response);
465             } else {
466                 //Don't release the page if there were errors.
467                 transferPageErrorsToRequest(currentPage, response);
468                 transition(DISPLAY_STATE, request, response);
469             }
470         } catch (WizardException ex) {
471             throw new ControllerException("Error going to previous page", ex);
472         }
473     }
474 
475     protected void addOnFinishWizardParameters(final ExpressoRequest request,
476                                                final ExpressoResponse response,
477                                                final Map parameterMap) {
478 
479     }
480 
481     /***
482      * Template method.  Once the wizard is released and processing is finished
483      * what do we do?
484      *
485      * @param request  ExpressoRequest Expresso Request object
486      * @param response ExpressoResponse ExpressoResponse object
487      * @throws ControllerException upon errors.
488      */
489     protected void afterFinishState(final ExpressoRequest request, final ExpressoResponse response) throws
490             ControllerException {
491         transition(DISPLAY_STATE, request, response);
492     }
493 
494     /***
495      * Removes the wizard resources from the session.
496      *
497      * @param request ExpressoRequest the ExpressoRequest object.
498      * @param wiz     Wizard the Wizard to release.
499      * @throws ControllerException upon session removal error.
500      * @throws WizardException     upon wizard destruction error.
501      */
502     protected void releaseWizard(final ExpressoRequest request, final Wizard wiz) throws ControllerException,
503             WizardException {
504         wiz.destroy();
505 
506         //Remove the wizard from session and put it
507         //in the request so the JSP pages can get the data from the
508         //wizard, and then we're done with it.
509         request.getSession().removePersistentAttribute(this.getSessionKey(request.getDataContext(),
510                 wiz.getId().toString()));
511 
512         request.getSession().setAttribute(
513                 getSessionKey(request.getDataContext(), wiz.getId().toString()),
514                 wiz);
515     }
516 
517     /***
518      * Runs the next state.  This fucntion checks for input and transitions
519      * to the display state after invoking the wizard's 'next' function if there
520      * were not errors.
521      *
522      * @param request  the <code>ExpressoRequest</code> object
523      * @param response the <code>ExpressoResponse</code> object.
524      * @throws ControllerException    upon error
525      * @throws NonHandleableException upon fatal error
526      */
527     protected void runNextState(final ExpressoRequest request,
528                                 final ExpressoResponse response) throws ControllerException, NonHandleableException {
529         try {
530             Wizard wiz = getCurrentWizard(request);
531             if (wiz == null) {
532                 response.addError("We're sorry, but your session has expired.  Please re-run the wizard");
533                 Transition cancel = getCancelTransition();
534                 cancel.executeTransition(request, response);
535                 return;
536             }
537 
538             //Verify that data has been entered.
539             WizardPage currentPage = getCurrentPage(request, wiz);
540 
541             //At the time of the writing of this comment, data can be
542             //either a list or Strings -- of course, other possibilities
543             //exist.
544             Serializable data = extractPostedWizardData(request);
545 
546             boolean errorInPage = false;
547             errorInPage = hasNoDataEntryAndShould(data, currentPage);
548 
549             if (errorInPage) {
550                 handleNoEntryErrorInPage(request, response);
551                 return;
552             }
553 
554             wiz.next(currentPage, data);
555 
556             //Check for errors on the page.
557             transferPageErrorsToRequest(currentPage, response);
558             transition(DISPLAY_STATE, request, response);
559         } catch (WizardException ex) {
560             throw new ControllerException("Error going to previous page", ex);
561         }
562     }
563 
564     private void transferPageErrorsToRequest(WizardPage currentPage, final ExpressoResponse response) throws
565             ControllerException {
566         ErrorCollection ec = currentPage.getPageErrors();
567         if (ec != null && ec.size() > 0) {
568             response.saveErrors(ec);
569             //Clear errors associated with the page
570             //since we've handled them now.
571             currentPage.removeErrors();
572         }
573     }
574 
575     /***
576      * Template Method, override in your derived classes if desired.  No matter
577      * what, it tries to extract WIZ_DATA_ID.  Then It appends _1, _2, _3, etc
578      * until it doesn't find any new parameters.  If it finds more than one
579      * value it returns a Collection, if not, it will return a string.  Derived
580      * classes, of course, may return other collections depending on the
581      * wizard.
582      *
583      * @param request ExpressoRequest
584      * @return Serializable may be null if the data is not found or incomplete.
585      */
586     protected Serializable extractPostedWizardData(final ExpressoRequest request) {
587         String data = request.getParameter(WIZ_DATA_ID);
588 
589         //If we found at least the one data, then check for more.
590         if (data != null) {
591 
592             //Return value-- lazily constructed.
593             ArrayList returnCollection = null;
594 
595             //Current parameter counter.
596             int paramCounter = 1;
597 
598             //The result of the last extract.
599             String extractedData = null;
600 
601             //
602             //Keep going for WIZ_DATA_ID + "_1", WIZ_DATA)DI+ "_2"
603             //until we don't hit anything.
604             //
605             do {
606                 extractedData = request.getParameter(WIZ_DATA_ID + "_" + paramCounter);
607                 if (extractedData != null) {
608                     if (returnCollection == null) {
609                         returnCollection = new ArrayList();
610                         returnCollection.add(data);
611                     }
612 
613                     returnCollection.add(extractedData);
614                 }
615                 paramCounter++;
616             } while (extractedData != null);
617 
618             //If we ended up building an arraylist, then return the list.
619             if (returnCollection != null) {
620                 return returnCollection;
621             } else {
622                 //Otherwise, return the original single data point we extracted.
623                 return data;
624             }
625         }
626         return data;
627     }
628 
629     /***
630      * Method that handles no-data entry if pages that should have occurred.
631      *
632      * @param request  ExpressoRequest
633      * @param response ExpressoResponse
634      * @throws NonHandleableException upon transition error.
635      * @throws ControllerException    upon error in substate (Usually Display)
636      */
637     protected void handleNoEntryErrorInPage(final ExpressoRequest request, final ExpressoResponse response) throws
638             NonHandleableException,
639             ControllerException {
640         ErrorCollection ec = request.getErrorCollection();
641         if (ec == null) {
642             ec = new ErrorCollection();
643         }
644 
645         ec.addError("Please enter a value for this step.");
646         response.saveErrors(ec);
647         transition(DISPLAY_STATE, request, response);
648     }
649 
650     /***
651      * Function that allows for consistent checking for whether data was
652      * entered.  In this case it checks for null values, zero length strings,
653      * or zero size collections.
654      *
655      * @param data        String
656      * @param currentPage WizardPage
657      * @return boolean
658      */
659     protected boolean hasNoDataEntryAndShould(final Object data, final WizardPage currentPage) {
660         boolean errorInPage = false;
661 
662         if (currentPage.getMetadata().isHasEntry() && currentPage.getMetadata().isEntryRequired()) {
663             if (data == null) {
664                 errorInPage = true;
665             } else if ((data instanceof String) && ((String) data).trim().length() == 0) {
666                 errorInPage = true;
667             } else if ((data instanceof Collection) && ((Collection) data).size() == 0) {
668                 errorInPage = true;
669             }
670         }
671         return errorInPage;
672     }
673 
674     /***
675      * Runs the begin state.
676      *
677      * @param request  the <code>ExpressoRequest</code> object
678      * @param response the <code>ExpressoResponse</code> object.
679      * @throws ControllerException    upon error
680      * @throws NonHandleableException upon fatal error
681      */
682     protected void runPreviousState(final ExpressoRequest request, final ExpressoResponse response) throws
683             ControllerException, WizardException {
684         Wizard wiz = getCurrentWizard(request);
685 
686         if (checkSessionExpired(request, response, wiz)) {
687             return;
688         }
689 
690         wiz.previous();
691         transition(DISPLAY_STATE, request, response);
692     }
693 
694     private boolean checkSessionExpired(ExpressoRequest request, ExpressoResponse response,
695                                         Wizard wiz) throws NonHandleableException, ControllerException {
696         if (wiz == null) {
697             response.addError("We're sorry, but your session has expired.  Please re-run the wizard");
698             Transition cancel = getCancelTransition();
699             cancel.executeTransition(request, response);
700             return true;
701         } else {
702             return false;
703         }
704     }
705 
706 
707     /***
708      * Runs the state that jumps the wizard backwards to some sort of previous page.
709      *
710      * @param request  the <code>ExpressoRequest</code> object
711      * @param response the <code>ExpressoResponse</code> object.
712      * @throws ControllerException upon error
713      */
714     protected void runJumpBackwardsState(final ExpressoRequest request, final ExpressoResponse response) throws
715             ControllerException {
716         Wizard wiz = getCurrentWizard(request);
717 
718         if (checkSessionExpired(request, response, wiz)) {
719             return;
720         }
721 
722 
723         String pageId = request.getParameter(WIZ_PAGE_PARAMETER);
724         if (pageId == null || pageId.length() == 0) {
725             throw new ControllerException(
726                     "Parameter " + WIZ_PAGE_PARAMETER + " is required to jump backwards in the wizard.");
727         }
728 
729         wiz.backupToPage(pageId);
730         transition(DISPLAY_STATE, request, response);
731     }
732 
733 
734     /***
735      * Adds the wizard page input.  Template method to allow overriding based
736      * on
737      *
738      * @param response ExpressoResponse
739      * @param wiz      Wizard
740      * @param wp       PageMetadata
741      * @throws ControllerException
742      */
743     protected void addWizardInput(final ExpressoResponse response, final Wizard wiz,
744                                   final PageMetadata wp) throws ControllerException {
745         if (wp.isHasEntry()) {
746             Input i = new Input();
747             i.setName(WIZ_DATA_ID);
748             i.setLabel(wp.getDirective());
749 
750             // @todo why not set Input type? -- A: Type of input varies too much
751             //between pages to really tell.  We'd have to modify the API to tie
752             //it to a widget of some sort.  Direct JSP manipulation seems
753             //to be easier af the moment.  (IMO of course! )  -MR
754 
755             // if we have data, display it
756             if (wiz.getCurrentPage().getMenu() != null && wiz.getCurrentPage().getMenu().length > 0) {
757 
758                 List v = Arrays.asList(wiz.getCurrentPage().getMenu());
759                 i.setValidValues(v);
760                 String curVal = (String) wiz.getCurrentPage().getData();
761                 if (curVal != null && curVal.length() > 0) {
762                     i.setDefaultValue(curVal);
763                 }
764             }
765 
766 
767             if (wiz.getCurrentPage().getData() instanceof List) {
768                 List inputs = (List) wiz.getCurrentPage().getData();
769                 int counter = 0;
770                 for (Iterator iterator = inputs.iterator(); iterator.hasNext();) {
771                     if (counter == 0) {
772                         i.setDefaultValue((String) iterator.next());
773                         response.add(i);
774                     } else {
775                         Input nextInput = new Input();
776                         nextInput.setName(WIZ_DATA_ID + "_" + counter);
777                         nextInput.setDefaultValue((String) iterator.next());
778                         response.add(nextInput);
779                     }
780 
781                     counter++;
782                 }
783             } else {
784                 if (wiz.getCurrentPage().getData() != null) {
785                     i.setDefaultValue(wiz.getCurrentPage().getData().toString());
786                 }
787                 response.add(i);
788             }
789         }
790     }
791 
792     protected void addPageNumberForSequentialWizards(final Wizard wiz, final ExpressoResponse response) throws
793             ControllerException {
794         //If we have a sequential wizard where we know the exact number
795         //of steps that will be taken then we can
796         //output the page sequences.
797         if (wiz instanceof SequentialWizard) {
798             SequentialWizard sequence = (SequentialWizard) wiz;
799             response.add(new Output(CURRENT_PAGE_NUM, "" + sequence.getCurrentPageIndex()));
800             response.add(new Output(TOTAL_PAGES, "" + sequence.getTotalPages()));
801         }
802     }
803 }