KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * Copyright 2002-2006 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.Arrays JavaDoc;
20 import java.util.Map JavaDoc;
21
22 import javax.portlet.ActionRequest;
23 import javax.portlet.ActionResponse;
24 import javax.portlet.PortletException;
25 import javax.portlet.PortletRequest;
26 import javax.portlet.PortletSession;
27 import javax.portlet.RenderRequest;
28 import javax.portlet.RenderResponse;
29
30 import org.springframework.validation.BindException;
31 import org.springframework.validation.Errors;
32 import org.springframework.web.portlet.ModelAndView;
33 import org.springframework.web.portlet.bind.PortletRequestDataBinder;
34 import org.springframework.web.portlet.handler.PortletSessionRequiredException;
35
36 /**
37  * <p>Form controller that auto-populates a form bean from the request.
38  * This, either using a new bean instance per request, or using the same bean
39  * when the <code>sessionForm</code> property has been set to
40  * <code>true</code>.</p>
41  *
42  * <p>This class is the base class for both framework subclasses like
43  * {@link SimpleFormController SimpleFormController} and
44  * {@link AbstractWizardFormController AbstractWizardFormController}, and
45  * custom form controllers you can provide yourself.</p>
46  *
47  * <p>Both form-input-views and after-submission-views have to be provided
48  * programmatically. To provide those views using configuration properties,
49  * use the {@link SimpleFormController SimpleFormController}.</p>
50  *
51  * <p>Subclasses need to override <code>showForm</code> to prepare the form view,
52  * <code>processFormSubmission</code> to handle submit requests, and
53  * <code>renderFormSubmission</code> to display the results of the submit.
54  * For the latter two methods, binding errors like type mismatches will be
55  * reported via the given "errors" holder. For additional custom form validation,
56  * a validator (property inherited from BaseCommandController) can be used,
57  * reporting via the same "errors" instance.</p>
58  *
59  * <p>Comparing this Controller to the Struts notion of the <code>Action</code>
60  * shows us that with Spring, you can use any ordinary JavaBeans or database-
61  * backed JavaBeans without having to implement a framework-specific class
62  * (like Struts' <code>ActionForm</code>). More complex properties of JavaBeans
63  * (Dates, Locales, but also your own application-specific or compound types)
64  * can be represented and submitted to the controller, by using the notion of
65  * a <code>java.beans.PropertyEditors</code>. For more information on that
66  * subject, see the workflow of this controller and the explanation of the
67  * {@link BaseCommandController BaseCommandController}.</p>
68  *
69  * This controller is different from it's servlet counterpart in that it must take
70  * into account the two phases of a portlet request: the action phase and the render
71  * phase. See the JSR-168 spec for more details on these two phases.
72  * Be especially aware that the action phase is called only once, but that the
73  * render phase will be called repeatedly by the portal -- it does this every time
74  * the page containing the portlet is updated, even if the activity is in some other
75  * portlet. (This is not quite true, the portal can also be told to cache the results of
76  * the render for a period of time, but assume it is true for programming purposes.)
77  *
78  * <p><b><a name="workflow">Workflow
79  * (<a HREF="BaseCommandController.html#workflow">and that defined by superclass</a>):</b><br>
80  * <ol>
81  * <li><b>The controller receives a request for a new form (typically a
82  * Render Request only).</b> The render phase will proceed to display
83  * the form as follows.</li>
84  * <li>Call to {@link #formBackingObject formBackingObject()} which by
85  * default, returns an instance of the commandClass that has been
86  * configured (see the properties the superclass exposes), but can also be
87  * overridden to e.g. retrieve an object from the database (that needs to
88  * be modified using the form).</li>
89  * <li>Call to {@link #initBinder initBinder()} which allows you to
90  * register custom editors for certain fields (often properties of non-
91  * primitive or non-String types) of the command class. This will render
92  * appropriate Strings for those property values, e.g. locale-specific
93  * date strings. </li>
94  * <li>The {@link PortletRequestDataBinder PortletRequestDataBinder}
95  * gets applied to populate the new form object with initial request parameters and the
96  * {@link #onBindOnNewForm(RenderRequest, Object, BindException)} callback method is invoked.
97  * (<i>only if <code>bindOnNewForm</code> is set to <code>true</code></i>)
98  * Make sure that the initial parameters do not include the parameter that indicates a
99  * form submission has occurred.</li>
100  * <li>Call to {@link #showForm(RenderRequest, RenderResponse,
101         * BindException) showForm} to return a View that should be rendered
102  * (typically the view that renders the form). This method has to be
103  * implemented in subclasses. </li>
104  * <li>The showForm() implementation will call {@link #referenceData referenceData},
105  * which you can implement to provide any relevant reference data you might need
106  * when editing a form (e.g. a List of Locale objects you're going to let the
107  * user select one from).</li>
108  * <li>Model gets exposed and view gets rendered, to let the user fill in
109  * the form.</li>
110  * <li><b>The controller receives a form submission (typically an Action
111  * Request).</b> To use a different way of detecting a form submission,
112  * override the {@link #isFormSubmission isFormSubmission} method.
113  * The action phase will proceed to process the form submission as follows.</li>
114  * <li>If <code>sessionForm</code> is not set, {@link #formBackingObject
115  * formBackingObject} is called to retrieve a form object. Otherwise,
116  * the controller tries to find the command object which is already bound
117  * in the session. If it cannot find the object, the action phase does a
118  * call to {@link #handleInvalidSubmit handleInvalidSubmit} which - by default -
119  * tries to create a new form object and resubmit the form. It then sets
120  * a render parameter that will indicate to the render phase that this was
121  * an invalid submit.</li>
122  * <li>Still in the action phase of a valid submit, the {@link
123  * PortletRequestDataBinder PortletRequestDataBinder} gets applied to populate
124  * the form object with current request parameters.</li>
125  * <li>Call to {@link #onBind onBind(PortletRequest, Object, Errors)}
126  * which allows you to do custom processing after binding but before
127  * validation (e.g. to manually bind request parameters to bean
128  * properties, to be seen by the Validator).</li>
129  * <li>If <code>validateOnBinding</code> is set, a registered Validator
130  * will be invoked. The Validator will check the form object properties,
131  * and register corresponding errors via the given {@link Errors Errors}
132  * object.</li>
133  * <li>Call to {@link #onBindAndValidate onBindAndValidate} which allows
134  * you to do custom processing after binding and validation (e.g. to
135  * manually bind request parameters, and to validate them outside a
136  * Validator).</li>
137  * <li>Call to {@link #processFormSubmission processFormSubmission}
138  * to process the submission, with or without binding errors.
139  * This method has to be implemented in subclasses and will be called
140  * only once per form submission.</li>
141  * <li>The portal will then call the render phase of processing the form
142  * submission. This phase will be called repeatedly by the portal every
143  * time the page is refreshed. All processing here should take this into
144  * account. Any one-time-only actions (such as modifying a database) must
145  * be done in the action phase.</li>
146  * <li>If the action phase indicated this is an invalid submit, the render
147  * phase calls {@link #renderInvalidSubmit renderInvalidSubmit} which &ndash;
148  * also by default &ndash; will render the results of the resubmitted
149  * form. Be sure to override both <code>handleInvalidSubmit</code> and
150  * <code>renderInvalidSubmit</code> if you want to change this overall
151  * behavior.</li>
152  * <li>Finally, call {@link #renderFormSubmission renderFormSubmission} to
153  * render the results of the submission, with or without binding errors.
154  * This method has to be implemented in subclasses and will be called
155  * repeatedly by the portal.</li>
156  * </ol>
157  * </p>
158  *
159  * <p>In session form mode, a submission without an existing form object in the
160  * session is considered invalid, like in the case of a resubmit/reload by the browser.
161  * The {@link #handleInvalidSubmit handleInvalidSubmit} /
162  * {@link #renderInvalidSubmit renderInvalidSubmit} methods are invoked then,
163  * by default trying to resubmit. This can be overridden in subclasses to show
164  * corresponding messages or to redirect to a new form, in order to avoid duplicate
165  * submissions. The form object in the session can be considered a transaction token
166  * in that case.</p>
167  *
168  * Make sure that any URLs that take you to your form controller are Render URLs, so
169  * that it will not try to treat the initial call as a form submission. If you use
170  * Action URLs to link to your controller, you will need to override the
171  * {@link #isFormSubmission isFormSubmission} method to use a different mechanism for
172  * determining whether a form has been submitted. Make sure this method will work for
173  * both the ActionRequest and the RenderRequest objects.
174  *
175  * <p>Note that views should never retrieve form beans from the session but always
176  * from the request, as prepared by the form controller. Remember that some view
177  * technologies like Velocity cannot even access a HTTP session.</p>
178  *
179  * <p><b><a name="config">Exposed configuration properties</a>
180  * (<a HREF="BaseCommandController.html#config">and those defined by superclass</a>):</b><br>
181  * <table border="1">
182  * <tr>
183  * <td><b>name</b></td>
184  * <td><b>default</b></td>
185  * <td><b>description</b></td>
186  * </tr>
187  * <tr>
188  * <td>bindOnNewForm</td>
189  * <td>false</td>
190  * <td>Indicates whether to bind portlet request parameters when
191  * creating a new form. Otherwise, the parameters will only be
192  * bound on form submission attempts.</td>
193  * </tr>
194  * <tr>
195  * <td>sessionForm</td>
196  * <td>false</td>
197  * <td>Indicates whether the form object should be kept in the session
198  * when a user asks for a new form. This allows you e.g. to retrieve
199  * an object from the database, let the user edit it, and then persist
200  * it again. Otherwise, a new command object will be created for each
201  * request (even when showing the form again after validation errors).</td>
202  * </tr>
203  * <tr>
204  * <td>redirectAction</td>
205  * <td>false</td>
206  * <td>Specifies whether <code>processFormSubmission</code> is expected to call
207  * {@link ActionResponse#sendRedirect ActionResponse.sendRedirect}.
208  * This is important because some methods may not be called before
209  * {@link ActionResponse#sendRedirect ActionResponse.sendRedirect} (e.g.
210  * {@link ActionResponse#setRenderParameter ActionResponse.setRenderParameter}).
211  * Setting this flag will prevent AbstractFormController from setting render
212  * parameters that it normally needs for the render phase.
213  * If this is set true and <code>sendRedirect</code> is not called, then
214  * <code>processFormSubmission</code> must call
215  * {@link #setFormSubmit setFormSubmit}.
216  * Otherwise, the render phase will not realize the form was submitted
217  * and will simply display a new blank form.</td>
218  * </tr>
219  * <tr>
220  * <td>renderParameters</td>
221  * <td>null</td>
222  * <td>An array of parameters that will be passed forward from the action
223  * phase to the render phase if the form needs to be displayed
224  * again. These can also be passed forward explicitly by calling
225  * the <code>passRenderParameters</code> method from any action
226  * phase method. Abstract descendants of this controller should follow
227  * similar behavior. If there are parameters you need in
228  * <code>renderFormSubmission</code>, then you need to pass those
229  * forward from <code>processFormSubmission</code>. If you override the
230  * default behavior of invalid submits and you set sessionForm to true,
231  * then you probably will not need to set this because your parameters
232  * are only going to be needed on the first request.</td>
233  * </tr>
234  * </table>
235  * </p>
236  *
237  * @author John A. Lewis
238  * @author Juergen Hoeller
239  * @author Alef Arendsen
240  * @author Rob Harrop
241  * @author Rainer Schmitz
242  * @since 2.0
243  * @see #showForm(RenderRequest, RenderResponse, BindException)
244  * @see SimpleFormController
245  * @see AbstractWizardFormController
246  */

247 public abstract class AbstractFormController extends BaseCommandController {
248
249     /**
250      * These render parameters are used to indicate forward to the render phase
251      * if the form was submitted and if the submission was invalid.
252      */

253     private static final String JavaDoc FORM_SUBMISSION_PARAMETER = "form-submit";
254
255     private static final String JavaDoc INVALID_SUBMISSION_PARAMETER = "invalid-submit";
256
257     private static final String JavaDoc TRUE = Boolean.TRUE.toString();
258
259
260     private boolean bindOnNewForm = false;
261
262     private boolean sessionForm = false;
263
264     private boolean redirectAction = false;
265
266     private String JavaDoc[] renderParameters = null;
267
268
269     /**
270      * Create a new AbstractFormController.
271      * <p>Subclasses should set the following properties, either in the constructor
272      * or via a BeanFactory: commandName, commandClass, bindOnNewForm, sessionForm.
273      * Note that commandClass doesn't need to be set when overriding
274      * <code>formBackingObject</code>, as the latter determines the class anyway.
275      * <p>"cacheSeconds" is by default set to 0 (-> no caching for all form controllers).
276      * @see #setCommandName
277      * @see #setCommandClass
278      * @see #setBindOnNewForm
279      * @see #setSessionForm
280      * @see #formBackingObject
281      */

282     public AbstractFormController() {
283         setCacheSeconds(0);
284     }
285
286     /**
287      * Set if request parameters should be bound to the form object
288      * in case of a non-submitting request, i.e. a new form.
289      */

290     public final void setBindOnNewForm(boolean bindOnNewForm) {
291         this.bindOnNewForm = bindOnNewForm;
292     }
293
294     /**
295      * Return if request parameters should be bound in case of a new form.
296      */

297     public final boolean isBindOnNewForm() {
298         return bindOnNewForm;
299     }
300
301     /**
302      * Activate/deactivate session form mode. In session form mode,
303      * the form is stored in the session to keep the form object instance
304      * between requests, instead of creating a new one on each request.
305      * <p>This is necessary for either wizard-style controllers that populate a
306      * single form object from multiple pages, or forms that populate a persistent
307      * object that needs to be identical to allow for tracking changes.
308      */

309     public final void setSessionForm(boolean sessionForm) {
310         this.sessionForm = sessionForm;
311     }
312
313     /**
314      * Return if session form mode is activated.
315      */

316     public final boolean isSessionForm() {
317         return sessionForm;
318     }
319
320     /**
321      * Specify whether the action phase is expected to call
322      * {@link ActionResponse#sendRedirect}.
323      * This information is important because some methods may not be called
324      * before {@link ActionResponse#sendRedirect}, e.g.
325      * {@link ActionResponse#setRenderParameter} and
326      * {@link ActionResponse#setRenderParameters}.
327      * @param redirectAction true if ActionResponse#sendRedirect is expected to be called
328      * @see ActionResponse#sendRedirect
329      */

330     public void setRedirectAction(boolean redirectAction) {
331         this.redirectAction = redirectAction;
332     }
333
334     /**
335      * Return if {@link ActionResponse#sendRedirect} is
336      * expected to be called in the action phase.
337      */

338     public boolean isRedirectAction() {
339         return redirectAction;
340     }
341
342     /**
343      * Specify the list of parameters that should be passed forward
344      * from the action phase to the render phase whenever the form is
345      * rerendered or when {@link #passRenderParameters} is called.
346      * @see #passRenderParameters
347      */

348     public void setRenderParameters(String JavaDoc[] parameters) {
349         this.renderParameters = parameters;
350     }
351
352     /**
353      * Returns the list of parameters that will be passed forward
354      * from the action phase to the render phase whenever the form is
355      * rerendered or when {@link #passRenderParameters} is called.
356      * @return the list of parameters
357      * @see #passRenderParameters
358      */

359     public String JavaDoc[] getRenderParameters() {
360         return renderParameters;
361     }
362
363
364     /**
365      * Handles action phase of two cases: form submissions and showing a new form.
366      * Delegates the decision between the two to <code>isFormSubmission</code>,
367      * always treating requests without existing form session attribute
368      * as new form when using session form mode.
369      * @see #isFormSubmission
370      * @see #processFormSubmission
371      * @see #handleRenderRequestInternal
372      */

373     protected void handleActionRequestInternal(ActionRequest request, ActionResponse response)
374             throws Exception JavaDoc {
375
376         // Form submission or new form to show?
377
if (isFormSubmission(request)) {
378             // Fetch form object, bind, validate, process submission.
379
try {
380                 Object JavaDoc command = getCommand(request);
381                 if (logger.isDebugEnabled()) {
382                     logger.debug("Processing valid submit (redirectAction = " + isRedirectAction() + ")");
383                 }
384                 if (!isRedirectAction()) {
385                     setFormSubmit(response);
386                 }
387                 PortletRequestDataBinder binder = bindAndValidate(request, command);
388                 BindException errors = new BindException(binder.getBindingResult());
389                 processFormSubmission(request, response, command, errors);
390                 setRenderCommandAndErrors(request, command, errors);
391                 return;
392             }
393             catch (PortletSessionRequiredException ex) {
394                 // Cannot submit a session form if no form object is in the session.
395
if (logger.isDebugEnabled()) {
396                     logger.debug("Invalid submit detected: " + ex.getMessage());
397                 }
398                 setFormSubmit(response);
399                 setInvalidSubmit(response);
400                 handleInvalidSubmit(request, response);
401                 return;
402             }
403         }
404
405         else {
406             logger.debug("Not a form submit - passing parameters to render phase");
407             passRenderParameters(request, response);
408             return;
409         }
410     }
411
412     /**
413      * Handles render phase of two cases: form submissions and showing a new form.
414      * Delegates the decision between the two to <code>isFormSubmission</code>,
415      * always treating requests without existing form session attribute
416      * as new form when using session form mode.
417      * @see #isFormSubmission
418      * @see #showNewForm
419      * @see #processFormSubmission
420      * @see #handleActionRequestInternal
421      */

422     protected ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response)
423             throws Exception JavaDoc {
424
425         // Form submission or new form to show?
426
if (isFormSubmission(request)) {
427
428             // If it is an invalid submit then handle it.
429
if (isInvalidSubmission(request)) {
430                 logger.debug("Invalid submit - calling renderInvalidSubmit");
431                 return renderInvalidSubmit(request, response);
432             }
433
434             // Valid submit -> render.
435
logger.debug("Valid submit - calling renderFormSubmission");
436             return renderFormSubmission(request, response, getRenderCommand(request), getRenderErrors(request));
437         }
438
439         else {
440             // New form to show: render form view.
441
return showNewForm(request, response);
442         }
443     }
444
445     /**
446      * Determine if the given request represents a form submission.
447      * <p>Default implementation checks to see if this is an ActionRequest
448      * and treats all action requests as form submission. During the action
449      * phase it will pass forward a render parameter to indicate to the render
450      * phase that this is a form submission. This method can check both
451      * kinds of requests and indicate if this is a form submission.
452      * <p>Subclasses can override this to use a custom strategy, e.g. a specific
453      * request parameter (assumably a hidden field or submit button name). Make
454      * sure that the override can handle both ActionRequest and RenderRequest
455      * objects properly.
456      * @param request current request
457      * @return if the request represents a form submission
458      */

459     protected boolean isFormSubmission(PortletRequest request) {
460         return (request instanceof ActionRequest ? true :
461                 TRUE.equals(request.getParameter(getFormSubmitParameterName())));
462     }
463
464     /**
465      * Determine if the given request represents an invalid form submission.
466      */

467     protected boolean isInvalidSubmission(PortletRequest request) {
468         return TRUE.equals(request.getParameter(getInvalidSubmitParameterName()));
469     }
470
471     /**
472      * Return the name of the render parameter that indicates this
473      * was a form submission.
474      * @return the name of the render parameter
475      * @see javax.portlet.RenderRequest#getParameter
476      */

477     protected String JavaDoc getFormSubmitParameterName() {
478         return FORM_SUBMISSION_PARAMETER;
479     }
480
481     /**
482      * Return the name of the render parameter that indicates this
483      * was an invalid form submission.
484      * @return the name of the render parameter
485      * @see javax.portlet.RenderRequest#getParameter
486      */

487     protected String JavaDoc getInvalidSubmitParameterName() {
488         return INVALID_SUBMISSION_PARAMETER;
489     }
490
491     /**
492      * Set the action response parameter that indicates this in a form submission.
493      * @param response the current action response
494      * @see #getFormSubmitParameterName()
495      */

496     protected final void setFormSubmit(ActionResponse response) {
497         if (logger.isDebugEnabled()) {
498             logger.debug("Setting render parameter [" + getFormSubmitParameterName() +
499                     "] to indicate this is a form submission");
500         }
501         try {
502             response.setRenderParameter(getFormSubmitParameterName(), TRUE);
503         }
504         catch (IllegalStateException JavaDoc ex) {
505             // Ignore in case sendRedirect was already set.
506
}
507     }
508
509     /**
510      * Set the action response parameter that indicates this in an invalid submission.
511      * @param response the current action response
512      * @see #getInvalidSubmitParameterName()
513      */

514     protected final void setInvalidSubmit(ActionResponse response) {
515         if (logger.isDebugEnabled()) {
516             logger.debug("Setting render parameter [" + getInvalidSubmitParameterName() +
517                     "] to indicate this is an invalid submission");
518         }
519         try {
520             response.setRenderParameter(getInvalidSubmitParameterName(), TRUE);
521         }
522         catch (IllegalStateException JavaDoc ex) {
523             // Ignore in case sendRedirect was already set.
524
}
525     }
526
527     /**
528      * Return the name of the PortletSession attribute that holds the form object
529      * for this form controller.
530      * <p>Default implementation delegates to the <code>getFormSessionAttributeName</code>
531      * version without arguments.
532      * @param request current HTTP request
533      * @return the name of the form session attribute, or null if not in session form mode
534      * @see #getFormSessionAttributeName
535      * @see javax.portlet.PortletSession#getAttribute
536      */

537     protected String JavaDoc getFormSessionAttributeName(PortletRequest request) {
538         return getFormSessionAttributeName();
539     }
540
541     /**
542      * Return the name of the PortletSession attribute that holds the form object
543      * for this form controller.
544      * <p>Default is an internal name, of no relevance to applications, as the form
545      * session attribute is not usually accessed directly. Can be overridden to use
546      * an application-specific attribute name, which allows other code to access
547      * the session attribute directly.
548      * @return the name of the form session attribute
549      * @see javax.portlet.PortletSession#getAttribute
550      */

551     protected String JavaDoc getFormSessionAttributeName() {
552         return getClass().getName() + ".FORM." + getCommandName();
553     }
554
555     /**
556      * Pass the specified list of action request parameters to the render phase
557      * by putting them into the action response object. This may not be called
558      * when the action will call will call
559      * {@link ActionResponse#sendRedirect sendRedirect}.
560      * @param request the current action request
561      * @param response the current action response
562      * @see ActionResponse#setRenderParameter
563      */

564     protected void passRenderParameters(ActionRequest request, ActionResponse response) {
565         if (this.renderParameters == null) {
566             return;
567         }
568         try {
569             for (int i = 0; i < this.renderParameters.length; i++) {
570                 String JavaDoc paramName = this.renderParameters[i];
571                 String JavaDoc paramValues[] = request.getParameterValues(paramName);
572                 if (paramValues != null) {
573                     if (logger.isDebugEnabled()) {
574                         logger.debug("Passing parameter to render phase '" + paramName + "' = " +
575                                 (paramValues == null ? "NULL" : Arrays.asList(paramValues).toString()));
576                     }
577                     response.setRenderParameter(paramName, paramValues);
578                 }
579             }
580         }
581         catch (IllegalStateException JavaDoc ex) {
582             // Ignore in case sendRedirect was already set.
583
}
584     }
585
586
587     /**
588      * Show a new form. Prepares a backing object for the current form
589      * and the given request, including checking its validity.
590      * @param request current render request
591      * @param response current render response
592      * @return the prepared form view
593      * @throws Exception in case of an invalid new form object
594      * @see #getErrorsForNewForm
595      */

596     protected final ModelAndView showNewForm(RenderRequest request, RenderResponse response)
597             throws Exception JavaDoc {
598
599         logger.debug("Displaying new form");
600         return showForm(request, response, getErrorsForNewForm(request));
601     }
602
603     /**
604      * Create a BindException instance for a new form.
605      * Called by <code>showNewForm</code>.
606      * <p>Can be used directly when intending to show a new form but with
607      * special errors registered on it (for example, on invalid submit).
608      * Usually, the resulting BindException will be passed to
609      * <code>showForm</code>, after registering the errors on it.
610      * @param request current render request
611      * @return the BindException instance
612      * @throws Exception in case of an invalid new form object
613      */

614     protected final BindException getErrorsForNewForm(RenderRequest request) throws Exception JavaDoc {
615         // Create form-backing object for new form
616
Object JavaDoc command = formBackingObject(request);
617         if (command == null) {
618             throw new PortletException("Form object returned by formBackingObject() must not be null");
619         }
620         if (!checkCommand(command)) {
621             throw new PortletException("Form object returned by formBackingObject() must match commandClass");
622         }
623
624         // Bind without validation, to allow for prepopulating a form, and for
625
// convenient error evaluation in views (on both first attempt and resubmit).
626
PortletRequestDataBinder binder = createBinder(request, command);
627         BindException errors = new BindException(binder.getBindingResult());
628
629         if (isBindOnNewForm()) {
630             if (logger.isDebugEnabled()) {
631                 logger.debug("Binding to new form");
632             }
633             binder.bind(request);
634             onBindOnNewForm(request, command, errors);
635         }
636         
637         // Return BindException object that resulted from binding.
638
return errors;
639     }
640
641     /**
642      * Callback for custom post-processing in terms of binding for a new form.
643      * Called when preparing a new form if <code>bindOnNewForm</code> is <code>true</code>.
644      * <p>Default implementation delegates to <code>onBindOnNewForm(request, command)</code>.
645      * @param request current render request
646      * @param command the command object to perform further binding on
647      * @param errors validation errors holder, allowing for additional
648      * custom registration of binding errors
649      * @throws Exception in case of invalid state or arguments
650      * @see #onBindOnNewForm(RenderRequest, Object)
651      * @see #setBindOnNewForm
652      */

653     protected void onBindOnNewForm(RenderRequest request, Object JavaDoc command, BindException errors)
654             throws Exception JavaDoc {
655
656         onBindOnNewForm(request, command);
657     }
658
659     /**
660      * Callback for custom post-processing in terms of binding for a new form.
661      * Called by the default implementation of the <code>onBindOnNewForm</code> version
662      * with all parameters, after standard binding when displaying the form view.
663      * Only called if <code>bindOnNewForm</code> is set to <code>true</code>.
664      * <p>Default implementation is empty.
665      * @param request current render request
666      * @param command the command object to perform further binding on
667      * @throws Exception in case of invalid state or arguments
668      * @see #onBindOnNewForm(RenderRequest, Object, BindException)
669      * @see #setBindOnNewForm(boolean)
670      */

671     protected void onBindOnNewForm(RenderRequest request, Object JavaDoc command) throws Exception JavaDoc {
672     }
673
674
675     /**
676      * Return the form object for the given request.
677      * <p>Calls <code>formBackingObject</code> if the object is not in the session
678      * @param request current request
679      * @return object form to bind onto
680      * @see #formBackingObject
681      */

682     protected final Object JavaDoc getCommand(PortletRequest request) throws Exception JavaDoc {
683         // If not in session-form mode, create a new form-backing object.
684
if (!isSessionForm()) {
685             if (logger.isDebugEnabled()) {
686                 logger.debug("Not a session-form -- using new formBackingObject");
687             }
688             return formBackingObject(request);
689         }
690         
691         // Session-form mode: retrieve form object from portlet session attribute.
692
PortletSession session = request.getPortletSession(false);
693         if (session == null) {
694             throw new PortletSessionRequiredException("Must have session when trying to bind (in session-form mode)");
695         }
696         String JavaDoc formAttrName = getFormSessionAttributeName(request);
697         Object JavaDoc sessionFormObject = session.getAttribute(formAttrName);
698         if (sessionFormObject == null) {
699             throw new PortletSessionRequiredException("Form object not found in session (in session-form mode)");
700         }
701
702         // Remove form object from porlet session: we might finish the form workflow
703
// in this request. If it turns out that we need to show the form view again,
704
// we'll re-bind the form object to the portlet session.
705
if (logger.isDebugEnabled()) {
706             logger.debug("Removing form session attribute [" + formAttrName + "]");
707         }
708         session.removeAttribute(formAttrName);
709
710         // Check the command object to make sure its valid
711
if (!checkCommand(sessionFormObject)) {
712             throw new PortletSessionRequiredException("Object found in session does not match commandClass");
713         }
714
715         return sessionFormObject;
716     }
717
718     /**
719      * Retrieve a backing object for the current form from the given request.
720      * <p>The properties of the form object will correspond to the form field values
721      * in your form view. This object will be exposed in the model under the specified
722      * command name, to be accessed under that name in the view: for example, with
723      * a "spring:bind" tag. The default command name is "command".
724      * <p>Note that you need to activate session form mode to reuse the form-backing
725      * object across the entire form workflow. Else, a new instance of the command
726      * class will be created for each submission attempt, just using this backing
727      * object as template for the initial form.
728      * <p>Default implementation calls <code>BaseCommandController.createCommand</code>,
729      * creating a new empty instance of the command class.
730      * Subclasses can override this to provide a preinitialized backing object.
731      * @param request current portlet request
732      * @return the backing object
733      * @throws Exception in case of invalid state or arguments
734      * @see #setCommandName
735      * @see #setCommandClass
736      * @see #createCommand
737      */

738     protected Object JavaDoc formBackingObject(PortletRequest request) throws Exception JavaDoc {
739         return createCommand();
740     }
741
742
743     /**
744      * Prepare the form model and view, including reference and error data.
745      * Can show a configured form page, or generate a form view programmatically.
746      * <p>A typical implementation will call
747      * <code>showForm(request, errors, "myView")</code>
748      * to prepare the form view for a specific view name, returning the
749      * ModelAndView provided there.
750      * <p>For building a custom ModelAndView, call <code>errors.getModel()</code>
751      * to populate the ModelAndView model with the command and the Errors instance,
752      * under the specified command name, as expected by the "spring:bind" tag.
753      * You also need to include the model returned by <code>referenceData</code>.
754      * <p>Note: If you decide to have a "formView" property specifying the
755      * view name, consider using SimpleFormController.
756      * @param request current render request
757      * @param response current render response
758      * @param errors validation errors holder
759      * @return the prepared form view, or null if handled directly
760      * @throws Exception in case of invalid state or arguments
761      * @see #showForm(RenderRequest, BindException, String)
762      * @see org.springframework.validation.Errors
763      * @see org.springframework.validation.BindException#getModel
764      * @see #referenceData(PortletRequest, Object, Errors)
765      * @see SimpleFormController#setFormView
766      */

767     protected abstract ModelAndView showForm(RenderRequest request, RenderResponse response, BindException errors) throws Exception JavaDoc;
768
769     /**
770      * Prepare model and view for the given form, including reference and errors.
771      * <p>In session form mode: Re-puts the form object in the session when
772      * returning to the form, as it has been removed by getCommand.
773      * <p>Can be used in subclasses to redirect back to a specific form page.
774      * @param request current render request
775      * @param errors validation errors holder
776      * @param viewName name of the form view
777      * @return the prepared form view
778      * @throws Exception in case of invalid state or arguments
779      * @see #showForm(RenderRequest, BindException, String, Map)
780      * @see #showForm(RenderRequest, RenderResponse, BindException)
781      */

782     protected final ModelAndView showForm(RenderRequest request, BindException errors, String JavaDoc viewName)
783             throws Exception JavaDoc {
784
785         return showForm(request, errors, viewName, null);
786     }
787
788     /**
789      * Prepare model and view for the given form, including reference and errors,
790      * adding a controller-specific control model.
791      * <p>In session form mode: Re-puts the form object in the session when returning
792      * to the form, as it has been removed by getCommand.
793      * <p>Can be used in subclasses to redirect back to a specific form page.
794      * @param request current render request
795      * @param errors validation errors holder
796      * @param viewName name of the form view
797      * @param controlModel model map containing controller-specific control data
798      * (e.g. current page in wizard-style controllers or special error message)
799      * @return the prepared form view
800      * @throws Exception in case of invalid state or arguments
801      * @see #showForm(RenderRequest, BindException, String)
802      * @see #showForm(RenderRequest, RenderResponse, BindException)
803      */

804     protected final ModelAndView showForm(RenderRequest request, BindException errors, String JavaDoc viewName, Map JavaDoc controlModel)
805             throws Exception JavaDoc {
806
807         // In session form mode, re-expose form object as portlet session attribute.
808
// Re-binding is necessary for proper state handling in a cluster,
809
// to notify other nodes of changes in the form object.
810
if (isSessionForm()) {
811             String JavaDoc formAttrName = getFormSessionAttributeName(request);
812             if (logger.isDebugEnabled()) {
813                 logger.debug("Setting form session attribute [" + formAttrName + "] to: " + errors.getTarget());
814             }
815             request.getPortletSession().setAttribute(formAttrName, errors.getTarget());
816         }
817
818         // Fetch errors model as starting point, containing form object under
819
// "commandName", and corresponding Errors instance under internal key.
820
Map JavaDoc model = errors.getModel();
821         
822         // Merge reference data into model, if any.
823
Map JavaDoc referenceData = referenceData(request, errors.getTarget(), errors);
824         if (referenceData != null) {
825             model.putAll(referenceData);
826         }
827
828         // Merge control attributes into model, if any.
829
if (controlModel != null) {
830             model.putAll(controlModel);
831         }
832
833         // Trigger rendering of the specified view, using the final model.
834
return new ModelAndView(viewName, model);
835     }
836
837     /**
838      * Create a reference data map for the given request, consisting of
839      * bean name/bean instance pairs as expected by ModelAndView.
840      * <p>Default implementation returns null.
841      * Subclasses can override this to set reference data used in the view.
842      * @param request current render request
843      * @param command form object with request parameters bound onto it
844      * @param errors validation errors holder
845      * @return a Map with reference data entries, or null if none
846      * @throws Exception in case of invalid state or arguments
847      * @see ModelAndView
848      */

849     protected Map JavaDoc referenceData(PortletRequest request, Object JavaDoc command, Errors errors) throws Exception JavaDoc {
850         return null;
851     }
852
853
854     /**
855      * Process render phase of form submission request. Called by <code>handleRequestInternal</code>
856      * in case of a form submission, with or without binding errors. Implementations
857      * need to proceed properly, typically showing a form view in case of binding
858      * errors or rendering the result of a submit action else.
859      * <p>For a success view, call <code>errors.getModel()</code> to populate the
860      * ModelAndView model with the command and the Errors instance, under the
861      * specified command name, as expected by the "spring:bind" tag. For a form view,
862      * simply return the ModelAndView object privded by <code>showForm</code>.
863      * @param request current render request
864      * @param response current render response
865      * @param command form object with request parameters bound onto it
866      * @param errors errors holder
867      * @return the prepared model and view, or null
868      * @throws Exception in case of errors
869      * @see #handleRenderRequestInternal
870      * @see #processFormSubmission
871      * @see #isFormSubmission
872      * @see #showForm(RenderRequest, RenderResponse, BindException)
873      * @see org.springframework.validation.Errors
874      * @see org.springframework.validation.BindException#getModel
875      */

876     protected abstract ModelAndView renderFormSubmission(RenderRequest request, RenderResponse response, Object JavaDoc command, BindException errors)
877             throws Exception JavaDoc;
878
879     /**
880      * Process action phase of form submission request. Called by <code>handleRequestInternal</code>
881      * in case of a form submission, with or without binding errors. Implementations
882      * need to proceed properly, typically performing a submit action if there are no binding errors.
883      * <p>Subclasses can implement this to provide custom submission handling
884      * like triggering a custom action. They can also provide custom validation
885      * or proceed with the submission accordingly.
886      * @param request current action request
887      * @param response current action response
888      * @param command form object with request parameters bound onto it
889      * @param errors errors holder (subclass can add errors if it wants to)
890      * @throws Exception in case of errors
891      * @see #handleActionRequestInternal
892      * @see #renderFormSubmission
893      * @see #isFormSubmission
894      * @see org.springframework.validation.Errors
895      */

896     protected abstract void processFormSubmission(ActionRequest request, ActionResponse response, Object JavaDoc command, BindException errors)
897             throws Exception JavaDoc;
898
899     /**
900      * Handle an invalid submit request, e.g. when in session form mode but no form object
901      * was found in the session (like in case of an invalid resubmit by the browser).
902      * <p>Default implementation simply tries to resubmit the form with a new form object.
903      * This should also work if the user hit the back button, changed some form data,
904      * and resubmitted the form.
905      * <p>Note: To avoid duplicate submissions, you need to override this method.
906      * Either show some "invalid submit" message, or call <code>showNewForm</code> for
907      * resetting the form (prepopulating it with the current values if "bindOnNewForm"
908      * is true). In this case, the form object in the session serves as transaction token.
909      * <pre>
910      * protected ModelAndView handleInvalidSubmit(RenderRequest request, RenderResponse response) throws Exception {
911      * return showNewForm(request, response);
912      * }</pre>
913      * You can also show a new form but with special errors registered on it:
914      * <pre>
915      * protected ModelAndView handleInvalidSubmit(RenderRequest request, RenderResponse response) throws Exception {
916      * BindException errors = getErrorsForNewForm(request);
917      * errors.reject("duplicateFormSubmission", "Duplicate form submission");
918      * return showForm(request, response, errors);
919      * }</pre>
920      * <p><b>WARNING:</b> If you override this method, be sure to also override the action
921      * phase version of this method so that it will not attempt to perform the resubmit
922      * action by default.
923      * @param request current render request
924      * @param response current render response
925      * @return a prepared view, or null if handled directly
926      * @throws Exception in case of errors
927      * @see #handleInvalidSubmit
928      */

929     protected ModelAndView renderInvalidSubmit(RenderRequest request, RenderResponse response)
930             throws Exception JavaDoc {
931
932         return renderFormSubmission(request, response, getRenderCommand(request), getRenderErrors(request));
933     }
934
935     /**
936      * Handle an invalid submit request, e.g. when in session form mode but no form object
937      * was found in the session (like in case of an invalid resubmit by the browser).
938      * <p>Default implementation simply tries to resubmit the form with a new form object.
939      * This should also work if the user hit the back button, changed some form data,
940      * and resubmitted the form.
941      * <p>Note: To avoid duplicate submissions, you need to override this method.
942      * Most likely you will simply want it to do nothing here in the action phase
943      * and diplay an appropriate error and a new form in the render phase.
944      * <pre>
945      * protected void handleInvalidSubmit(ActionRequest request, ActionResponse response) throws Exception {
946      * }</pre>
947      * <p>If you override this method but you do need a command object and bind errors
948      * in the render phase, be sure to call {@link #setRenderCommandAndErrors setRenderCommandAndErrors}
949      * from here.
950      * @param request current action request
951      * @param response current action response
952      * @throws Exception in case of errors
953      * @see #renderInvalidSubmit
954      * @see #setRenderCommandAndErrors
955      */

956     protected void handleInvalidSubmit(ActionRequest request, ActionResponse response) throws Exception JavaDoc {
957         passRenderParameters(request, response);
958         Object JavaDoc command = formBackingObject(request);
959         if (command == null) {
960             throw new PortletException("Form object returned by formBackingObject() must not be null");
961         }
962         if (!checkCommand(command)) {
963             throw new PortletException("Form object returned by formBackingObject() must match commandClass");
964         }
965         PortletRequestDataBinder binder = bindAndValidate(request, command);
966         BindException errors = new BindException(binder.getBindingResult());
967         processFormSubmission(request, response, command, errors);
968         setRenderCommandAndErrors(request, command, errors);
969     }
970
971 }
972
Popular Tags