001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.web.servlet.mvc;
018:
019: import java.util.Enumeration;
020: import java.util.HashMap;
021: import java.util.Map;
022:
023: import javax.servlet.ServletException;
024: import javax.servlet.http.HttpServletRequest;
025: import javax.servlet.http.HttpServletResponse;
026:
027: import org.springframework.validation.BindException;
028: import org.springframework.validation.Errors;
029: import org.springframework.web.servlet.ModelAndView;
030: import org.springframework.web.util.WebUtils;
031:
032: /**
033: * Form controller for typical wizard-style workflows.
034: *
035: * <p>In contrast to classic forms, wizards have more than one form view page.
036: * Therefore, there are various actions instead of one single submit action:
037: * <ul>
038: * <li>finish: trying to leave the wizard successfully, that is, perform its
039: * final action, and thus requiring a valid state;
040: * <li>cancel: leaving the wizard without performing its final action, and
041: * thus without regard to the validity of its current state;
042: * <li>page change: showing another wizard page, e.g. the next or previous
043: * one, with regard to "dirty back" and "dirty forward".
044: * </ul>
045: *
046: * <p>Finish and cancel actions can be triggered by request parameters, named
047: * PARAM_FINISH ("_finish") and PARAM_CANCEL ("_cancel"), ignoring parameter
048: * values to allow for HTML buttons. The target page for page changes can be
049: * specified by PARAM_TARGET, appending the page number to the parameter name
050: * (e.g. "_target1"). The action parameters are recognized when triggered by
051: * image buttons too (via "_finish.x", "_abort.x", or "_target1.x").
052: *
053: * <p>The current page number will be stored in the session. It can also be
054: * specified as request parameter PARAM_PAGE, to properly handle usage of
055: * the back button in a browser: In this case, a submission always contains
056: * the correct page number, even if the user submitted from an old view.
057: *
058: * <p>The page can only be changed if it validates correctly, except if a
059: * "dirty back" or "dirty forward" is allowed. At finish, all pages get
060: * validated again to guarantee a consistent state.
061: *
062: * <p>Note that a validator's default validate method is not executed when using
063: * this class! Rather, the {@link #validatePage} implementation should call
064: * special <code>validateXXX</code> methods that the validator needs to provide,
065: * validating certain pieces of the object. These can be combined to validate
066: * the elements of individual pages.
067: *
068: * <p>Note: Page numbering starts with 0, to be able to pass an array
069: * consisting of the corresponding view names to the "pages" bean property.
070: *
071: * @author Juergen Hoeller
072: * @since 25.04.2003
073: * @see #setPages
074: * @see #validatePage
075: * @see #processFinish
076: * @see #processCancel
077: */
078: public abstract class AbstractWizardFormController extends
079: AbstractFormController {
080:
081: /**
082: * Parameter triggering the finish action.
083: * Can be called from any wizard page!
084: */
085: public static final String PARAM_FINISH = "_finish";
086:
087: /**
088: * Parameter triggering the cancel action.
089: * Can be called from any wizard page!
090: */
091: public static final String PARAM_CANCEL = "_cancel";
092:
093: /**
094: * Parameter specifying the target page,
095: * appending the page number to the name.
096: */
097: public static final String PARAM_TARGET = "_target";
098:
099: /**
100: * Parameter specifying the current page as value. Not necessary on
101: * form pages, but allows to properly handle usage of the back button.
102: * @see #setPageAttribute
103: */
104: public static final String PARAM_PAGE = "_page";
105:
106: private String[] pages;
107:
108: private String pageAttribute;
109:
110: private boolean allowDirtyBack = true;
111:
112: private boolean allowDirtyForward = false;
113:
114: /**
115: * Create a new AbstractWizardFormController.
116: * <p>"sessionForm" is automatically turned on, "validateOnBinding"
117: * turned off, and "cacheSeconds" set to 0 by the base class
118: * (-> no caching for all form controllers).
119: */
120: public AbstractWizardFormController() {
121: // AbstractFormController sets default cache seconds to 0.
122: super ();
123:
124: // Always needs session to keep data from all pages.
125: setSessionForm(true);
126:
127: // Never validate everything on binding ->
128: // wizards validate individual pages.
129: setValidateOnBinding(false);
130: }
131:
132: /**
133: * Set the wizard pages, i.e. the view names for the pages.
134: * The array index is interpreted as page number.
135: * @param pages view names for the pages
136: */
137: public final void setPages(String[] pages) {
138: if (pages == null || pages.length == 0) {
139: throw new IllegalArgumentException(
140: "No wizard pages defined");
141: }
142: this .pages = pages;
143: }
144:
145: /**
146: * Return the wizard pages, i.e. the view names for the pages.
147: * The array index corresponds to the page number.
148: * <p>Note that a concrete wizard form controller might override
149: * {@link #getViewName(HttpServletRequest, Object, int)} to
150: * determine the view name for each page dynamically.
151: * @see #getViewName(javax.servlet.http.HttpServletRequest, Object, int)
152: */
153: public final String[] getPages() {
154: return pages;
155: }
156:
157: /**
158: * Return the number of wizard pages.
159: * Useful to check whether the last page has been reached.
160: * <p>Note that a concrete wizard form controller might override
161: * {@link #getPageCount(HttpServletRequest, Object)} to determine
162: * the page count dynamically. The default implementation of that extended
163: * <code>getPageCount</code> variant returns the static page count as
164: * determined by this <code>getPageCount()</code> method.
165: * @see #getPageCount(javax.servlet.http.HttpServletRequest, Object)
166: */
167: protected final int getPageCount() {
168: return this .pages.length;
169: }
170:
171: /**
172: * Set the name of the page attribute in the model, containing
173: * an Integer with the current page number.
174: * <p>This will be necessary for single views rendering multiple view pages.
175: * It also allows for specifying the optional "_page" parameter.
176: * @param pageAttribute name of the page attribute
177: * @see #PARAM_PAGE
178: */
179: public final void setPageAttribute(String pageAttribute) {
180: this .pageAttribute = pageAttribute;
181: }
182:
183: /**
184: * Return the name of the page attribute in the model.
185: */
186: public final String getPageAttribute() {
187: return pageAttribute;
188: }
189:
190: /**
191: * Set if "dirty back" is allowed, that is, if moving to a former wizard
192: * page is allowed in case of validation errors for the current page.
193: * @param allowDirtyBack if "dirty back" is allowed
194: */
195: public final void setAllowDirtyBack(boolean allowDirtyBack) {
196: this .allowDirtyBack = allowDirtyBack;
197: }
198:
199: /**
200: * Return whether "dirty back" is allowed.
201: */
202: public final boolean isAllowDirtyBack() {
203: return allowDirtyBack;
204: }
205:
206: /**
207: * Set if "dirty forward" is allowed, that is, if moving to a later wizard
208: * page is allowed in case of validation errors for the current page.
209: * @param allowDirtyForward if "dirty forward" is allowed
210: */
211: public final void setAllowDirtyForward(boolean allowDirtyForward) {
212: this .allowDirtyForward = allowDirtyForward;
213: }
214:
215: /**
216: * Return whether "dirty forward" is allowed.
217: */
218: public final boolean isAllowDirtyForward() {
219: return allowDirtyForward;
220: }
221:
222: /**
223: * Calls page-specific onBindAndValidate method.
224: */
225: protected final void onBindAndValidate(HttpServletRequest request,
226: Object command, BindException errors) throws Exception {
227:
228: onBindAndValidate(request, command, errors,
229: getCurrentPage(request));
230: }
231:
232: /**
233: * Callback for custom post-processing in terms of binding and validation.
234: * Called on each submit, after standard binding but before page-specific
235: * validation of this wizard form controller.
236: * <p>Note: AbstractWizardFormController does not perform standand
237: * validation on binding but rather applies page-specific validation
238: * on processing the form submission.
239: * @param request current HTTP request
240: * @param command bound command
241: * @param errors Errors instance for additional custom validation
242: * @param page current wizard page
243: * @throws Exception in case of invalid state or arguments
244: * @see #bindAndValidate
245: * @see #processFormSubmission
246: * @see org.springframework.validation.Errors
247: */
248: protected void onBindAndValidate(HttpServletRequest request,
249: Object command, BindException errors, int page)
250: throws Exception {
251: }
252:
253: /**
254: * Consider an explicit finish or cancel request as a form submission too.
255: * @see #isFinishRequest(javax.servlet.http.HttpServletRequest)
256: * @see #isCancelRequest(javax.servlet.http.HttpServletRequest)
257: */
258: protected boolean isFormSubmission(HttpServletRequest request) {
259: return super .isFormSubmission(request)
260: || isFinishRequest(request) || isCancelRequest(request);
261: }
262:
263: /**
264: * Calls page-specific referenceData method.
265: */
266: protected final Map referenceData(HttpServletRequest request,
267: Object command, Errors errors) throws Exception {
268:
269: return referenceData(request, command, errors,
270: getCurrentPage(request));
271: }
272:
273: /**
274: * Create a reference data map for the given request, consisting of
275: * bean name/bean instance pairs as expected by ModelAndView.
276: * <p>The default implementation delegates to referenceData(HttpServletRequest, int).
277: * Subclasses can override this to set reference data used in the view.
278: * @param request current HTTP request
279: * @param command form object with request parameters bound onto it
280: * @param errors validation errors holder
281: * @param page current wizard page
282: * @return a Map with reference data entries, or <code>null</code> if none
283: * @throws Exception in case of invalid state or arguments
284: * @see #referenceData(HttpServletRequest, int)
285: * @see ModelAndView
286: */
287: protected Map referenceData(HttpServletRequest request,
288: Object command, Errors errors, int page) throws Exception {
289:
290: return referenceData(request, page);
291: }
292:
293: /**
294: * Create a reference data map for the given request, consisting of
295: * bean name/bean instance pairs as expected by ModelAndView.
296: * <p>The default implementation returns <code>null</code>.
297: * Subclasses can override this to set reference data used in the view.
298: * @param request current HTTP request
299: * @param page current wizard page
300: * @return a Map with reference data entries, or <code>null</code> if none
301: * @throws Exception in case of invalid state or arguments
302: * @see ModelAndView
303: */
304: protected Map referenceData(HttpServletRequest request, int page)
305: throws Exception {
306: return null;
307: }
308:
309: /**
310: * Show first page as form view.
311: */
312: protected final ModelAndView showForm(HttpServletRequest request,
313: HttpServletResponse response, BindException errors)
314: throws Exception {
315:
316: return showPage(request, errors, getInitialPage(request, errors
317: .getTarget()));
318: }
319:
320: /**
321: * Prepare the form model and view, including reference and error data,
322: * for the given page. Can be used in {@link #processFinish} implementations,
323: * to show the corresponding page in case of validation errors.
324: * @param request current HTTP request
325: * @param errors validation errors holder
326: * @param page number of page to show
327: * @return the prepared form view
328: * @throws Exception in case of invalid state or arguments
329: */
330: protected final ModelAndView showPage(HttpServletRequest request,
331: BindException errors, int page) throws Exception {
332:
333: if (page >= 0
334: && page < getPageCount(request, errors.getTarget())) {
335: if (logger.isDebugEnabled()) {
336: logger.debug("Showing wizard page " + page
337: + " for form bean '" + getCommandName() + "'");
338: }
339:
340: // Set page session attribute, expose overriding request attribute.
341: Integer pageInteger = new Integer(page);
342: String pageAttrName = getPageSessionAttributeName(request);
343: if (isSessionForm()) {
344: if (logger.isDebugEnabled()) {
345: logger.debug("Setting page session attribute ["
346: + pageAttrName + "] to: " + pageInteger);
347: }
348: request.getSession().setAttribute(pageAttrName,
349: pageInteger);
350: }
351: request.setAttribute(pageAttrName, pageInteger);
352:
353: // Set page request attribute for evaluation by views.
354: Map controlModel = new HashMap();
355: if (this .pageAttribute != null) {
356: controlModel.put(this .pageAttribute, new Integer(page));
357: }
358: String viewName = getViewName(request, errors.getTarget(),
359: page);
360: return showForm(request, errors, viewName, controlModel);
361: }
362:
363: else {
364: throw new ServletException("Invalid wizard page number: "
365: + page);
366: }
367: }
368:
369: /**
370: * Return the page count for this wizard form controller.
371: * The default implementation delegates to {@link #getPageCount()}.
372: * <p>Can be overridden to dynamically adapt the page count.
373: * @param request current HTTP request
374: * @param command the command object as returned by formBackingObject
375: * @return the current page count
376: * @see #getPageCount
377: */
378: protected int getPageCount(HttpServletRequest request,
379: Object command) {
380: return getPageCount();
381: }
382:
383: /**
384: * Return the name of the view for the specified page of this wizard form controller.
385: * <p>The default implementation takes the view name from the {@link #getPages()} array.
386: * <p>Can be overridden to dynamically switch the page view or to return view names
387: * for dynamically defined pages.
388: * @param request current HTTP request
389: * @param command the command object as returned by formBackingObject
390: * @return the current page count
391: * @see #getPageCount
392: */
393: protected String getViewName(HttpServletRequest request,
394: Object command, int page) {
395: return getPages()[page];
396: }
397:
398: /**
399: * Return the initial page of the wizard, that is, the page shown at wizard startup.
400: * <p>The default implementation delegates to {@link #getInitialPage(HttpServletRequest)}.
401: * @param request current HTTP request
402: * @param command the command object as returned by formBackingObject
403: * @return the initial page number
404: * @see #getInitialPage(HttpServletRequest)
405: * @see #formBackingObject
406: */
407: protected int getInitialPage(HttpServletRequest request,
408: Object command) {
409: return getInitialPage(request);
410: }
411:
412: /**
413: * Return the initial page of the wizard, that is, the page shown at wizard startup.
414: * <p>The default implementation returns 0 for first page.
415: * @param request current HTTP request
416: * @return the initial page number
417: */
418: protected int getInitialPage(HttpServletRequest request) {
419: return 0;
420: }
421:
422: /**
423: * Return the name of the HttpSession attribute that holds the page object
424: * for this wizard form controller.
425: * <p>The default implementation delegates to the {@link #getPageSessionAttributeName()}
426: * variant without arguments.
427: * @param request current HTTP request
428: * @return the name of the form session attribute, or <code>null</code> if not in session form mode
429: * @see #getPageSessionAttributeName
430: * @see #getFormSessionAttributeName(javax.servlet.http.HttpServletRequest)
431: * @see javax.servlet.http.HttpSession#getAttribute
432: */
433: protected String getPageSessionAttributeName(
434: HttpServletRequest request) {
435: return getPageSessionAttributeName();
436: }
437:
438: /**
439: * Return the name of the HttpSession attribute that holds the page object
440: * for this wizard form controller.
441: * <p>Default is an internal name, of no relevance to applications, as the form
442: * session attribute is not usually accessed directly. Can be overridden to use
443: * an application-specific attribute name, which allows other code to access
444: * the session attribute directly.
445: * @return the name of the page session attribute
446: * @see #getFormSessionAttributeName
447: * @see javax.servlet.http.HttpSession#getAttribute
448: */
449: protected String getPageSessionAttributeName() {
450: return getClass().getName() + ".PAGE." + getCommandName();
451: }
452:
453: /**
454: * Handle an invalid submit request, e.g. when in session form mode but no form object
455: * was found in the session (like in case of an invalid resubmit by the browser).
456: * <p>The default implementation for wizard form controllers simply shows the initial page
457: * of a new wizard form. If you want to show some "invalid submit" message, you need
458: * to override this method.
459: * @param request current HTTP request
460: * @param response current HTTP response
461: * @return a prepared view, or <code>null</code> if handled directly
462: * @throws Exception in case of errors
463: * @see #showNewForm
464: * @see #setBindOnNewForm
465: */
466: protected ModelAndView handleInvalidSubmit(
467: HttpServletRequest request, HttpServletResponse response)
468: throws Exception {
469:
470: return showNewForm(request, response);
471: }
472:
473: /**
474: * Apply wizard workflow: finish, cancel, page change.
475: */
476: protected final ModelAndView processFormSubmission(
477: HttpServletRequest request, HttpServletResponse response,
478: Object command, BindException errors) throws Exception {
479:
480: int currentPage = getCurrentPage(request);
481: // Remove page session attribute, provide copy as request attribute.
482: String pageAttrName = getPageSessionAttributeName(request);
483: if (isSessionForm()) {
484: if (logger.isDebugEnabled()) {
485: logger.debug("Removing page session attribute ["
486: + pageAttrName + "]");
487: }
488: request.getSession().removeAttribute(pageAttrName);
489: }
490: request.setAttribute(pageAttrName, new Integer(currentPage));
491:
492: // cancel?
493: if (isCancelRequest(request)) {
494: if (logger.isDebugEnabled()) {
495: logger.debug("Cancelling wizard for form bean '"
496: + getCommandName() + "'");
497: }
498: return processCancel(request, response, command, errors);
499: }
500:
501: // finish?
502: if (isFinishRequest(request)) {
503: if (logger.isDebugEnabled()) {
504: logger.debug("Finishing wizard for form bean '"
505: + getCommandName() + "'");
506: }
507: return validatePagesAndFinish(request, response, command,
508: errors, currentPage);
509: }
510:
511: // Normal submit: validate current page and show specified target page.
512: if (!suppressValidation(request, command, errors)) {
513: if (logger.isDebugEnabled()) {
514: logger.debug("Validating wizard page " + currentPage
515: + " for form bean '" + getCommandName() + "'");
516: }
517: validatePage(command, errors, currentPage, false);
518: }
519:
520: // Give subclasses a change to perform custom post-procession
521: // of the current page and its command object.
522: postProcessPage(request, command, errors, currentPage);
523:
524: int targetPage = getTargetPage(request, command, errors,
525: currentPage);
526: if (logger.isDebugEnabled()) {
527: logger.debug("Target page " + targetPage + " requested");
528: }
529: if (targetPage != currentPage) {
530: if (!errors.hasErrors()
531: || (this .allowDirtyBack && targetPage < currentPage)
532: || (this .allowDirtyForward && targetPage > currentPage)) {
533: // Allowed to go to target page.
534: return showPage(request, errors, targetPage);
535: }
536: }
537:
538: // Show current page again.
539: return showPage(request, errors, currentPage);
540: }
541:
542: /**
543: * Return the current page number. Used by processFormSubmission.
544: * <p>The default implementation checks the page session attribute.
545: * Subclasses can override this for customized page determination.
546: * @see #processFormSubmission
547: * @see #getPageSessionAttributeName
548: */
549: protected int getCurrentPage(HttpServletRequest request) {
550: // Check for overriding attribute in request.
551: String pageAttrName = getPageSessionAttributeName(request);
552: Integer pageAttr = (Integer) request.getAttribute(pageAttrName);
553: if (pageAttr != null) {
554: return pageAttr.intValue();
555: }
556: // Check for explicit request parameter.
557: String pageParam = request.getParameter(PARAM_PAGE);
558: if (pageParam != null) {
559: return Integer.parseInt(pageParam);
560: }
561: // Check for original attribute in session.
562: if (isSessionForm()) {
563: pageAttr = (Integer) request.getSession().getAttribute(
564: pageAttrName);
565: if (pageAttr != null) {
566: return pageAttr.intValue();
567: }
568: }
569: throw new IllegalStateException("Page attribute ["
570: + pageAttrName
571: + "] neither found in session nor in request");
572: }
573:
574: /**
575: * Determine whether the incoming request is a request to finish the
576: * processing of the current form.
577: * <p>By default, this method returns <code>true</code> if a parameter
578: * matching the "_finish" key is present in the request, otherwise it
579: * returns <code>false</code>. Subclasses may override this method
580: * to provide custom logic to detect a finish request.
581: * <p>The parameter is recognized both when sent as a plain parameter
582: * ("_finish") or when triggered by an image button ("_finish.x").
583: * @param request current HTTP request
584: * @see #PARAM_FINISH
585: */
586: protected boolean isFinishRequest(HttpServletRequest request) {
587: return WebUtils.hasSubmitParameter(request, PARAM_FINISH);
588: }
589:
590: /**
591: * Determine whether the incoming request is a request to cancel the
592: * processing of the current form.
593: * <p>By default, this method returns <code>true</code> if a parameter
594: * matching the "_cancel" key is present in the request, otherwise it
595: * returns <code>false</code>. Subclasses may override this method
596: * to provide custom logic to detect a cancel request.
597: * <p>The parameter is recognized both when sent as a plain parameter
598: * ("_cancel") or when triggered by an image button ("_cancel.x").
599: * @param request current HTTP request
600: * @see #PARAM_CANCEL
601: */
602: protected boolean isCancelRequest(HttpServletRequest request) {
603: return WebUtils.hasSubmitParameter(request, PARAM_CANCEL);
604: }
605:
606: /**
607: * Return the target page specified in the request.
608: * <p>The default implementation delegates to {@link #getTargetPage(HttpServletRequest, int)}.
609: * Subclasses can override this for customized target page determination.
610: * @param request current HTTP request
611: * @param command form object with request parameters bound onto it
612: * @param errors validation errors holder
613: * @param currentPage the current page, to be returned as fallback
614: * if no target page specified
615: * @return the page specified in the request, or current page if not found
616: * @see #getTargetPage(HttpServletRequest, int)
617: */
618: protected int getTargetPage(HttpServletRequest request,
619: Object command, Errors errors, int currentPage) {
620: return getTargetPage(request, currentPage);
621: }
622:
623: /**
624: * Return the target page specified in the request.
625: * <p>The default implementation examines "_target" parameter (e.g. "_target1").
626: * Subclasses can override this for customized target page determination.
627: * @param request current HTTP request
628: * @param currentPage the current page, to be returned as fallback
629: * if no target page specified
630: * @return the page specified in the request, or current page if not found
631: * @see #PARAM_TARGET
632: */
633: protected int getTargetPage(HttpServletRequest request,
634: int currentPage) {
635: Enumeration paramNames = request.getParameterNames();
636: while (paramNames.hasMoreElements()) {
637: String paramName = (String) paramNames.nextElement();
638: if (paramName.startsWith(PARAM_TARGET)) {
639: for (int i = 0; i < WebUtils.SUBMIT_IMAGE_SUFFIXES.length; i++) {
640: String suffix = WebUtils.SUBMIT_IMAGE_SUFFIXES[i];
641: if (paramName.endsWith(suffix)) {
642: paramName = paramName.substring(0, paramName
643: .length()
644: - suffix.length());
645: }
646: }
647: return Integer.parseInt(paramName
648: .substring(PARAM_TARGET.length()));
649: }
650: }
651: return currentPage;
652: }
653:
654: /**
655: * Validate all pages and process finish.
656: * If there are page validation errors, show the corresponding view page.
657: */
658: private ModelAndView validatePagesAndFinish(
659: HttpServletRequest request, HttpServletResponse response,
660: Object command, BindException errors, int currentPage)
661: throws Exception {
662:
663: // In case of binding errors -> show current page.
664: if (errors.hasErrors()) {
665: return showPage(request, errors, currentPage);
666: }
667:
668: if (!suppressValidation(request, command, errors)) {
669: // In case of remaining errors on a page -> show the page.
670: for (int page = 0; page < getPageCount(request, command); page++) {
671: validatePage(command, errors, page, true);
672: if (errors.hasErrors()) {
673: return showPage(request, errors, page);
674: }
675: }
676: }
677:
678: // No remaining errors -> proceed with finish.
679: return processFinish(request, response, command, errors);
680: }
681:
682: /**
683: * Template method for custom validation logic for individual pages.
684: * The default implementation calls {@link #validatePage(Object, Errors, int)}.
685: * <p>Implementations will typically call fine-granular <code>validateXXX</code>
686: * methods of this instance's Validator, combining them to validation of the
687: * corresponding pages. The Validator's default <code>validate</code> method
688: * will not be called by a wizard form controller!
689: * @param command form object with the current wizard state
690: * @param errors validation errors holder
691: * @param page number of page to validate
692: * @param finish whether this method is called during final revalidation on finish
693: * (else, it is called for validating the current page)
694: * @see #validatePage(Object, Errors, int)
695: * @see org.springframework.validation.Validator#validate
696: */
697: protected void validatePage(Object command, Errors errors,
698: int page, boolean finish) {
699: validatePage(command, errors, page);
700: }
701:
702: /**
703: * Template method for custom validation logic for individual pages.
704: * The default implementation is empty.
705: * <p>Implementations will typically call fine-granular validateXXX methods of this
706: * instance's validator, combining them to validation of the corresponding pages.
707: * The validator's default <code>validate</code> method will not be called by a
708: * wizard form controller!
709: * @param command form object with the current wizard state
710: * @param errors validation errors holder
711: * @param page number of page to validate
712: * @see org.springframework.validation.Validator#validate
713: */
714: protected void validatePage(Object command, Errors errors, int page) {
715: }
716:
717: /**
718: * Post-process the given page after binding and validation, potentially
719: * updating its command object. The passed-in request might contain special
720: * parameters sent by the page.
721: * <p>Only invoked when displaying another page or the same page again,
722: * not when finishing or cancelling.
723: * @param request current HTTP request
724: * @param command form object with request parameters bound onto it
725: * @param errors validation errors holder
726: * @param page number of page to post-process
727: * @throws Exception in case of invalid state or arguments
728: */
729: protected void postProcessPage(HttpServletRequest request,
730: Object command, Errors errors, int page) throws Exception {
731: }
732:
733: /**
734: * Template method for processing the final action of this wizard.
735: * <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
736: * with the command and the Errors instance, under the specified command name,
737: * as expected by the "spring:bind" tag.
738: * <p>You can call the {@link #showPage} method to return back to the wizard,
739: * in case of last-minute validation errors having been found that you would
740: * like to present to the user within the original wizard form.
741: * @param request current HTTP request
742: * @param response current HTTP response
743: * @param command form object with the current wizard state
744: * @param errors validation errors holder
745: * @return the finish view
746: * @throws Exception in case of invalid state or arguments
747: * @see org.springframework.validation.Errors
748: * @see org.springframework.validation.BindException#getModel
749: * @see #showPage(javax.servlet.http.HttpServletRequest, org.springframework.validation.BindException, int)
750: */
751: protected abstract ModelAndView processFinish(
752: HttpServletRequest request, HttpServletResponse response,
753: Object command, BindException errors) throws Exception;
754:
755: /**
756: * Template method for processing the cancel action of this wizard.
757: * <p>The default implementation throws a ServletException, saying that a cancel
758: * operation is not supported by this controller. Thus, you do not need to
759: * implement this template method if you do not support a cancel operation.
760: * <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
761: * with the command and the Errors instance, under the specified command name,
762: * as expected by the "spring:bind" tag.
763: * @param request current HTTP request
764: * @param response current HTTP response
765: * @param command form object with the current wizard state
766: * @param errors Errors instance containing errors
767: * @return the cancellation view
768: * @throws Exception in case of invalid state or arguments
769: * @see org.springframework.validation.Errors
770: * @see org.springframework.validation.BindException#getModel
771: */
772: protected ModelAndView processCancel(HttpServletRequest request,
773: HttpServletResponse response, Object command,
774: BindException errors) throws Exception {
775:
776: throw new ServletException("Wizard form controller class ["
777: + getClass().getName()
778: + "] does not support a cancel operation");
779: }
780:
781: }
|