KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > web > portlet > mvc > AbstractWizardFormController


1 /*
2  * Copyright 2002-2005 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.web.portlet.mvc;
18
19 import java.util.Enumeration JavaDoc;
20 import java.util.HashMap JavaDoc;
21 import java.util.Iterator JavaDoc;
22 import java.util.Map JavaDoc;
23
24 import javax.portlet.ActionRequest;
25 import javax.portlet.ActionResponse;
26 import javax.portlet.PortletException;
27 import javax.portlet.PortletRequest;
28 import javax.portlet.RenderRequest;
29 import javax.portlet.RenderResponse;
30
31 import org.springframework.validation.BindException;
32 import org.springframework.validation.Errors;
33 import org.springframework.web.portlet.ModelAndView;
34 import org.springframework.web.portlet.util.PortletUtils;
35 import org.springframework.web.util.WebUtils;
36
37 /**
38  * Form controller for typical wizard-style workflows.
39  *
40  * <p>In contrast to classic forms, wizards have more than one form view page.
41  * Therefore, there are various actions instead of one single submit action:
42  * <ul>
43  * <li>finish: trying to leave the wizard successfully, i.e. performing its
44  * final action, and thus needing a valid state;
45  * <li>cancel: leaving the wizard without performing its final action, and
46  * thus without regard to the validity of its current state;
47  * <li>page change: showing another wizard page, e.g. the next or previous
48  * one, with regard to "dirty back" and "dirty forward".
49  * </ul>
50  *
51  * <p>Finish and cancel actions can be triggered by request parameters, named
52  * PARAM_FINISH ("_finish") and PARAM_CANCEL ("_cancel"), ignoring parameter
53  * values to allow for HTML buttons. The target page for page changes can be
54  * specified by PARAM_TARGET, appending the page number to the parameter name
55  * (e.g. "_target1"). The action parameters are recognized when triggered by
56  * image buttons too (via "_finish.x", "_abort.x", or "_target1.x").
57  *
58  * <p>The current page number will be stored in the session. It can also be
59  * specified as request parameter PARAM_PAGE, to properly handle usage of
60  * the back button in a browser: In this case, a submission always contains
61  * the correct page number, even if the user submitted from an old view.
62  *
63  * <p>The page can only be changed if it validates correctly, except if a
64  * "dirty back" or "dirty forward" is allowed. At finish, all pages get
65  * validated again to guarantee a consistent state.
66  *
67  * <p>Note that a validator's default validate method is not executed when using
68  * this class! Rather, the <code>validatePage</code> implementation should call
69  * special <code>validateXXX</code> methods that the validator needs to provide,
70  * validating certain pieces of the object. These can be combined to validate
71  * the elements of individual pages.
72  *
73  * <p>Note: Page numbering starts with 0, to be able to pass an array
74  * consisting of the corresponding view names to the "pages" bean property.
75  *
76  * <p>Parameters indicated with <code>setPassRenderParameters</code> will be present
77  * for each page. If there are render parameters you need in <code>renderFinish</code>
78  * or <code>renderCancel</code>, then you need to pass those forward from the
79  * <code>processFinish</code> or <code>processCancel</code> methods, respectively.
80
81  * @author Juergen Hoeller
82  * @author John A. Lewis
83  * @since 2.0
84  * @see #setPages
85  * @see #validatePage
86  * @see #processFinish
87  * @see #processCancel
88  */

89 public abstract class AbstractWizardFormController extends AbstractFormController {
90
91     /**
92      * Parameter triggering the finish action.
93      * Can be called from any wizard page!
94      */

95     public static final String JavaDoc PARAM_FINISH = "_finish";
96
97     /**
98      * Parameter triggering the cancel action.
99      * Can be called from any wizard page!
100      */

101     public static final String JavaDoc PARAM_CANCEL = "_cancel";
102
103     /**
104      * Parameter specifying the target page,
105      * appending the page number to the name.
106      */

107     public static final String JavaDoc PARAM_TARGET = "_target";
108
109     /**
110      * Parameter specifying the current page as value. Not necessary on
111      * form pages, but allows to properly handle usage of the back button.
112      * @see #setPageAttribute
113      */

114     public static final String JavaDoc PARAM_PAGE = "_page";
115
116
117     private String JavaDoc[] pages;
118
119     private String JavaDoc pageAttribute;
120
121     private boolean allowDirtyBack = true;
122
123     private boolean allowDirtyForward = false;
124
125
126     /**
127      * Create a new AbstractWizardFormController.
128      * <p>"sessionForm" is automatically turned on, "validateOnBinding"
129      * turned off, and "cacheSeconds" set to 0 by the base class
130      * (-> no caching for all form controllers).
131      */

132     public AbstractWizardFormController() {
133         // AbstractFormController sets default cache seconds to 0.
134
super();
135
136         // Always needs session to keep data from all pages.
137
setSessionForm(true);
138
139         // Never validate everything on binding ->
140
// wizards validate individual pages.
141
setValidateOnBinding(false);
142     }
143
144     /**
145      * Set the wizard pages, i.e. the view names for the pages.
146      * The array index is interpreted as page number.
147      * @param pages view names for the pages
148      */

149     public final void setPages(String JavaDoc[] pages) {
150         if (pages == null || pages.length == 0) {
151             throw new IllegalArgumentException JavaDoc("No wizard pages defined");
152         }
153         this.pages = pages;
154     }
155
156     /**
157      * Return the wizard pages, i.e. the view names for the pages.
158      * The array index corresponds to the page number.
159      * <p>Note that a concrete wizard form controller might override
160      * <code>getViewName(PortletRequest, Object, int)</code> to
161      * determine the view name for each page dynamically.
162      * @see #getViewName(PortletRequest, Object, int)
163      */

164     public final String JavaDoc[] getPages() {
165         return pages;
166     }
167
168     /**
169      * Return the number of wizard pages.
170      * Useful to check whether the last page has been reached.
171      * <p>Note that a concrete wizard form controller might override
172      * <code>getPageCount(PortletRequest, Object)</code> to determine
173      * the page count dynamically.
174      * @see #getPageCount(PortletRequest, Object)
175      */

176     protected final int getPageCount() {
177         return this.pages.length;
178     }
179
180     /**
181      * Set the name of the page attribute in the model, containing
182      * an Integer with the current page number.
183      * <p>This will be necessary for single views rendering multiple view pages.
184      * It also allows for specifying the optional "_page" parameter.
185      * @param pageAttribute name of the page attribute
186      * @see #PARAM_PAGE
187      */

188     public final void setPageAttribute(String JavaDoc pageAttribute) {
189         this.pageAttribute = pageAttribute;
190     }
191
192     /**
193      * Return the name of the page attribute in the model.
194      */

195     public final String JavaDoc getPageAttribute() {
196         return pageAttribute;
197     }
198
199     /**
200      * Set if "dirty back" is allowed, i.e. if moving to a former wizard
201      * page is allowed in case of validation errors for the current page.
202      * @param allowDirtyBack if "dirty back" is allowed
203      */

204     public final void setAllowDirtyBack(boolean allowDirtyBack) {
205         this.allowDirtyBack = allowDirtyBack;
206     }
207
208     /**
209      * Return whether "dirty back" is allowed.
210      */

211     public final boolean isAllowDirtyBack() {
212         return allowDirtyBack;
213     }
214
215     /**
216      * Set if "dirty forward" is allowed, i.e. if moving to a later wizard
217      * page is allowed in case of validation errors for the current page.
218      * @param allowDirtyForward if "dirty forward" is allowed
219      */

220     public final void setAllowDirtyForward(boolean allowDirtyForward) {
221         this.allowDirtyForward = allowDirtyForward;
222     }
223
224     /**
225      * Return whether "dirty forward" is allowed.
226      */

227     public final boolean isAllowDirtyForward() {
228         return allowDirtyForward;
229     }
230
231
232     /**
233      * Calls page-specific onBindAndValidate method.
234      */

235     protected final void onBindAndValidate(PortletRequest request, Object JavaDoc command, BindException errors)
236             throws Exception JavaDoc {
237
238         onBindAndValidate(request, command, errors, getCurrentPage(request));
239     }
240
241     /**
242      * Callback for custom post-processing in terms of binding and validation.
243      * Called on each submit, after standard binding but before page-specific
244      * validation of this wizard form controller.
245      * <p>Note: AbstractWizardFormController does not perform standard
246      * validation on binding but rather applies page-specific validation
247      * on processing the form submission.
248      * @param request current portlet request
249      * @param command bound command
250      * @param errors Errors instance for additional custom validation
251      * @param page current wizard page
252      * @throws Exception in case of invalid state or arguments
253      * @see #bindAndValidate
254      * @see #processFormSubmission
255      * @see org.springframework.validation.Errors
256      */

257     protected void onBindAndValidate(PortletRequest request, Object JavaDoc command, BindException errors, int page)
258             throws Exception JavaDoc {
259     }
260
261     /**
262      * Consider an explicit finish or cancel request as a form submission too.
263      * @see #isFinishRequest(PortletRequest)
264      * @see #isCancelRequest(PortletRequest)
265      */

266     protected boolean isFormSubmission(PortletRequest request) {
267         return super.isFormSubmission(request) || isFinishRequest(request) || isCancelRequest(request);
268     }
269
270     /**
271      * Calls page-specific referenceData method.
272      */

273     protected final Map JavaDoc referenceData(PortletRequest request, Object JavaDoc command, Errors errors)
274             throws Exception JavaDoc {
275
276         return referenceData(request, command, errors, getCurrentPage(request));
277     }
278
279     /**
280      * Create a reference data map for the given request, consisting of
281      * bean name/bean instance pairs as expected by ModelAndView.
282      * <p>Default implementation delegates to referenceData(HttpServletRequest, int).
283      * Subclasses can override this to set reference data used in the view.
284      * @param request current portlet request
285      * @param command form object with request parameters bound onto it
286      * @param errors validation errors holder
287      * @param page current wizard page
288      * @return a Map with reference data entries, or null if none
289      * @throws Exception in case of invalid state or arguments
290      * @see #referenceData(PortletRequest, int)
291      * @see org.springframework.web.portlet.ModelAndView
292      */

293     protected Map JavaDoc referenceData(PortletRequest request, Object JavaDoc command, Errors errors, int page)
294             throws Exception JavaDoc {
295
296         return referenceData(request, page);
297     }
298
299     /**
300      * Create a reference data map for the given request, consisting of
301      * bean name/bean instance pairs as expected by ModelAndView.
302      * <p>Default implementation returns null.
303      * Subclasses can override this to set reference data used in the view.
304      * @param request current portlet request
305      * @param page current wizard page
306      * @return a Map with reference data entries, or null if none
307      * @throws Exception in case of invalid state or arguments
308      * @see org.springframework.web.portlet.ModelAndView
309      */

310     protected Map JavaDoc referenceData(PortletRequest request, int page) throws Exception JavaDoc {
311         return null;
312     }
313
314
315     /**
316      * Show first page as form view.
317      */

318     protected final ModelAndView showForm(
319             RenderRequest request, RenderResponse response, BindException errors) throws Exception JavaDoc {
320
321         return showPage(request, errors, getInitialPage(request, errors.getTarget()));
322     }
323
324     /**
325      * Prepare the form model and view, including reference and error data,
326      * for the given page. Can be used in <code>processFinish</code> implementations,
327      * to show the corresponding page in case of validation errors.
328      * @param request current portlet render request
329      * @param errors validation errors holder
330      * @param page number of page to show
331      * @return the prepared form view
332      * @throws Exception in case of invalid state or arguments
333      */

334     protected final ModelAndView showPage(RenderRequest request, BindException errors, int page)
335             throws Exception JavaDoc {
336
337         if (page >= 0 && page < getPageCount(request, errors.getTarget())) {
338             if (logger.isDebugEnabled()) {
339                 logger.debug("Showing wizard page " + page + " for form bean '" + getCommandName() + "'");
340             }
341             
342             // Set page session attribute, expose overriding request attribute.
343
Integer JavaDoc pageInteger = new Integer JavaDoc(page);
344             String JavaDoc pageAttrName = getPageSessionAttributeName(request);
345             if (isSessionForm()) {
346                 if (logger.isDebugEnabled()) {
347                     logger.debug("Setting page session attribute [" + pageAttrName + "] to: " + pageInteger);
348                 }
349                 request.getPortletSession().setAttribute(pageAttrName, pageInteger);
350             }
351             request.setAttribute(pageAttrName, pageInteger);
352             
353             // Set page request attribute for evaluation by views.
354
Map JavaDoc controlModel = new HashMap JavaDoc();
355             if (this.pageAttribute != null) {
356                 controlModel.put(this.pageAttribute, new Integer JavaDoc(page));
357             }
358             String JavaDoc viewName = getViewName(request, errors.getTarget(), page);
359             return showForm(request, errors, viewName, controlModel);
360         }
361
362         else {
363             throw new PortletException("Invalid wizard page number: " + page);
364         }
365     }
366
367     /**
368      * Return the page count for this wizard form controller.
369      * Default implementation delegates to <code>getPageCount()</code>.
370      * <p>Can be overridden to dynamically adapt the page count.
371      * @param request current portlet request
372      * @param command the command object as returned by formBackingObject
373      * @return the current page count
374      * @see #getPageCount
375      */

376     protected int getPageCount(PortletRequest request, Object JavaDoc command) {
377         return getPageCount();
378     }
379
380     /**
381      * Return the name of the view for the specified page of this wizard form controller.
382      * Default implementation takes the view name from the <code>getPages()</code> array.
383      * <p>Can be overridden to dynamically switch the page view or to return view names
384      * for dynamically defined pages.
385      * @param request current portlet request
386      * @param command the command object as returned by formBackingObject
387      * @return the current page count
388      * @see #getPageCount
389      */

390     protected String JavaDoc getViewName(PortletRequest request, Object JavaDoc command, int page) {
391         return getPages()[page];
392     }
393
394     /**
395      * Return the initial page of the wizard, i.e. the page shown at wizard startup.
396      * Default implementation delegates to <code>getInitialPage(PortletRequest)</code>.
397      * @param request current portlet request
398      * @param command the command object as returned by formBackingObject
399      * @return the initial page number
400      * @see #getInitialPage(PortletRequest)
401      * @see #formBackingObject
402      */

403     protected int getInitialPage(PortletRequest request, Object JavaDoc command) {
404         return getInitialPage(request);
405     }
406
407     /**
408      * Return the initial page of the wizard, i.e. the page shown at wizard startup.
409      * Default implementation returns 0 for first page.
410      * @param request current portlet request
411      * @return the initial page number
412      */

413     protected int getInitialPage(PortletRequest request) {
414         return 0;
415     }
416
417     /**
418      * Return the name of the PortletSession attribute that holds the page object
419      * for this wizard form controller.
420      * <p>Default implementation delegates to the <code>getPageSessionAttributeName</code>
421      * version without arguments.
422      * @param request current portlet request
423      * @return the name of the form session attribute, or null if not in session form mode
424      * @see #getPageSessionAttributeName
425      * @see #getFormSessionAttributeName
426      * @see javax.portlet.PortletSession#getAttribute
427      */

428     protected String JavaDoc getPageSessionAttributeName(PortletRequest request) {
429         return getPageSessionAttributeName();
430     }
431
432     /**
433      * Return the name of the PortletSession attribute that holds the page object
434      * for this wizard form controller.
435      * <p>Default is an internal name, of no relevance to applications, as the form
436      * session attribute is not usually accessed directly. Can be overridden to use
437      * an application-specific attribute name, which allows other code to access
438      * the session attribute directly.
439      * @return the name of the page session attribute
440      * @see #getFormSessionAttributeName
441      * @see javax.portlet.PortletSession#getAttribute
442      */

443     protected String JavaDoc getPageSessionAttributeName() {
444         return getClass().getName() + ".PAGE." + getCommandName();
445     }
446
447     /**
448      * Pass the page number to the render phase by setting a render parameter.
449      * This method may not be called when the action calls
450      * {@link javax.portlet.ActionResponse#sendRedirect(String)}.
451      * @param response the current action response
452      * @param page the page number
453      * @see ActionResponse#setRenderParameter
454      */

455     protected void setPageRenderParameter(ActionResponse response, int page) {
456         if (logger.isDebugEnabled())
457             logger.debug("Setting page number render parameter [" + PARAM_PAGE + "] to [" + page + "]");
458         try {
459             response.setRenderParameter(PARAM_PAGE, new Integer JavaDoc(page).toString());
460         }
461         catch (IllegalStateException JavaDoc ex) {
462             // ignore in case sendRedirect was already set
463
}
464     }
465
466     /**
467      * Pass the the parameter that indicates the target page of the request
468      * forward to the render phase. If the <code>getTargetPage<code> method
469      * was overridden, this may need to be overriden as well.
470      * @param request the current action request
471      * @param response the current action response
472      * @see #PARAM_TARGET
473      * @see #getTargetPage(PortletRequest, int)
474      * @see #getTargetPage(PortletRequest, Object, Errors, int)
475      * @see ActionResponse#setRenderParameter
476      */

477     protected void setTargetRenderParameter(ActionRequest request, ActionResponse response) {
478         try {
479             Iterator JavaDoc it = PortletUtils.getParametersStartingWith(request, PARAM_TARGET).entrySet().iterator();
480             while (it.hasNext()) {
481                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
482                 String JavaDoc param = PARAM_TARGET + (String JavaDoc) entry.getKey();
483                 Object JavaDoc value = entry.getValue();
484                 if (logger.isDebugEnabled()) logger.debug("Setting target render parameter [" + param + "]");
485                 if (value instanceof String JavaDoc) response.setRenderParameter(param, (String JavaDoc) value);
486                 if (value instanceof String JavaDoc[]) response.setRenderParameter(param, (String JavaDoc[]) value);
487             }
488         }
489         catch (IllegalStateException JavaDoc ex) {
490             // ignore in case sendRedirect was already set
491
}
492     }
493
494     /**
495      * Pass the the parameter that indicates a finish request forward to the
496      * render phase. If the <code>isFinishRequest</code> method
497      * was overridden, this may need to be overriden as well.
498      * @param request the current action request
499      * @param response the current action response
500      * @see #PARAM_FINISH
501      * @see #isFinishRequest
502      * @see ActionResponse#setRenderParameter
503      */

504     protected void setFinishRenderParameter(ActionRequest request, ActionResponse response) {
505         if (logger.isDebugEnabled())
506             logger.debug("Setting cancel render parameter [" + PARAM_FINISH + "]");
507         try {
508             String JavaDoc name = PortletUtils.getSubmitParameter(request, PARAM_FINISH);
509             if (name != null)
510                 response.setRenderParameter(name, request.getParameter(name));
511         }
512         catch (IllegalStateException JavaDoc ex) {
513             // ignore in case sendRedirect was already set
514
}
515     }
516
517     /**
518      * Pass the the parameter that indicates a cancel request forward to the
519      * render phase. If the <code>isCancelRequest</code> method
520      * was overridden, this may need to be overriden as well.
521      * @param request the current action request
522      * @param response the current action response
523      * @see #PARAM_CANCEL
524      * @see #isCancelRequest
525      * @see ActionResponse#setRenderParameter
526      */

527     protected void setCancelRenderParameter(ActionRequest request, ActionResponse response) {
528         if (logger.isDebugEnabled())
529             logger.debug("Setting cancel render parameter [" + PARAM_CANCEL + "]");
530         try {
531             String JavaDoc name = PortletUtils.getSubmitParameter(request, PARAM_CANCEL);
532             if (name != null)
533                 response.setRenderParameter(name, request.getParameter(name));
534         }
535         catch (IllegalStateException JavaDoc ex) {
536             // ignore in case sendRedirect was already set
537
}
538     }
539
540     /**
541      * Handle an invalid submit request, e.g. when in session form mode but no form object
542      * was found in the session (like in case of an invalid resubmit by the browser).
543      * <p>Default implementation for wizard form controllers simply shows the initial page
544      * of a new wizard form. If you want to show some "invalid submit" message, you need
545      * to override this method.
546      * @param request current portlet render request
547      * @param response current portlet render response
548      * @return a prepared view, or null if handled directly
549      * @throws Exception in case of errors
550      * @see #showNewForm
551      * @see #setBindOnNewForm
552      * @see #handleInvalidSubmit
553      */

554     protected ModelAndView renderInvalidSubmit(RenderRequest request, RenderResponse response)
555             throws Exception JavaDoc {
556
557         return showNewForm(request, response);
558     }
559
560     /**
561      * Handle an invalid submit request, e.g. when in session form mode but no form object
562      * was found in the session (like in case of an invalid resubmit by the browser).
563      * <p>Default implementation for wizard form controllers simply shows the initial page
564      * of a new wizard form, so here in the action phase this method does nothing. If you
565      * want to take some action on an invalid submit, you need to override this method.
566      * @param request current portlet action request
567      * @param response current portlet action response
568      * @throws Exception in case of errors
569      * @see #renderInvalidSubmit
570      */

571     protected void handleInvalidSubmit(ActionRequest request, ActionResponse response) throws Exception JavaDoc {
572     }
573
574     /**
575      * Apply wizard workflow: finish, cancel, page change.
576      * @see #processFormSubmission
577      */

578     protected final ModelAndView renderFormSubmission(RenderRequest request, RenderResponse response, Object JavaDoc command, BindException errors)
579             throws Exception JavaDoc {
580
581         int currentPage = getCurrentPage(request);
582         String JavaDoc pageAttrName = getPageSessionAttributeName(request);
583         request.setAttribute(pageAttrName, new Integer JavaDoc(currentPage));
584
585         // cancel?
586
if (isCancelRequest(request)) {
587             if (logger.isDebugEnabled()) {
588                 logger.debug("Cancelling wizard for form bean '" + getCommandName() + "'");
589             }
590             return renderCancel(request, response, command, errors);
591         }
592
593         // finish?
594
if (isFinishRequest(request)) {
595             if (logger.isDebugEnabled()) {
596                 logger.debug("Finishing wizard for form bean '" + getCommandName() + "'");
597             }
598             return renderValidatePagesAndFinish(request, response, command, errors, currentPage);
599         }
600
601         // Normal submit: show specified target page.
602
int targetPage = getTargetPage(request, command, errors, currentPage);
603         if (logger.isDebugEnabled()) {
604             logger.debug("Target page " + targetPage + " requested");
605         }
606         if (targetPage != currentPage) {
607             if (!errors.hasErrors() || (this.allowDirtyBack && targetPage < currentPage) ||
608                     (this.allowDirtyForward && targetPage > currentPage)) {
609                 // Allowed to go to target page.
610
return showPage(request, errors, targetPage);
611             }
612         }
613
614         // Show current page again
615
return showPage(request, errors, currentPage);
616     }
617
618
619     /**
620      * Apply wizard workflow: finish, cancel, page change.
621      * @see #renderFormSubmission
622      */

623     protected final void processFormSubmission(
624             ActionRequest request, ActionResponse response, Object JavaDoc command, BindException errors)
625             throws Exception JavaDoc {
626
627         int currentPage = getCurrentPage(request);
628         // Remove page session attribute, provide copy as request attribute.
629
String JavaDoc pageAttrName = getPageSessionAttributeName(request);
630         if (isSessionForm()) {
631             if (logger.isDebugEnabled()) {
632                 logger.debug("Removing page session attribute [" + pageAttrName + "]");
633             }
634             request.getPortletSession().removeAttribute(pageAttrName);
635         }
636         request.setAttribute(pageAttrName, new Integer JavaDoc(currentPage));
637         
638         // cancel?
639
if (isCancelRequest(request)) {
640             if (logger.isDebugEnabled()) {
641                 logger.debug("Cancelling wizard for form bean '" + getCommandName() + "'");
642             }
643             setPageRenderParameter(response, currentPage);
644             setCancelRenderParameter(request, response);
645             processCancel(request, response, command, errors);
646             return;
647         }
648
649         // finish?
650
if (isFinishRequest(request)) {
651             if (logger.isDebugEnabled()) {
652                 logger.debug("Finishing wizard for form bean '" + getCommandName() + "'");
653             }
654             if (!isRedirectAction()) {
655                 setPageRenderParameter(response, currentPage);
656                 setFinishRenderParameter(request, response);
657             }
658             validatePagesAndFinish(request, response, command, errors, currentPage);
659             return;
660         }
661
662         // Normal submit: validate current page
663
if (!suppressValidation(request)) {
664             if (logger.isDebugEnabled()) {
665                 logger.debug("Validating wizard page " + currentPage + " for form bean '" + getCommandName() + "'");
666             }
667             validatePage(command, errors, currentPage, false);
668         }
669
670         setPageRenderParameter(response, currentPage);
671         setTargetRenderParameter(request, response);
672         passRenderParameters(request, response);
673
674         // Give subclasses a change to perform custom post-procession
675
// of the current page and its command object.
676
postProcessPage(request, command, errors, currentPage);
677
678     }
679
680     /**
681      * Return the current page number. Used by processFormSubmission.
682      * <p>The default implementation checks the page session attribute.
683      * Subclasses can override this for customized page determination.
684      * @see #processFormSubmission
685      * @see #getPageSessionAttributeName
686      */

687     protected int getCurrentPage(PortletRequest request) {
688         // Check for overriding attribute in request.
689
String JavaDoc pageAttrName = getPageSessionAttributeName(request);
690         Integer JavaDoc pageAttr = (Integer JavaDoc) request.getAttribute(pageAttrName);
691         if (pageAttr != null) {
692             return pageAttr.intValue();
693         }
694         // Check for explicit request parameter.
695
String JavaDoc pageParam = request.getParameter(PARAM_PAGE);
696         if (pageParam != null) {
697             return Integer.parseInt(pageParam);
698         }
699         // Check for original attribute in session.
700
if (isSessionForm()) {
701             pageAttr = (Integer JavaDoc) request.getPortletSession().getAttribute(pageAttrName);
702             if (pageAttr != null) {
703                 return pageAttr.intValue();
704             }
705         }
706         throw new IllegalStateException JavaDoc("Page attribute [" + pageAttrName + "] neither found in session nor in request");
707     }
708
709     /**
710      * Determine whether the incoming request is a request to finish the
711      * processing of the current form.
712      * <p>By default, this method returns <code>true</code> if a parameter
713      * matching the "_finish" key is present in the request, otherwise it
714      * returns <code>false</code>. Subclasses may override this method
715      * to provide custom logic to detect a finish request.
716      * <p>The parameter is recognized both when sent as a plain parameter
717      * ("_finish") or when triggered by an image button ("_finish.x").
718      * @param request current portlet request
719      * @see #PARAM_FINISH
720      */

721     protected boolean isFinishRequest(PortletRequest request) {
722         return PortletUtils.hasSubmitParameter(request, PARAM_FINISH);
723     }
724
725     /**
726      * Determine whether the incoming request is a request to cancel the
727      * processing of the current form.
728      * <p>By default, this method returns <code>true</code> if a parameter
729      * matching the "_cancel" key is present in the request, otherwise it
730      * returns <code>false</code>. Subclasses may override this method
731      * to provide custom logic to detect a cancel request.
732      * <p>The parameter is recognized both when sent as a plain parameter
733      * ("_cancel") or when triggered by an image button ("_cancel.x").
734      * @param request current portlet request
735      * @see #PARAM_CANCEL
736      */

737     protected boolean isCancelRequest(PortletRequest request) {
738         return PortletUtils.hasSubmitParameter(request, PARAM_CANCEL);
739     }
740
741     /**
742      * Return the target page specified in the request.
743      * <p>Default implementation delegates to
744      * <code>getTargetPage(PortletRequest, int)</code>.
745      * Subclasses can override this for customized target page determination.
746      * @param request current portlet request
747      * @param command form object with request parameters bound onto it
748      * @param errors validation errors holder
749      * @param currentPage the current page, to be returned as fallback
750      * if no target page specified
751      * @return the page specified in the request, or current page if not found
752      * @see #getTargetPage(PortletRequest, int)
753      */

754     protected int getTargetPage(PortletRequest request, Object JavaDoc command, Errors errors, int currentPage) {
755         return getTargetPage(request, currentPage);
756     }
757
758     /**
759      * Return the target page specified in the request.
760      * <p>Default implementation examines "_target" parameter (e.g. "_target1").
761      * Subclasses can override this for customized target page determination.
762      * @param request current portlet request
763      * @param currentPage the current page, to be returned as fallback
764      * if no target page specified
765      * @return the page specified in the request, or current page if not found
766      * @see #PARAM_TARGET
767      */

768     protected int getTargetPage(PortletRequest request, int currentPage) {
769         Enumeration JavaDoc paramNames = request.getParameterNames();
770         while (paramNames.hasMoreElements()) {
771             String JavaDoc paramName = (String JavaDoc) paramNames.nextElement();
772             if (paramName.startsWith(PARAM_TARGET)) {
773                 for (int i = 0; i < WebUtils.SUBMIT_IMAGE_SUFFIXES.length; i++) {
774                     String JavaDoc suffix = WebUtils.SUBMIT_IMAGE_SUFFIXES[i];
775                     if (paramName.endsWith(suffix)) {
776                         paramName = paramName.substring(0, paramName.length() - suffix.length());
777                     }
778                 }
779                 return Integer.parseInt(paramName.substring(PARAM_TARGET.length()));
780             }
781         }
782         return currentPage;
783     }
784
785     /**
786      * Validate all pages and process finish.
787      * If there are page validation errors, show the corresponding view page.
788      * @see #validatePagesAndFinish
789      */

790     private ModelAndView renderValidatePagesAndFinish(
791             RenderRequest request, RenderResponse response, Object JavaDoc command, BindException errors, int currentPage)
792             throws Exception JavaDoc {
793
794         // In case of any errors -> show current page.
795
if (errors.hasErrors())
796             return showPage(request, errors, currentPage);
797
798         // No remaining errors -> proceed with finish.
799
return renderFinish(request, response, command, errors);
800     }
801
802     /**
803      * Validate all pages and process finish.
804      * If there are page validation errors, show the corresponding view page.
805      * @see #renderValidatePagesAndFinish
806      */

807     private void validatePagesAndFinish(
808             ActionRequest request, ActionResponse response, Object JavaDoc command, BindException errors, int currentPage)
809             throws Exception JavaDoc {
810
811         // In case of binding errors -> show current page.
812
if (errors.hasErrors()) {
813             setPageRenderParameter(response, currentPage);
814             passRenderParameters(request, response);
815             return;
816         }
817
818         if (!suppressValidation(request)) {
819             // In case of remaining errors on a page -> show the page.
820
for (int page = 0; page < getPageCount(request, command); page++) {
821                 validatePage(command, errors, page, true);
822                 if (errors.hasErrors()) {
823                     setPageRenderParameter(response, currentPage);
824                     passRenderParameters(request, response);
825                     return;
826                 }
827             }
828         }
829
830         // No remaining errors -> proceed with finish.
831
if (!isRedirectAction())
832             setPageRenderParameter(response, currentPage);
833         processFinish(request, response, command, errors);
834
835     }
836
837     /**
838      * Template method for custom validation logic for individual pages.
839      * Default implementation calls <code>validatePage(command, errors, page)</code>.
840      * <p>Implementations will typically call fine-granular <code>validateXXX</code>
841      * methods of this instance's Validator, combining them to validation of the
842      * corresponding pages. The Validator's default <code>validate</code> method
843      * will not be called by a wizard form controller!
844      * @param command form object with the current wizard state
845      * @param errors validation errors holder
846      * @param page number of page to validate
847      * @param finish whether this method is called during final revalidation on finish
848      * (else, it is called for validating the current page)
849      * @see #validatePage(Object, Errors, int)
850      * @see org.springframework.validation.Validator#validate
851      */

852     protected void validatePage(Object JavaDoc command, Errors errors, int page, boolean finish) {
853         validatePage(command, errors, page);
854     }
855
856     /**
857      * Template method for custom validation logic for individual pages.
858      * Default implementation is empty.
859      * <p>Implementations will typically call fine-granular validateXXX methods of this
860      * instance's validator, combining them to validation of the corresponding pages.
861      * The validator's default <code>validate</code> method will not be called by a
862      * wizard form controller!
863      * @param command form object with the current wizard state
864      * @param errors validation errors holder
865      * @param page number of page to validate
866      * @see org.springframework.validation.Validator#validate
867      */

868     protected void validatePage(Object JavaDoc command, Errors errors, int page) {
869     }
870
871     /**
872      * Post-process the given page after binding and validation, potentially
873      * updating its command object. The passed-in request might contain special
874      * parameters sent by the page.
875      * <p>Only invoked when displaying another page or the same page again,
876      * not when finishing or cancelling.
877      * @param request current action request
878      * @param command form object with request parameters bound onto it
879      * @param errors validation errors holder
880      * @param page number of page to post-process
881      * @throws Exception in case of invalid state or arguments
882      */

883     protected void postProcessPage(ActionRequest request, Object JavaDoc command, Errors errors, int page)
884             throws Exception JavaDoc {
885     }
886
887     /**
888      * Template method for the render phase of the finish action of this wizard.
889      * <p>Default implementation throws a PortletException, saying that a finish
890      * render request is not supported by this controller. Thus, you do not need to
891      * implement this template method if you do not need to render after a finish.
892      * <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
893      * with the command and the Errors instance, under the specified command name,
894      * as expected by the "spring:bind" tag.
895      * @param request current portlet render request
896      * @param response current portlet render response
897      * @param command form object with the current wizard state
898      * @param errors validation errors holder
899      * @return the finish view
900      * @throws Exception in case of invalid state or arguments
901      * @see #processFinish
902      * @see org.springframework.validation.Errors
903      * @see org.springframework.validation.BindException#getModel
904      */

905     protected ModelAndView renderFinish(
906             RenderRequest request, RenderResponse response, Object JavaDoc command, BindException errors)
907             throws Exception JavaDoc {
908
909         throw new PortletException("Wizard form controller class [" + getClass().getName() + "] does not support a finish render request");
910     }
911
912     /**
913      * Template method for the action phase of the finish action of this wizard.
914      * <p>Default implementation throws a PortletException, saying that a finish
915      * action request is not supported by this controller. You will almost certainly
916      * need to override this method.
917      * @param request current portlet action request
918      * @param response current portlet action response
919      * @param command form object with the current wizard state
920      * @param errors validation errors holder
921      * @throws Exception in case of invalid state or arguments
922      * @see #renderFinish
923      * @see org.springframework.validation.Errors
924      */

925     protected void processFinish(
926             ActionRequest request, ActionResponse response, Object JavaDoc command, BindException errors)
927             throws Exception JavaDoc {
928
929         throw new PortletException(
930                 "Wizard form controller class [" + getClass().getName() + "] does not support a finish action request");
931     }
932
933     /**
934      * Template method for the render phase of the cancel action of this wizard.
935      * <p>Default implementation throws a PortletException, saying that a cancel
936      * render request is not supported by this controller. Thus, you do not need to
937      * implement this template method if you do not support a cancel operation.
938      * <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
939      * with the command and the Errors instance, under the specified command name,
940      * as expected by the "spring:bind" tag.
941      * @param request current portlet render request
942      * @param response current portlet render response
943      * @param command form object with the current wizard state
944      * @param errors Errors instance containing errors
945      * @return the cancellation view
946      * @throws Exception in case of invalid state or arguments
947      * @see #processCancel
948      * @see org.springframework.validation.Errors
949      * @see org.springframework.validation.BindException#getModel
950      */

951     protected ModelAndView renderCancel(
952             RenderRequest request, RenderResponse response, Object JavaDoc command, BindException errors)
953             throws Exception JavaDoc {
954
955         throw new PortletException(
956                 "Wizard form controller class [" + getClass().getName() + "] does not support a cancel render request");
957     }
958
959     /**
960      * Template method for the action phase of the cancel action of this wizard.
961      * <p>Default implementation throws a PortletException, saying that a cancel
962      * action request is not supported by this controller. Thus, you do not need to
963      * implement this template method if you do not support a cancel operation.
964      * @param request current portlet action request
965      * @param response current portlet action response
966      * @param command form object with the current wizard state
967      * @param errors Errors instance containing errors
968      * @throws Exception in case of invalid state or arguments
969      * @see #renderCancel
970      * @see org.springframework.validation.Errors
971      */

972     protected void processCancel(
973             ActionRequest request, ActionResponse response, Object JavaDoc command, BindException errors)
974             throws Exception JavaDoc {
975
976         throw new PortletException(
977                 "Wizard form controller class [" + getClass().getName() + "] does not support a cancel action request");
978     }
979
980 }
981
Popular Tags