001: /*
002: * Copyright 2002-2006 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.Map;
020:
021: import javax.servlet.ServletException;
022: import javax.servlet.http.HttpServletRequest;
023: import javax.servlet.http.HttpServletResponse;
024: import javax.servlet.http.HttpSession;
025:
026: import org.springframework.validation.BindException;
027: import org.springframework.validation.Errors;
028: import org.springframework.web.HttpSessionRequiredException;
029: import org.springframework.web.bind.ServletRequestDataBinder;
030: import org.springframework.web.servlet.ModelAndView;
031:
032: /**
033: * <p>Form controller that auto-populates a form bean from the request.
034: * This, either using a new bean instance per request, or using the same bean
035: * when the <code>sessionForm</code> property has been set to <code>true</code>.</p>
036: *
037: * <p>This class is the base class for both framework subclasses like
038: * {@link SimpleFormController SimpleFormController} and
039: * {@link AbstractWizardFormController AbstractWizardFormController}, and
040: * custom form controllers you can provide yourself.</p>
041: *
042: * <p>Both form-input views and after-submission views have to be provided
043: * programmatically. To provide those views using configuration properties,
044: * use the {@link SimpleFormController SimpleFormController}.</p>
045: *
046: * <p>Subclasses need to override <code>showForm</code> to prepare the form view,
047: * and <code>processFormSubmission</code> to handle submit requests. For the latter,
048: * binding errors like type mismatches will be reported via the given "errors" holder.
049: * For additional custom form validation, a validator (property inherited from
050: * BaseCommandController) can be used, reporting via the same "errors" instance.</p>
051: *
052: * <p>Comparing this Controller to the Struts notion of the <code>Action</code>
053: * shows us that with Spring, you can use any ordinary JavaBeans or database-
054: * backed JavaBeans without having to implement a framework-specific class
055: * (like Struts' <code>ActionForm</code>). More complex properties of JavaBeans
056: * (Dates, Locales, but also your own application-specific or compound types)
057: * can be represented and submitted to the controller, by using the notion of
058: * a <code>java.beans.PropertyEditor</code>. For more information on that
059: * subject, see the workflow of this controller and the explanation of the
060: * {@link BaseCommandController BaseCommandController}.</p>
061: *
062: * <p><b><a name="workflow">Workflow
063: * (<a href="BaseCommandController.html#workflow">and that defined by superclass</a>):</b><br>
064: * <ol>
065: * <li><b>The controller receives a request for a new form (typically a GET).</b></li>
066: * <li>Call to {@link #formBackingObject formBackingObject()} which by default,
067: * returns an instance of the commandClass that has been configured
068: * (see the properties the superclass exposes), but can also be overridden
069: * to e.g. retrieve an object from the database (that needs to be modified
070: * using the form).</li>
071: * <li>Call to {@link #initBinder initBinder()} which allows you to register
072: * custom editors for certain fields (often properties of non-primitive
073: * or non-String types) of the command class. This will render appropriate
074: * Strings for those property values, e.g. locale-specific date strings.</li>
075: * <li><em>Only if <code>bindOnNewForm</code> is set to <code>true</code></em>, then
076: * {@link org.springframework.web.bind.ServletRequestDataBinder ServletRequestDataBinder}
077: * gets applied to populate the new form object with initial request parameters and the
078: * {@link #onBindOnNewForm(HttpServletRequest, Object, BindException)} callback method is
079: * called. <em>Note:</em> any defined Validators are not applied at this point, to allow
080: * partial binding. However be aware that any Binder customizations applied via
081: * initBinder() (such as
082: * {@link org.springframework.validation.DataBinder#setRequiredFields(String[])} will
083: * still apply. As such, if using bindOnNewForm=true and initBinder() customizations are
084: * used to validate fields instead of using Validators, in the case that only some fields
085: * will be populated for the new form, there will potentially be some bind errors for
086: * missing fields in the errors object. Any view (JSP, etc.) that displays binder errors
087: * needs to be intelligent and for this case take into account whether it is displaying the
088: * initial form view or subsequent post results, skipping error display for the former.</li>
089: * <li>Call to {@link #showForm(HttpServletRequest, HttpServletResponse, BindException) showForm()}
090: * to return a View that should be rendered (typically the view that renders
091: * the form). This method has to be implemented in subclasses.</li>
092: * <li>The showForm() implementation will call {@link #referenceData referenceData()},
093: * which you can implement to provide any relevant reference data you might need
094: * when editing a form (e.g. a List of Locale objects you're going to let the
095: * user select one from).</li>
096: * <li>Model gets exposed and view gets rendered, to let the user fill in the form.</li>
097: * <li><b>The controller receives a form submission (typically a POST).</b>
098: * To use a different way of detecting a form submission, override the
099: * {@link #isFormSubmission isFormSubmission} method.
100: * </li>
101: * <li>If <code>sessionForm</code> is not set, {@link #formBackingObject formBackingObject()}
102: * is called to retrieve a form object. Otherwise, the controller tries to
103: * find the command object which is already bound in the session. If it cannot
104: * find the object, it does a call to {@link #handleInvalidSubmit handleInvalidSubmit}
105: * which - by default - tries to create a new form object and resubmit the form.</li>
106: * <li>The {@link org.springframework.web.bind.ServletRequestDataBinder ServletRequestDataBinder}
107: * gets applied to populate the form object with current request parameters.
108: * <li>Call to {@link #onBind onBind(HttpServletRequest, Object, Errors)} which allows
109: * you to do custom processing after binding but before validation (e.g. to manually
110: * bind request parameters to bean properties, to be seen by the Validator).</li>
111: * <li>If <code>validateOnBinding</code> is set, a registered Validator will be invoked.
112: * The Validator will check the form object properties, and register corresponding
113: * errors via the given {@link org.springframework.validation.Errors Errors}</li> object.
114: * <li>Call to {@link #onBindAndValidate onBindAndValidate()} which allows you
115: * to do custom processing after binding and validation (e.g. to manually
116: * bind request parameters, and to validate them outside a Validator).</li>
117: * <li>Call {@link #processFormSubmission(HttpServletRequest, HttpServletResponse,
118: * Object, BindException) processFormSubmission()} to process the submission, with
119: * or without binding errors. This method has to be implemented in subclasses.</li>
120: * </ol>
121: * </p>
122: *
123: * <p>In session form mode, a submission without an existing form object in the
124: * session is considered invalid, like in case of a resubmit/reload by the browser.
125: * The {@link #handleInvalidSubmit handleInvalidSubmit} method is invoked then,
126: * by default trying to resubmit. It can be overridden in subclasses to show
127: * corresponding messages or to redirect to a new form, in order to avoid duplicate
128: * submissions. The form object in the session can be considered a transaction
129: * token in that case.</p>
130: *
131: * <p>Note that views should never retrieve form beans from the session but always
132: * from the request, as prepared by the form controller. Remember that some view
133: * technologies like Velocity cannot even access a HTTP session.</p>
134: *
135: * <p><b><a name="config">Exposed configuration properties</a>
136: * (<a href="BaseCommandController.html#config">and those defined by superclass</a>):</b><br>
137: * <table border="1">
138: * <tr>
139: * <td><b>name</b></td>
140: * <td><b>default</b></td>
141: * <td><b>description</b></td>
142: * </tr>
143: * <tr>
144: * <td>bindOnNewForm</td>
145: * <td>false</td>
146: * <td>Indicates whether to bind servlet request parameters when
147: * creating a new form. Otherwise, the parameters will only be
148: * bound on form submission attempts.</td>
149: * </tr>
150: * <tr>
151: * <td>sessionForm</td>
152: * <td>false</td>
153: * <td>Indicates whether the form object should be kept in the session
154: * when a user asks for a new form. This allows you e.g. to retrieve
155: * an object from the database, let the user edit it, and then persist
156: * it again. Otherwise, a new command object will be created for each
157: * request (even when showing the form again after validation errors).</td>
158: * </tr>
159: * </table>
160: * </p>
161: *
162: * @author Rod Johnson
163: * @author Juergen Hoeller
164: * @author Alef Arendsen
165: * @author Rob Harrop
166: * @author Colin Sampaleanu
167: * @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
168: * @see #processFormSubmission
169: * @see SimpleFormController
170: * @see AbstractWizardFormController
171: */
172: public abstract class AbstractFormController extends
173: BaseCommandController {
174:
175: private boolean bindOnNewForm = false;
176:
177: private boolean sessionForm = false;
178:
179: /**
180: * Create a new AbstractFormController.
181: * <p>Subclasses should set the following properties, either in the constructor
182: * or via a BeanFactory: commandName, commandClass, bindOnNewForm, sessionForm.
183: * Note that "commandClass" doesn't need to be set when overriding
184: * {@link #formBackingObject}, since the latter determines the class anyway.
185: * <p>"cacheSeconds" is by default set to 0 (-> no caching for all form controllers).
186: * @see #setCommandName
187: * @see #setCommandClass
188: * @see #setBindOnNewForm
189: * @see #setSessionForm
190: * @see #formBackingObject
191: */
192: public AbstractFormController() {
193: setCacheSeconds(0);
194: }
195:
196: /**
197: * Set if request parameters should be bound to the form object
198: * in case of a non-submitting request, i.e. a new form.
199: */
200: public final void setBindOnNewForm(boolean bindOnNewForm) {
201: this .bindOnNewForm = bindOnNewForm;
202: }
203:
204: /**
205: * Return if request parameters should be bound in case of a new form.
206: */
207: public final boolean isBindOnNewForm() {
208: return bindOnNewForm;
209: }
210:
211: /**
212: * Activate resp. deactivate session form mode. In session form mode,
213: * the form is stored in the session to keep the form object instance
214: * between requests, instead of creating a new one on each request.
215: * <p>This is necessary for either wizard-style controllers that populate a
216: * single form object from multiple pages, or forms that populate a persistent
217: * object that needs to be identical to allow for tracking changes.
218: */
219: public final void setSessionForm(boolean sessionForm) {
220: this .sessionForm = sessionForm;
221: }
222:
223: /**
224: * Return if session form mode is activated.
225: */
226: public final boolean isSessionForm() {
227: return sessionForm;
228: }
229:
230: /**
231: * Handles two cases: form submissions and showing a new form.
232: * Delegates the decision between the two to {@link #isFormSubmission},
233: * always treating requests without existing form session attribute
234: * as new form when using session form mode.
235: * @see #isFormSubmission
236: * @see #showNewForm
237: * @see #processFormSubmission
238: */
239: protected ModelAndView handleRequestInternal(
240: HttpServletRequest request, HttpServletResponse response)
241: throws Exception {
242:
243: // Form submission or new form to show?
244: if (isFormSubmission(request)) {
245: // Fetch form object from HTTP session, bind, validate, process submission.
246: try {
247: Object command = getCommand(request);
248: ServletRequestDataBinder binder = bindAndValidate(
249: request, command);
250: BindException errors = new BindException(binder
251: .getBindingResult());
252: return processFormSubmission(request, response,
253: command, errors);
254: } catch (HttpSessionRequiredException ex) {
255: // Cannot submit a session form if no form object is in the session.
256: if (logger.isDebugEnabled()) {
257: logger.debug("Invalid submit detected: "
258: + ex.getMessage());
259: }
260: return handleInvalidSubmit(request, response);
261: }
262: }
263:
264: else {
265: // New form to show: render form view.
266: return showNewForm(request, response);
267: }
268: }
269:
270: /**
271: * Determine if the given request represents a form submission.
272: * <p>The default implementation treats a POST request as form submission.
273: * Note: If the form session attribute doesn't exist when using session form
274: * mode, the request is always treated as new form by handleRequestInternal.
275: * <p>Subclasses can override this to use a custom strategy, e.g. a specific
276: * request parameter (assumably a hidden field or submit button name).
277: * @param request current HTTP request
278: * @return if the request represents a form submission
279: */
280: protected boolean isFormSubmission(HttpServletRequest request) {
281: return "POST".equals(request.getMethod());
282: }
283:
284: /**
285: * Return the name of the HttpSession attribute that holds the form object
286: * for this form controller.
287: * <p>The default implementation delegates to the {@link #getFormSessionAttributeName()}
288: * variant without arguments.
289: * @param request current HTTP request
290: * @return the name of the form session attribute, or <code>null</code> if not in session form mode
291: * @see #getFormSessionAttributeName
292: * @see javax.servlet.http.HttpSession#getAttribute
293: */
294: protected String getFormSessionAttributeName(
295: HttpServletRequest request) {
296: return getFormSessionAttributeName();
297: }
298:
299: /**
300: * Return the name of the HttpSession attribute that holds the form object
301: * for this form controller.
302: * <p>Default is an internal name, of no relevance to applications, as the form
303: * session attribute is not usually accessed directly. Can be overridden to use
304: * an application-specific attribute name, which allows other code to access
305: * the session attribute directly.
306: * @return the name of the form session attribute
307: * @see javax.servlet.http.HttpSession#getAttribute
308: */
309: protected String getFormSessionAttributeName() {
310: return getClass().getName() + ".FORM." + getCommandName();
311: }
312:
313: /**
314: * Show a new form. Prepares a backing object for the current form
315: * and the given request, including checking its validity.
316: * @param request current HTTP request
317: * @param response current HTTP response
318: * @return the prepared form view
319: * @throws Exception in case of an invalid new form object
320: * @see #getErrorsForNewForm
321: */
322: protected final ModelAndView showNewForm(
323: HttpServletRequest request, HttpServletResponse response)
324: throws Exception {
325:
326: logger.debug("Displaying new form");
327: return showForm(request, response, getErrorsForNewForm(request));
328: }
329:
330: /**
331: * Create a BindException instance for a new form.
332: * Called by {@link #showNewForm}.
333: * <p>Can be used directly when intending to show a new form but with
334: * special errors registered on it (for example, on invalid submit).
335: * Usually, the resulting BindException will be passed to
336: * {@link #showForm(HttpServletRequest, HttpServletResponse, BindException)},
337: * after registering the errors on it.
338: * @param request current HTTP request
339: * @return the BindException instance
340: * @throws Exception in case of an invalid new form object
341: * @see #showNewForm
342: * @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
343: * @see #handleInvalidSubmit
344: */
345: protected final BindException getErrorsForNewForm(
346: HttpServletRequest request) throws Exception {
347: // Create form-backing object for new form.
348: Object command = formBackingObject(request);
349: if (command == null) {
350: throw new ServletException(
351: "Form object returned by formBackingObject() must not be null");
352: }
353: if (!checkCommand(command)) {
354: throw new ServletException(
355: "Form object returned by formBackingObject() must match commandClass");
356: }
357:
358: // Bind without validation, to allow for prepopulating a form, and for
359: // convenient error evaluation in views (on both first attempt and resubmit).
360: ServletRequestDataBinder binder = createBinder(request, command);
361: BindException errors = new BindException(binder
362: .getBindingResult());
363: if (isBindOnNewForm()) {
364: logger.debug("Binding to new form");
365: binder.bind(request);
366: onBindOnNewForm(request, command, errors);
367: }
368:
369: // Return BindException object that resulted from binding.
370: return errors;
371: }
372:
373: /**
374: * Callback for custom post-processing in terms of binding for a new form.
375: * Called when preparing a new form if <code>bindOnNewForm</code> is <code>true</code>.
376: * <p>The default implementation delegates to <code>onBindOnNewForm(request, command)</code>.
377: * @param request current HTTP request
378: * @param command the command object to perform further binding on
379: * @param errors validation errors holder, allowing for additional
380: * custom registration of binding errors
381: * @throws Exception in case of invalid state or arguments
382: * @see #onBindOnNewForm(javax.servlet.http.HttpServletRequest, Object)
383: * @see #setBindOnNewForm
384: */
385: protected void onBindOnNewForm(HttpServletRequest request,
386: Object command, BindException errors) throws Exception {
387:
388: onBindOnNewForm(request, command);
389: }
390:
391: /**
392: * Callback for custom post-processing in terms of binding for a new form.
393: * <p>Called by the default implementation of the
394: * {@link #onBindOnNewForm(HttpServletRequest, Object, BindException)} variant
395: * with all parameters, after standard binding when displaying the form view.
396: * Only called if <code>bindOnNewForm</code> is set to <code>true</code>.
397: * <p>The default implementation is empty.
398: * @param request current HTTP request
399: * @param command the command object to perform further binding on
400: * @throws Exception in case of invalid state or arguments
401: * @see #onBindOnNewForm(HttpServletRequest, Object, BindException)
402: * @see #setBindOnNewForm(boolean)
403: */
404: protected void onBindOnNewForm(HttpServletRequest request,
405: Object command) throws Exception {
406: }
407:
408: /**
409: * Return the form object for the given request.
410: * <p>Calls {@link #formBackingObject} if not in session form mode.
411: * Else, retrieves the form object from the session. Note that the form object
412: * gets removed from the session, but it will be re-added when showing the
413: * form for resubmission.
414: * @param request current HTTP request
415: * @return object form to bind onto
416: * @throws org.springframework.web.HttpSessionRequiredException
417: * if a session was expected but no active session (or session form object) found
418: * @throws Exception in case of invalid state or arguments
419: * @see #formBackingObject
420: */
421: protected final Object getCommand(HttpServletRequest request)
422: throws Exception {
423: // If not in session-form mode, create a new form-backing object.
424: if (!isSessionForm()) {
425: return formBackingObject(request);
426: }
427:
428: // Session-form mode: retrieve form object from HTTP session attribute.
429: HttpSession session = request.getSession(false);
430: if (session == null) {
431: throw new HttpSessionRequiredException(
432: "Must have session when trying to bind (in session-form mode)");
433: }
434: String formAttrName = getFormSessionAttributeName(request);
435: Object sessionFormObject = session.getAttribute(formAttrName);
436: if (sessionFormObject == null) {
437: throw new HttpSessionRequiredException(
438: "Form object not found in session (in session-form mode)");
439: }
440:
441: // Remove form object from HTTP session: we might finish the form workflow
442: // in this request. If it turns out that we need to show the form view again,
443: // we'll re-bind the form object to the HTTP session.
444: if (logger.isDebugEnabled()) {
445: logger.debug("Removing form session attribute ["
446: + formAttrName + "]");
447: }
448: session.removeAttribute(formAttrName);
449:
450: return currentFormObject(request, sessionFormObject);
451: }
452:
453: /**
454: * Retrieve a backing object for the current form from the given request.
455: * <p>The properties of the form object will correspond to the form field values
456: * in your form view. This object will be exposed in the model under the specified
457: * command name, to be accessed under that name in the view: for example, with
458: * a "spring:bind" tag. The default command name is "command".
459: * <p>Note that you need to activate session form mode to reuse the form-backing
460: * object across the entire form workflow. Else, a new instance of the command
461: * class will be created for each submission attempt, just using this backing
462: * object as template for the initial form.
463: * <p>The default implementation calls {@link #createCommand()},
464: * creating a new empty instance of the specified command class.
465: * Subclasses can override this to provide a preinitialized backing object.
466: * @param request current HTTP request
467: * @return the backing object
468: * @throws Exception in case of invalid state or arguments
469: * @see #setCommandName
470: * @see #setCommandClass
471: * @see #createCommand
472: */
473: protected Object formBackingObject(HttpServletRequest request)
474: throws Exception {
475: return createCommand();
476: }
477:
478: /**
479: * Return the current form object to use for binding and further processing,
480: * based on the passed-in form object as found in the HttpSession.
481: * <p>The default implementation simply returns the session form object as-is.
482: * Subclasses can override this to post-process the session form object,
483: * for example reattaching it to a persistence manager.
484: * @param sessionFormObject the form object retrieved from the HttpSession
485: * @return the form object to use for binding and further processing
486: * @throws Exception in case of invalid state or arguments
487: */
488: protected Object currentFormObject(HttpServletRequest request,
489: Object sessionFormObject) throws Exception {
490: return sessionFormObject;
491: }
492:
493: /**
494: * Prepare the form model and view, including reference and error data.
495: * Can show a configured form page, or generate a form view programmatically.
496: * <p>A typical implementation will call
497: * <code>showForm(request, errors, "myView")</code>
498: * to prepare the form view for a specific view name, returning the
499: * ModelAndView provided there.
500: * <p>For building a custom ModelAndView, call <code>errors.getModel()</code>
501: * to populate the ModelAndView model with the command and the Errors instance,
502: * under the specified command name, as expected by the "spring:bind" tag.
503: * You also need to include the model returned by {@link #referenceData}.
504: * <p>Note: If you decide to have a "formView" property specifying the
505: * view name, consider using SimpleFormController.
506: * @param request current HTTP request
507: * @param response current HTTP response
508: * @param errors validation errors holder
509: * @return the prepared form view, or <code>null</code> if handled directly
510: * @throws Exception in case of invalid state or arguments
511: * @see #showForm(HttpServletRequest, BindException, String)
512: * @see org.springframework.validation.Errors
513: * @see org.springframework.validation.BindException#getModel
514: * @see #referenceData(HttpServletRequest, Object, Errors)
515: * @see SimpleFormController#setFormView
516: */
517: protected abstract ModelAndView showForm(
518: HttpServletRequest request, HttpServletResponse response,
519: BindException errors) throws Exception;
520:
521: /**
522: * Prepare model and view for the given form, including reference and errors.
523: * <p>In session form mode: Re-puts the form object in the session when
524: * returning to the form, as it has been removed by getCommand.
525: * <p>Can be used in subclasses to redirect back to a specific form page.
526: * @param request current HTTP request
527: * @param errors validation errors holder
528: * @param viewName name of the form view
529: * @return the prepared form view
530: * @throws Exception in case of invalid state or arguments
531: */
532: protected final ModelAndView showForm(HttpServletRequest request,
533: BindException errors, String viewName) throws Exception {
534:
535: return showForm(request, errors, viewName, null);
536: }
537:
538: /**
539: * Prepare model and view for the given form, including reference and errors,
540: * adding a controller-specific control model.
541: * <p>In session form mode: Re-puts the form object in the session when returning
542: * to the form, as it has been removed by getCommand.
543: * <p>Can be used in subclasses to redirect back to a specific form page.
544: * @param request current HTTP request
545: * @param errors validation errors holder
546: * @param viewName name of the form view
547: * @param controlModel model map containing controller-specific control data
548: * (e.g. current page in wizard-style controllers or special error message)
549: * @return the prepared form view
550: * @throws Exception in case of invalid state or arguments
551: */
552: protected final ModelAndView showForm(HttpServletRequest request,
553: BindException errors, String viewName, Map controlModel)
554: throws Exception {
555:
556: // In session form mode, re-expose form object as HTTP session attribute.
557: // Re-binding is necessary for proper state handling in a cluster,
558: // to notify other nodes of changes in the form object.
559: if (isSessionForm()) {
560: String formAttrName = getFormSessionAttributeName(request);
561: if (logger.isDebugEnabled()) {
562: logger.debug("Setting form session attribute ["
563: + formAttrName + "] to: " + errors.getTarget());
564: }
565: request.getSession().setAttribute(formAttrName,
566: errors.getTarget());
567: }
568:
569: // Fetch errors model as starting point, containing form object under
570: // "commandName", and corresponding Errors instance under internal key.
571: Map model = errors.getModel();
572:
573: // Merge reference data into model, if any.
574: Map referenceData = referenceData(request, errors.getTarget(),
575: errors);
576: if (referenceData != null) {
577: model.putAll(referenceData);
578: }
579:
580: // Merge control attributes into model, if any.
581: if (controlModel != null) {
582: model.putAll(controlModel);
583: }
584:
585: // Trigger rendering of the specified view, using the final model.
586: return new ModelAndView(viewName, model);
587: }
588:
589: /**
590: * Create a reference data map for the given request, consisting of
591: * bean name/bean instance pairs as expected by ModelAndView.
592: * <p>The default implementation returns <code>null</code>.
593: * Subclasses can override this to set reference data used in the view.
594: * @param request current HTTP request
595: * @param command form object with request parameters bound onto it
596: * @param errors validation errors holder
597: * @return a Map with reference data entries, or <code>null</code> if none
598: * @throws Exception in case of invalid state or arguments
599: * @see ModelAndView
600: */
601: protected Map referenceData(HttpServletRequest request,
602: Object command, Errors errors) throws Exception {
603: return null;
604: }
605:
606: /**
607: * Process form submission request. Called by {@link #handleRequestInternal}
608: * in case of a form submission, with or without binding errors. Implementations
609: * need to proceed properly, typically showing a form view in case of binding
610: * errors or performing a submit action else.
611: * <p>Subclasses can implement this to provide custom submission handling like
612: * triggering a custom action. They can also provide custom validation and call
613: * {@link #showForm(HttpServletRequest, HttpServletResponse, BindException)}
614: * or proceed with the submission accordingly.
615: * <p>For a success view, call <code>errors.getModel()</code> to populate the
616: * ModelAndView model with the command and the Errors instance, under the
617: * specified command name, as expected by the "spring:bind" tag. For a form view,
618: * simply return the ModelAndView object provided by
619: * {@link #showForm(HttpServletRequest, HttpServletResponse, BindException)}.
620: * @param request current servlet request
621: * @param response current servlet response
622: * @param command form object with request parameters bound onto it
623: * @param errors holder without errors (subclass can add errors if it wants to)
624: * @return the prepared model and view, or <code>null</code>
625: * @throws Exception in case of errors
626: * @see #handleRequestInternal
627: * @see #isFormSubmission
628: * @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
629: * @see org.springframework.validation.Errors
630: * @see org.springframework.validation.BindException#getModel
631: */
632: protected abstract ModelAndView processFormSubmission(
633: HttpServletRequest request, HttpServletResponse response,
634: Object command, BindException errors) throws Exception;
635:
636: /**
637: * Handle an invalid submit request, e.g. when in session form mode but no form object
638: * was found in the session (like in case of an invalid resubmit by the browser).
639: * <p>The default implementation simply tries to resubmit the form with a new
640: * form object. This should also work if the user hit the back button, changed
641: * some form data, and resubmitted the form.
642: * <p>Note: To avoid duplicate submissions, you need to override this method.
643: * Either show some "invalid submit" message, or call {@link #showNewForm} for
644: * resetting the form (prepopulating it with the current values if "bindOnNewForm"
645: * is true). In this case, the form object in the session serves as transaction token.
646: * <pre>
647: * protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response) throws Exception {
648: * return showNewForm(request, response);
649: * }</pre>
650: * You can also show a new form but with special errors registered on it:
651: * <pre>
652: * protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response) throws Exception {
653: * BindException errors = getErrorsForNewForm(request);
654: * errors.reject("duplicateFormSubmission", "Duplicate form submission");
655: * return showForm(request, response, errors);
656: * }</pre>
657: * @param request current HTTP request
658: * @param response current HTTP response
659: * @return a prepared view, or <code>null</code> if handled directly
660: * @throws Exception in case of errors
661: * @see #showNewForm
662: * @see #getErrorsForNewForm
663: * @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
664: * @see #setBindOnNewForm
665: */
666: protected ModelAndView handleInvalidSubmit(
667: HttpServletRequest request, HttpServletResponse response)
668: throws Exception {
669:
670: Object command = formBackingObject(request);
671: ServletRequestDataBinder binder = bindAndValidate(request,
672: command);
673: BindException errors = new BindException(binder
674: .getBindingResult());
675: return processFormSubmission(request, response, command, errors);
676: }
677:
678: }
|