KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > webflow > action > FormAction


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 package org.springframework.webflow.action;
17
18 import java.lang.reflect.Method JavaDoc;
19
20 import org.springframework.beans.MutablePropertyValues;
21 import org.springframework.beans.PropertyEditorRegistrar;
22 import org.springframework.beans.PropertyEditorRegistry;
23 import org.springframework.beans.factory.InitializingBean;
24 import org.springframework.beans.propertyeditors.PropertiesEditor;
25 import org.springframework.core.style.StylerUtils;
26 import org.springframework.util.Assert;
27 import org.springframework.util.ClassUtils;
28 import org.springframework.util.StringUtils;
29 import org.springframework.validation.BindException;
30 import org.springframework.validation.DataBinder;
31 import org.springframework.validation.Errors;
32 import org.springframework.validation.MessageCodesResolver;
33 import org.springframework.validation.Validator;
34 import org.springframework.web.bind.WebDataBinder;
35 import org.springframework.webflow.execution.Event;
36 import org.springframework.webflow.execution.RequestContext;
37 import org.springframework.webflow.execution.ScopeType;
38 import org.springframework.webflow.util.DispatchMethodInvoker;
39 import org.springframework.webflow.util.ReflectionUtils;
40
41 /**
42  * Multi-action that implements common logic dealing with input forms. This
43  * class leverages the Spring Web data binding code to do binding and
44  * validation.
45  * <p>
46  * Several action execution methods are provided:
47  * <ul>
48  * <li> {@link #setupForm(RequestContext)} - Prepares the form object for
49  * display on a form, {@link #createFormObject(RequestContext) creating it} and
50  * an associated {@link Errors errors object} if necessary, then caching them in
51  * the configured {@link #getFormObjectScope() form object scope} and
52  * {@link #getFormErrorsScope() form errors scope}, respectively. Also
53  * {@link #registerPropertyEditors(PropertyEditorRegistry) installs} any custom
54  * property editors for formatting form object field values. This action method
55  * will return the "success" event unless an exception is thrown. </li>
56  * <li> {@link #bindAndValidate(RequestContext)} - Binds all incoming request
57  * parameters to the form object and then validates the form object using a
58  * {@link #setValidator(Validator) registered validator}. This action method
59  * will return the "success" event if there are no binding or validation errors,
60  * otherwise it will return the "error" event. </li>
61  * <li> {@link #bind(RequestContext)} - Binds all incoming request parameters to
62  * the form object. No additional validation is performed. This action method
63  * will return the "success" event if there are no binding errors, otherwise it
64  * will return the "error" event. </li>
65  * <li> {@link #validate(RequestContext)} - Validates the form object using a
66  * registered validator. No data binding is performed. This action method will
67  * return the "success" event if there are no validation errors, otherwise it
68  * will return the "error" event. </li>
69  * <li> {@link #resetForm(RequestContext)} - Resets the form by reloading the
70  * backing form object and reinstalling any custom property editors. Returns
71  * "success" on completion, an exception is thrown when a failure occurs. </li>
72  * </ul>
73  * <p>
74  * Since this is a multi-action a subclass could add any number of additional
75  * action execution methods, e.g. "setupReferenceData(RequestContext)", or
76  * "processSubmit(RequestContext)".
77  * <p>
78  * Using this action, it becomes very easy to implement form preparation and
79  * submission logic in your flow. One way to do this follows:
80  * <ol>
81  * <li> Create a view state to display the form. In a render action of that
82  * state, invoke {@link #setupForm(RequestContext) setupForm} to prepare the new
83  * form for display. </li>
84  * <li> On a matching "submit" transition execute an action that invokes
85  * {@link #bindAndValidate(RequestContext) bindAndValidate} to bind incoming
86  * request parameters to the form object and validate the form object. </li>
87  * <li> If there are binding or validation errors, the transition will not be
88  * allowed and the view state will automatically be re-entered.
89  * <li> If binding and validation is successful go to an action state called
90  * "processSubmit" (or any other appropriate name). This will invoke an action
91  * method called "processSubmit" you must provide on a subclass to process form
92  * submission, e.g. interacting with the business logic. </li>
93  * <li> If business processing is ok, continue to a view state to display the
94  * success view. </li>
95  * </ol>
96  * <p>
97  * Here is an example implementation of such a compact form flow:
98  *
99  * <pre>
100  * &lt;view-state id=&quot;displayCriteria&quot; view=&quot;searchCriteria&quot;&gt;
101  * &lt;render-actions&gt;
102  * &lt;action bean=&quot;formAction&quot; method=&quot;setupForm&quot;/&gt;
103  * &lt;/render-actions&gt;
104  * &lt;transition on=&quot;search&quot; to=&quot;executeSearch&quot;&gt;
105  * &lt;action bean=&quot;formAction&quot; method=&quot;bindAndValidate&quot;/&gt;
106  * &lt;/transition&gt;
107  * &lt;/view-state&gt;
108  *
109  * &lt;action-state id=&quot;executeSearch&quot;&gt;
110  * &lt;action bean=&quot;formAction&quot; method=&quot;executeSearch&quot;/&gt;
111  * &lt;transition on=&quot;success&quot; to=&quot;displayResults&quot;/&gt;
112  * &lt;/action-state&gt;
113  * </pre>
114  *
115  * <p>
116  * When you need additional flexibility consider splitting the view state above
117  * acting as a single logical <i>form state</i> into multiple states. For
118  * example, you could have one action state handle form setup, a view state
119  * trigger form display, another action state handle data binding and
120  * validation, and another process form submission. This would be a bit more
121  * verbose but would also give you more control over how you respond to specific
122  * results of fine-grained actions that occur within the flow.
123  * <p>
124  * <b>Subclassing hooks:</b>
125  * <ul>
126  * <li>A important hook is
127  * {@link #createFormObject(RequestContext) createFormObject}. You may override
128  * this to customize where the backing form object instance comes from (e.g
129  * instantiated transiently in memory or loaded from a database).</li>
130  * <li>An optional hook method provided by this class is
131  * {@link #initBinder(RequestContext, DataBinder) initBinder}. This is called
132  * after a new data binder is created.
133  * <li>Another optional ook method is
134  * {@link #registerPropertyEditors(PropertyEditorRegistry)}. By overriding it
135  * you can register any required property editors for your form. Instead of
136  * overriding this method, consider setting an explicit
137  * {@link org.springframework.beans.PropertyEditorRegistrar} strategy as a more
138  * reusable way to encapsulate custom PropertyEditor installation logic.</li>
139  * <li>Override {@link #validationEnabled(RequestContext)} to dynamically
140  * decide whether or not to do validation based on data available in the request
141  * context.
142  * </ul>
143  * <p>
144  * Note that this action does not provide a <i>referenceData()</i> hook method
145  * similar to that of Spring MVC's <code>SimpleFormController</code>. If you
146  * wish to expose reference data to populate form drop downs you can define a
147  * custom action method in your FormAction subclass that does just that. Simply
148  * invoke it as either a chained action as part of the setupForm state, or as a
149  * fine grained state definition itself.
150  * <p>
151  * For example, you might create this method in your subclass:
152  *
153  * <pre>
154  * public Event setupReferenceData(RequestContext context) throws Exception {
155  * MutableAttributeMap requestScope = context.getRequestScope();
156  * requestScope.put(&quot;refData&quot;, lookupService.getSupportingFormData());
157  * return success();
158  * }
159  * </pre>
160  *
161  * ... and then invoke it like this:
162  *
163  * <pre>
164  * &lt;view-state id=&quot;displayCriteria&quot; view=&quot;searchCriteria&quot;&gt;
165  * &lt;render-actions&gt;
166  * &lt;action bean=&quot;searchFormAction&quot; method=&quot;setupForm&quot;/&gt;
167  * &lt;action bean=&quot;searchFormAction&quot; method=&quot;setupReferenceData&quot;/&gt;
168  * &lt;/render-actions&gt;
169  * ...
170  * &lt;/view-state&gt;
171  * </pre>
172  *
173  * This style of calling multiple action methods in a chain (Chain of
174  * Responsibility) is preferred to overridding a single action method. In
175  * general, action method overriding is discouraged.
176  * <p>
177  * When it comes to validating submitted input data using a registered
178  * {@link org.springframework.validation.Validator}, this class offers the
179  * following options:
180  * <ul>
181  * <li>If you don't want validation at all, just call
182  * {@link #bind(RequestContext)} instead of
183  * {@link #bindAndValidate(RequestContext)} or don't register a validator.</li>
184  * <li>If you want piecemeal validation, e.g. in a multi-page wizard, call
185  * {@link #bindAndValidate(RequestContext)} or {@link #validate(RequestContext)}
186  * and specify a {@link #VALIDATOR_METHOD_ATTRIBUTE validatorMethod} action
187  * execution attribute. This will invoke the identified custom validator method
188  * on the validator. The validator method signature should follow the following
189  * pattern:
190  *
191  * <pre>
192  * public void ${validateMethodName}(${formObjectClass}, Errors)
193  * </pre>
194  *
195  * For instance, having a action definition like this:
196  *
197  * <pre>
198  * &lt;action bean=&quot;searchFormAction&quot; method=&quot;bindAndValidate&quot;&gt;
199  * &lt;attribute name=&quot;validatorMethod&quot; value=&quot;validateSearchCriteria&quot;/&gt;
200  * &lt;/action&gt;
201  * </pre>
202  *
203  * Would result in the
204  * <tt>public void validateSearchCriteria(SearchCriteria, Errors)</tt> method
205  * of the registered validator being called if the form object class would be
206  * <code>SearchCriteria</code>.</li>
207  * <li>If you want to do full validation using the
208  * {@link org.springframework.validation.Validator#validate(java.lang.Object, org.springframework.validation.Errors) validate}
209  * method of the registered validator, call
210  * {@link #bindAndValidate(RequestContext)} or {@link #validate(RequestContext)}
211  * without specifying a "validatorMethod" action execution attribute.</li>
212  * </ul>
213  *
214  * <p>
215  * <b>FormAction configurable properties</b><br>
216  * <table border="1">
217  * <tr>
218  * <td><b>name</b></td>
219  * <td><b>default</b></td>
220  * <td><b>description</b></td>
221  * </tr>
222  * <tr>
223  * <td>formObjectName</td>
224  * <td>formObject</td>
225  * <td>The name of the form object. The form object will be set in the
226  * configured scope using this name.</td>
227  * </tr>
228  * <tr>
229  * <td>formObjectClass</td>
230  * <td>null</td>
231  * <td>The form object class for this action. An instance of this class will
232  * get populated and validated. Required when using a validator.</td>
233  * </tr>
234  * <tr>
235  * <td>formObjectScope</td>
236  * <td>{@link org.springframework.webflow.execution.ScopeType#FLOW flow}</td>
237  * <td>The scope in which the form object will be put. If put in flow scope the
238  * object will be cached and reused over the life of the flow, preserving
239  * previous values. Request scope will cause a new fresh form object instance to
240  * be created on each request into the flow execution.</td>
241  * </tr>
242  * <tr>
243  * <td>formErrorsScope</td>
244  * <td>{@link org.springframework.webflow.execution.ScopeType#FLASH flash}</td>
245  * <td>The scope in which the form object errors instance will be put. If put
246  * in flash scope form errors will be cached until the next user event is signaled.
247  * </td>
248  * </tr>
249  * <tr>
250  * <td>propertyEditorRegistrar</td>
251  * <td>null</td>
252  * <td>The strategy used to register custom property editors with the data
253  * binder. This is an alternative to overriding the
254  * {@link #registerPropertyEditors(PropertyEditorRegistry)} hook method. </td>
255  * </tr>
256  * <tr>
257  * <td>validator</td>
258  * <td>null</td>
259  * <td>The validator for this action. The validator must support the specified
260  * form object class. </td>
261  * </tr>
262  * <tr>
263  * <td>messageCodesResolver</td>
264  * <td>null</td>
265  * <td>Set the strategy to use for resolving errors into message codes. </td>
266  * </tr>
267  * </table>
268  *
269  * @see org.springframework.beans.PropertyEditorRegistrar
270  * @see org.springframework.validation.DataBinder
271  * @see ScopeType
272  *
273  * @author Erwin Vervaet
274  * @author Keith Donald
275  */

276 public class FormAction extends MultiAction implements InitializingBean {
277
278     /*
279      * Implementation note: Uses deprecated DataBinder.getErrors() to remain
280      * compatible with Spring 1.2.x.
281      */

282
283     /*
284      * Implementation note: Introspects BindException at class init time to
285      * preserve 1.2.x compatability.
286      */

287     private static boolean hasPropertyEditorRegistryAccessor;
288
289     static {
290         hasPropertyEditorRegistryAccessor = ClassUtils
291                 .hasMethod(BindException.class, "getPropertyEditorRegistry", null);
292     }
293
294     /**
295      * The default form object name ("formObject").
296      */

297     public static final String JavaDoc DEFAULT_FORM_OBJECT_NAME = "formObject";
298
299     /**
300      * Optional attribute that identifies the method that should be invoked on
301      * the configured validator instance, to support piecemeal wizard page
302      * validation ("validatorMethod").
303      */

304     public static final String JavaDoc VALIDATOR_METHOD_ATTRIBUTE = "validatorMethod";
305
306     /**
307      * The name the form object should be exposed under. Default is
308      * {@link #DEFAULT_FORM_OBJECT_NAME}.
309      */

310     private String JavaDoc formObjectName = DEFAULT_FORM_OBJECT_NAME;
311
312     /**
313      * The type of form object, typically an instantiable class. Required if
314      * {@link #createFormObject(RequestContext)} is not overidden or when
315      * a validator is used.
316      */

317     private Class JavaDoc formObjectClass;
318
319     /**
320      * The scope in which the form object should be exposed. Default is
321      * {@link ScopeType#FLOW}.
322      */

323     private ScopeType formObjectScope = ScopeType.FLOW;
324
325     /**
326      * The scope in which the form object errors holder should be exposed.
327      * Default is {@link ScopeType#FLASH}.
328      */

329     private ScopeType formErrorsScope = ScopeType.FLASH;
330
331     /**
332      * A centralized service for property editor registration, for applying type
333      * conversion during form object data binding. Can be used as an alternative
334      * to overriding {@link #registerPropertyEditors(PropertyEditorRegistry)}.
335      */

336     private PropertyEditorRegistrar propertyEditorRegistrar;
337
338     /**
339      * A validator for the form's form object.
340      */

341     private Validator validator;
342
343     /**
344      * Strategy for resolving error message codes.
345      */

346     private MessageCodesResolver messageCodesResolver;
347
348     /**
349      * A cache for dispatched validator methods.
350      */

351     private DispatchMethodInvoker validateMethodInvoker;
352
353     /**
354      * Bean-style default constructor; creates a initially unconfigured
355      * FormAction instance relying on default property values. Clients invoking
356      * this constructor directly must set the {@link #formObjectClass} property
357      * or override {@link #createFormObject(RequestContext)}.
358      * @see #setFormObjectClass(Class)
359      */

360     public FormAction() {
361     }
362
363     /**
364      * Creates a new form action that manages instance(s) of the specified form
365      * object class.
366      * @param formObjectClass the class of the form object (must be instantiable)
367      */

368     public FormAction(Class JavaDoc formObjectClass) {
369         setFormObjectClass(formObjectClass);
370     }
371
372     /**
373      * Return the name of the form object in the configured scope.
374      */

375     public String JavaDoc getFormObjectName() {
376         return formObjectName;
377     }
378
379     /**
380      * Set the name of the form object in the configured scope. The form object
381      * will be included in the configured scope under this name.
382      */

383     public void setFormObjectName(String JavaDoc formObjectName) {
384         this.formObjectName = formObjectName;
385     }
386
387     /**
388      * Return the form object class for this action.
389      */

390     public Class JavaDoc getFormObjectClass() {
391         return formObjectClass;
392     }
393
394     /**
395      * Set the form object class for this action. An instance of this class will
396      * get populated and validated. This is a required property if you register
397      * a validator with the form action ({@link #setValidator(Validator)})!
398      * <p>
399      * If no form object name is set at the moment this method is called, a
400      * form object name will be automatically generated based on the provided
401      * form object class using
402      * {@link ClassUtils#getShortNameAsProperty(java.lang.Class)}.
403      */

404     public void setFormObjectClass(Class JavaDoc formObjectClass) {
405         this.formObjectClass = formObjectClass;
406         // generate a default form object name
407
if ((formObjectName == null || formObjectName == DEFAULT_FORM_OBJECT_NAME) && formObjectClass != null) {
408             formObjectName = ClassUtils.getShortNameAsProperty(formObjectClass);
409         }
410     }
411
412     /**
413      * Get the scope in which the form object will be placed.
414      */

415     public ScopeType getFormObjectScope() {
416         return formObjectScope;
417     }
418
419     /**
420      * Set the scope in which the form object will be placed. The default
421      * if not set is {@link ScopeType#FLOW flow scope}.
422      */

423     public void setFormObjectScope(ScopeType scopeType) {
424         this.formObjectScope = scopeType;
425     }
426
427     /**
428      * Get the scope in which the Errors object will be placed.
429      */

430     public ScopeType getFormErrorsScope() {
431         return formErrorsScope;
432     }
433
434     /**
435      * Set the scope in which the Errors object will be placed. The default
436      * if not set is {@link ScopeType#FLASH flash scope}.
437      */

438     public void setFormErrorsScope(ScopeType errorsScope) {
439         this.formErrorsScope = errorsScope;
440     }
441
442     /**
443      * Get the property editor registration strategy for this action's data
444      * binders.
445      */

446     public PropertyEditorRegistrar getPropertyEditorRegistrar() {
447         return propertyEditorRegistrar;
448     }
449
450     /**
451      * Set a property editor registration strategy for this action's data
452      * binders. This is an alternative to overriding the
453      * {@link #registerPropertyEditors(PropertyEditorRegistry)} method.
454      */

455     public void setPropertyEditorRegistrar(PropertyEditorRegistrar propertyEditorRegistrar) {
456         this.propertyEditorRegistrar = propertyEditorRegistrar;
457     }
458
459     /**
460      * Returns the validator for this action.
461      */

462     public Validator getValidator() {
463         return validator;
464     }
465
466     /**
467      * Set the validator for this action. When setting a validator, you must also
468      * set the {@link #setFormObjectClass(Class) form object class}. The validator
469      * must support the specified form object class.
470      */

471     public void setValidator(Validator validator) {
472         this.validator = validator;
473     }
474
475     /**
476      * Return the strategy to use for resolving errors into message codes.
477      */

478     public MessageCodesResolver getMessageCodesResolver() {
479         return messageCodesResolver;
480     }
481
482     /**
483      * Set the strategy to use for resolving errors into message codes. Applies
484      * the given strategy to all data binders used by this action.
485      * <p>
486      * Default is null, i.e. using the default strategy of the data binder.
487      * @see #createBinder(RequestContext, Object)
488      * @see org.springframework.validation.DataBinder#setMessageCodesResolver(org.springframework.validation.MessageCodesResolver)
489      */

490     public void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) {
491         this.messageCodesResolver = messageCodesResolver;
492     }
493
494     protected void initAction() {
495         if (getValidator() != null) {
496             Assert.notNull(getFormObjectClass(), "When using a validator, the form object class is required");
497             if (!getValidator().supports(getFormObjectClass())) {
498                 throw new IllegalArgumentException JavaDoc("Validator [" + getValidator()
499                         + "] does not support form object class [" + getFormObjectClass() + "]");
500             }
501             // signature: public void ${validateMethodName}(${formObjectClass}, Errors)
502
validateMethodInvoker = new DispatchMethodInvoker(validator, new Class JavaDoc[] { getFormObjectClass(),
503                     Errors.class });
504         }
505     }
506
507     // action methods
508

509     /**
510      * Prepares a form object for display in a new form, creating it and caching
511      * it in the {@link #getFormObjectScope()} if necessary. Also installs
512      * custom property editors for formatting form object values in UI controls
513      * such as text fields.
514      * <p>
515      * NOTE: This is action method is not designed to be overidden and might
516      * become <code>final</code> in a future version of Spring Web Flow. If
517      * you need to execute custom form setup logic have your flow call this
518      * method along with your own custom methods as part of a single action
519      * chain.
520      * @param context the action execution context, for accessing and setting
521      * data in "flow scope" or "request scope"
522      * @return "success" when binding and validation is successful
523      * @throws Exception an <b>unrecoverable</b> exception occurs, either
524      * checked or unchecked
525      * @see #createFormObject(RequestContext)
526      */

527     public Event setupForm(RequestContext context) throws Exception JavaDoc {
528         if (logger.isDebugEnabled()) {
529             logger.debug("Executing setupForm");
530         }
531         // retrieve the form object, creating it if necessary
532
Object JavaDoc formObject = getFormObject(context);
533         // ensure the form errors collection is created and exposed to the flow
534
if (!formErrorsExposed(context, formObject)) {
535             // initialize and expose a fresh errors instance to the flow with
536
// editors applied
537
initFormErrors(context, formObject);
538         }
539         else {
540             // reapply property editors against the existing errors instance
541
reinstallPropertyEditors(context);
542         }
543         return success();
544     }
545
546     /**
547      * Bind incoming request parameters to allowed fields of the form object and
548      * then validate the bound form object if a validator is configured.
549      * <p>
550      * NOTE: This action method is not designed to be overidden and might
551      * become <code>final</code> in a future version of Spring Web Flow. If
552      * you need to execute custom bind and validate logic have your flow call
553      * this method along with your own custom methods as part of a single action
554      * chain. Alternatively, override the
555      * {@link #doBind(RequestContext, DataBinder)} or
556      * {@link #doValidate(RequestContext, Object, Errors)} hooks.
557      * @param context the action execution context, for accessing and setting
558      * data in "flow scope" or "request scope"
559      * @return "success" when binding and validation is successful, "error" if
560      * there were binding and/or validation errors
561      * @throws Exception an <b>unrecoverable</b> exception occured, either
562      * checked or unchecked
563      */

564     public Event bindAndValidate(RequestContext context) throws Exception JavaDoc {
565         if (logger.isDebugEnabled()) {
566             logger.debug("Executing bind");
567         }
568         Object JavaDoc formObject = getFormObject(context);
569         DataBinder binder = createBinder(context, formObject);
570         doBind(context, binder);
571         if (getValidator() != null && validationEnabled(context)) {
572             if (logger.isDebugEnabled()) {
573                 logger.debug("Executing validation");
574             }
575             doValidate(context, formObject, binder.getErrors());
576         }
577         else {
578             if (logger.isDebugEnabled()) {
579                 if (validator == null) {
580                     logger.debug("No validator is configured, no validation will occur after binding");
581                 }
582                 else {
583                     logger.debug("Validation was disabled for this bindAndValidate request");
584                 }
585             }
586         }
587         putFormErrors(context, binder.getErrors());
588         return binder.getErrors().hasErrors() ? error() : success();
589     }
590
591     /**
592      * Bind incoming request parameters to allowed fields of the form object.
593      * <p>
594      * NOTE: This is action method is not designed to be overidden and might
595      * become <code>final</code> in a future version of Spring Web Flow. If
596      * you need to execute custom data binding logic have your flow call this
597      * method along with your own custom methods as part of a single action
598      * chain. Alternatively, override the
599      * {@link #doBind(RequestContext, DataBinder)} hook.
600      * @param context the action execution context, for accessing and setting
601      * data in "flow scope" or "request scope"
602      * @return "success" if there are no binding errors, "error" otherwise
603      * @throws Exception an <b>unrecoverable</b> exception occured, either
604      * checked or unchecked
605      */

606     public Event bind(RequestContext context) throws Exception JavaDoc {
607         if (logger.isDebugEnabled()) {
608             logger.debug("Executing bind");
609         }
610         Object JavaDoc formObject = getFormObject(context);
611         DataBinder binder = createBinder(context, formObject);
612         doBind(context, binder);
613         putFormErrors(context, binder.getErrors());
614         return binder.getErrors().hasErrors() ? error() : success();
615     }
616
617     /**
618      * Validate the form object by invoking the validator if configured.
619      * <p>
620      * NOTE: This is action method is not designed to be overidden and might
621      * become <code>final</code> in a future version of Spring Web Flow. If
622      * you need to execute custom validation logic have your flow call this
623      * method along with your own custom methods as part of a single action
624      * chain. Alternatively, override the
625      * {@link #doValidate(RequestContext, Object, Errors)} hook.
626      * @param context the action execution context, for accessing and setting
627      * data in "flow scope" or "request scope"
628      * @return "success" if there are no validation errors, "error" otherwise
629      * @throws Exception an <b>unrecoverable</b> exception occured, either
630      * checked or unchecked
631      * @see #getValidator()
632      */

633     public Event validate(RequestContext context) throws Exception JavaDoc {
634         if (getValidator() != null && validationEnabled(context)) {
635             if (logger.isDebugEnabled()) {
636                 logger.debug("Executing validation");
637             }
638             Object JavaDoc formObject = getFormObject(context);
639             Errors errors = getFormErrors(context);
640             doValidate(context, formObject, errors);
641             return errors.hasErrors() ? error() : success();
642         }
643         else {
644             if (logger.isDebugEnabled()) {
645                 if (validator == null) {
646                     logger.debug("No validator is configured, no validation will occur");
647                 }
648                 else {
649                     logger.debug("Validation was disabled for this request");
650                 }
651             }
652             return success();
653         }
654     }
655
656     /**
657      * Resets the form by clearing out the form object in the specified scope
658      * and recreating it.
659      * <p>
660      * NOTE: This is action method is not designed to be overidden and might
661      * become <code>final</code> in a future version of Spring Web Flow. If
662      * you need to execute custom reset logic have your flow call this method
663      * along with your own custom methods as part of a single action chain.
664      * @param context the request context
665      * @return "success" if the reset action completed successfully
666      * @throws Exception if an exception occured
667      * @see #createFormObject(RequestContext)
668      */

669     public Event resetForm(RequestContext context) throws Exception JavaDoc {
670         Object JavaDoc formObject = initFormObject(context);
671         initFormErrors(context, formObject);
672         return success();
673     }
674
675     // internal helpers
676

677     /**
678      * Create the new form object and put it in the configured
679      * {@link #getFormObjectScope() scope}.
680      * @param context the flow execution request context
681      * @return the new form object
682      * @throws Exception an exception occured creating the form object
683      */

684     private Object JavaDoc initFormObject(RequestContext context) throws Exception JavaDoc {
685         if (logger.isDebugEnabled()) {
686             logger.debug("Creating new form object with name '" + getFormObjectName() + "'");
687         }
688         Object JavaDoc formObject = createFormObject(context);
689         putFormObject(context, formObject);
690         return formObject;
691     }
692
693     /**
694      * Put given form object in the configured scope of given context.
695      */

696     private void putFormObject(RequestContext context, Object JavaDoc formObject) {
697         if (logger.isDebugEnabled()) {
698             logger.debug("Putting form object of type [" + formObject.getClass() + "] in scope " + getFormObjectScope()
699                     + " with name '" + getFormObjectName() + "'");
700         }
701         getFormObjectAccessor(context).putFormObject(formObject, getFormObjectName(), getFormObjectScope());
702     }
703
704     /**
705      * Initialize a new form object {@link Errors errors} instance in the
706      * configured {@link #getFormErrorsScope() scope}. This method also
707      * registers any {@link PropertiesEditor property editors} used to format
708      * form object property values.
709      * @param context the current flow execution request context
710      * @param formObject the form object for which errors will be tracked
711      */

712     private Errors initFormErrors(RequestContext context, Object JavaDoc formObject) {
713         if (logger.isDebugEnabled()) {
714             logger.debug("Creating new form errors for object with name '" + getFormObjectName() + "'");
715         }
716         Errors errors = createBinder(context, formObject).getErrors();
717         putFormErrors(context, errors);
718         return errors;
719     }
720
721     /**
722      * Put given errors instance in the configured scope of given context.
723      */

724     private void putFormErrors(RequestContext context, Errors errors) {
725         if (logger.isDebugEnabled()) {
726             logger.debug("Putting form errors instance in scope " + getFormErrorsScope());
727         }
728         getFormObjectAccessor(context).putFormErrors(errors, getFormErrorsScope());
729     }
730
731     /**
732      * Check if there is a <i>valid</i> Errors instance available in given
733      * context for given form object.
734      */

735     private boolean formErrorsExposed(RequestContext context, Object JavaDoc formObject) {
736         Errors errors = getFormObjectAccessor(context).getFormErrors(getFormObjectName(), getFormErrorsScope());
737         if (errors instanceof BindException) {
738             // make sure the existing form errors are consistent with the form
739
// object
740
BindException be = (BindException)errors;
741             if (be.getTarget() != formObject) {
742                 if (logger.isInfoEnabled()) {
743                     logger.info("Inconsistency detected: the Errors instance in '" + getFormErrorsScope()
744                             + "' does NOT wrap the current form object '" + formObject + "' of class "
745                             + formObject.getClass()
746                             + "; instead this Errors instance unexpectedly wraps the target object '" + be.getTarget()
747                             + "' of class: " + be.getTarget().getClass() + ". ");
748                 }
749                 return false; // a new Errors instance will be created
750
}
751         }
752         return errors != null;
753     }
754
755     /**
756      * Re-registers property editors against the current form errors instance.
757      * @param context the flow execution request context
758      */

759     private void reinstallPropertyEditors(RequestContext context) {
760         BindException errors = (BindException)
761             getFormObjectAccessor(context).getFormErrors(getFormObjectName(), getFormErrorsScope());
762         registerPropertyEditors(context, getPropertyEditorRegistry(errors));
763     }
764
765     /**
766      * Obtain a property editor registry from given bind exception (errors
767      * instance).
768      */

769     private PropertyEditorRegistry getPropertyEditorRegistry(BindException errors) {
770         Method JavaDoc accessor;
771         try {
772             if (hasPropertyEditorRegistryAccessor) {
773                 accessor = errors.getClass().getMethod("getPropertyEditorRegistry", null);
774             }
775             else {
776                 // only way to get at the registry in 1.2.8 or <.
777
accessor = errors.getClass().getDeclaredMethod("getBeanWrapper", null);
778                 accessor.setAccessible(true);
779             }
780         }
781         catch (NoSuchMethodException JavaDoc e) {
782             throw new IllegalStateException JavaDoc(
783                     "Unable to resolve property editor registry accessor method as expected - this should not happen");
784         }
785         return (PropertyEditorRegistry)ReflectionUtils.invokeMethod(accessor, errors);
786     }
787
788     /**
789      * Invoke specified validator method on the validator registered with this
790      * action. The validator method for piecemeal validation should have the
791      * following signature:
792      * <pre>
793      * public void ${validateMethodName}(${formObjectClass}, Errors)
794      * </pre>
795      * @param validatorMethod the name of the validator method to invoke
796      * @param formObject the form object
797      * @param errors possible binding errors
798      * @throws Exception when an unrecoverable exception occurs
799      */

800     private void invokeValidatorMethod(String JavaDoc validatorMethod, Object JavaDoc formObject, Errors errors) throws Exception JavaDoc {
801         if (logger.isDebugEnabled()) {
802             logger.debug("Invoking piecemeal validator method '" + validatorMethod + "(" + getFormObjectClass()
803                     + ", Errors)'");
804         }
805         getValidateMethodInvoker().invoke(validatorMethod, new Object JavaDoc[] { formObject, errors });
806     }
807
808     // accessible helpers (subclasses could override if necessary)
809

810     /**
811      * Convenience method that returns the form object for this form action. If
812      * not found in the configured scope, a new form object will be created by a
813      * call to {@link #createFormObject(RequestContext)} and exposed in the
814      * configured {@link #getFormObjectScope() scope}.
815      * <p>
816      * The returned form object will become the
817      * {@link FormObjectAccessor#setCurrentFormObject(Object, ScopeType) current}
818      * form object.
819      * @param context the flow execution request context
820      * @return the form object
821      * @throws Exception when an unrecoverable exception occurs
822      */

823     protected final Object JavaDoc getFormObject(RequestContext context) throws Exception JavaDoc {
824         FormObjectAccessor accessor = getFormObjectAccessor(context);
825         Object JavaDoc formObject = accessor.getFormObject(getFormObjectName(), getFormObjectScope());
826         if (formObject == null) {
827             formObject = initFormObject(context);
828         }
829         else {
830             if (logger.isDebugEnabled()) {
831                 logger.debug("Found existing form object with name '" + getFormObjectName() + "' of type ["
832                         + formObject.getClass() + "] in scope " + getFormObjectScope());
833             }
834             accessor.setCurrentFormObject(formObject, getFormObjectScope());
835         }
836         return formObject;
837     }
838
839     /**
840      * Convenience method that returns the form object errors for this form
841      * action. If not found in the configured scope, a new form object errors
842      * will be created, initialized, and exposed in the confgured
843      * {@link #getFormErrorsScope() scope}.
844      * <p>
845      * Keep in mind that an Errors instance wraps a form object, so a form
846      * object will also be created if required
847      * (see {@link #getFormObject(RequestContext)}).
848      * @param context the flow request context
849      * @return the form errors
850      * @throws Exception when an unrecoverable exception occurs
851      */

852     protected final Errors getFormErrors(RequestContext context) throws Exception JavaDoc {
853         Object JavaDoc formObject = getFormObject(context);
854         if (!formErrorsExposed(context, formObject)) {
855             initFormErrors(context, formObject);
856         }
857         return getFormObjectAccessor(context).getFormErrors(getFormObjectName(), getFormErrorsScope());
858     }
859
860     /**
861      * Create a new binder instance for the given form object and request
862      * context. Can be overridden to plug in custom DataBinder subclasses.
863      * <p>
864      * Default implementation creates a standard WebDataBinder, and invokes
865      * {@link #initBinder(RequestContext, DataBinder)} and
866      * {@link #registerPropertyEditors(PropertyEditorRegistry)}.
867      * @param context the action execution context, for accessing and setting
868      * data in "flow scope" or "request scope"
869      * @param formObject the form object to bind onto
870      * @return the new binder instance
871      * @see #initBinder(RequestContext, DataBinder)
872      * @see #setMessageCodesResolver(MessageCodesResolver)
873      */

874     protected DataBinder createBinder(RequestContext context, Object JavaDoc formObject) {
875         DataBinder binder = new WebDataBinder(formObject, getFormObjectName());
876         if (messageCodesResolver != null) {
877             binder.setMessageCodesResolver(messageCodesResolver);
878         }
879         initBinder(context, binder);
880         registerPropertyEditors(context, binder);
881         return binder;
882     }
883
884     /**
885      * Bind allowed parameters in the external context request parameter map to
886      * the form object using given binder.
887      * @param context the action execution context, for accessing and setting
888      * data in "flow scope" or "request scope"
889      * @param binder the data binder to use
890      */

891     protected void doBind(RequestContext context, DataBinder binder) {
892         if (logger.isDebugEnabled()) {
893             logger.debug("Binding allowed request parameters in "
894                     + StylerUtils.style(context.getExternalContext().getRequestParameterMap())
895                     + " to form object with name '" + binder.getObjectName() + "', pre-bind formObject toString = "
896                     + binder.getTarget());
897             if (binder.getAllowedFields() != null && binder.getAllowedFields().length > 0) {
898                 logger.debug("(Allowed fields are " + StylerUtils.style(binder.getAllowedFields()) + ")");
899             }
900             else {
901                 logger.debug("(Any field is allowed)");
902             }
903         }
904         binder.bind(new MutablePropertyValues(context.getRequestParameters().asMap()));
905         if (logger.isDebugEnabled()) {
906             logger.debug("Binding completed for form object with name '" + binder.getObjectName()
907                     + "', post-bind formObject toString = " + binder.getTarget());
908             logger.debug("There are [" + binder.getErrors().getErrorCount() + "] errors, details: "
909                     + binder.getErrors().getAllErrors());
910         }
911     }
912
913     /**
914      * Validate given form object using a registered validator. If a
915      * "validatorMethod" action property is specified for the currently
916      * executing action, the identified validator method will be invoked. When
917      * no such property is found, the defualt <code>validate()</code> method
918      * is invoked.
919      * @param context the action execution context, for accessing and setting
920      * data in "flow scope" or "request scope"
921      * @param formObject the form object
922      * @param errors the errors instance to record validation errors in
923      * @throws Exception when an unrecoverable exception occurs
924      */

925     protected void doValidate(RequestContext context, Object JavaDoc formObject, Errors errors) throws Exception JavaDoc {
926         Assert.notNull(validator, "The validator must not be null when attempting validation -- programmer error");
927         String JavaDoc validatorMethodName = context.getAttributes().getString(VALIDATOR_METHOD_ATTRIBUTE);
928         if (StringUtils.hasText(validatorMethodName)) {
929             if (logger.isDebugEnabled()) {
930                 logger.debug("Invoking validation method '" + validatorMethodName + "' on validator " + validator);
931             }
932             invokeValidatorMethod(validatorMethodName, formObject, errors);
933         }
934         else {
935             if (logger.isDebugEnabled()) {
936                 logger.debug("Invoking validator " + validator);
937             }
938             getValidator().validate(formObject, errors);
939         }
940         if (logger.isDebugEnabled()) {
941             logger.debug("Validation completed for form object");
942             logger.debug("There are [" + errors.getErrorCount() + "] errors, details: " + errors.getAllErrors());
943         }
944     }
945
946     /**
947      * Returns a dispatcher to invoke validation methods. Subclasses could
948      * override this to return a custom dispatcher.
949      */

950     protected DispatchMethodInvoker getValidateMethodInvoker() {
951         return validateMethodInvoker;
952     }
953
954     /**
955      * Factory method that returns a new form object accessor for accessing form
956      * objects in the provided request context.
957      * @param context the flow request context
958      * @return the accessor
959      */

960     protected FormObjectAccessor getFormObjectAccessor(RequestContext context) {
961         return new FormObjectAccessor(context);
962     }
963
964     // common subclassing hook methods
965

966     /**
967      * Create the backing form object instance that should be managed by this
968      * {@link FormAction form action}. By default, will attempt to instantiate
969      * a new form object instance of type {@link #getFormObjectClass()}
970      * transiently in memory.
971      * <p>
972      * Subclasses should override if they need to load the form object from a
973      * specific location or resource such as a database or filesystem.
974      * <p>
975      * Subclasses should override if they need to customize how a transient form
976      * object is assembled during creation.
977      * @param context the action execution context for accessing flow data
978      * @return the form object
979      * @throws IllegalStateException if the {@link #getFormObjectClass()}
980      * property is not set and this method has not been overridden
981      * @throws Exception when an unrecoverable exception occurs
982      */

983     protected Object JavaDoc createFormObject(RequestContext context) throws Exception JavaDoc {
984         if (formObjectClass == null) {
985             throw new IllegalStateException JavaDoc("Cannot create form object without formObjectClass property being set -- "
986                     + "either set formObjectClass or override createFormObject");
987         }
988         if (logger.isDebugEnabled()) {
989             logger.debug("Creating new instance of form object class [" + formObjectClass + "]");
990         }
991         return formObjectClass.newInstance();
992     }
993
994     /**
995      * Initialize the new binder instance. This hook allows customization of
996      * binder settings such as the {@link DataBinder#getAllowedFields() allowed fields}
997      * and {@link DataBinder#getRequiredFields() required fields}. Called by
998      * {@link #createBinder(RequestContext, Object)}
999      * <p>
1000     * Note that registration of custom property editors should be done in
1001     * {@link #registerPropertyEditors(PropertyEditorRegistry)}, not here! This
1002     * method will only be called when a new data binder is created, e.g. when
1003     * {@link #bind(RequestContext) data binding} needs to be done. In other cases,
1004     * e.g. {@link #setupForm(RequestContext) form setup}, it will <b>not</b> be
1005     * called since no data binder is required.
1006     * @param context the action execution context, for accessing and setting
1007     * data in "flow scope" or "request scope"
1008     * @param binder new binder instance
1009     * @see #createBinder(RequestContext, Object)
1010     */

1011    protected void initBinder(RequestContext context, DataBinder binder) {
1012    }
1013
1014    /**
1015     * Register custom editors to perform type conversion on fields of your form
1016     * object during data binding and form display. This method is called on
1017     * form errors initialization and
1018     * {@link #initBinder(RequestContext, DataBinder) data binder} initialization.
1019     * <p>
1020     * Property editors give you full control over how objects are transformed
1021     * to and from a formatted String form for display on a user interface such
1022     * as a HTML page.
1023     * <p>
1024     * This default implementation will call the
1025     * {@link #registerPropertyEditors(PropertyEditorRegistry) simpler form} of
1026     * the method not taking a <tt>RequestContext</tt>.
1027     * @param context the action execution context, for accessing and setting
1028     * data in "flow scope" or "request scope"
1029     * @param registry the property editor registry to register editors in
1030     * @see #registerPropertyEditors(PropertyEditorRegistry)
1031     */

1032    protected void registerPropertyEditors(RequestContext context, PropertyEditorRegistry registry) {
1033        registerPropertyEditors(registry);
1034    }
1035
1036    /**
1037     * Register custom editors to perform type conversion on fields of your form
1038     * object during data binding and form display. This method is called on
1039     * form errors initialization and
1040     * {@link #initBinder(RequestContext, DataBinder) data binder} initialization.
1041     * <p>
1042     * Property editors give you full control over how objects are transformed
1043     * to and from a formatted String form for display on a user interface such
1044     * as a HTML page.
1045     * <p>
1046     * This default implementation will simply call <tt>registerCustomEditors</tt>
1047     * on the {@link #getPropertyEditorRegistrar() propertyEditorRegistrar} object
1048     * that has been set for the action, if any.
1049     * @param registry the property editor registry to register editors in
1050     */

1051    protected void registerPropertyEditors(PropertyEditorRegistry registry) {
1052        if (propertyEditorRegistrar != null) {
1053            if (logger.isDebugEnabled()) {
1054                logger.debug("Registering custom property editors using configured registrar");
1055            }
1056            propertyEditorRegistrar.registerCustomEditors(registry);
1057        }
1058        else {
1059            if (logger.isDebugEnabled()) {
1060                logger.debug("No property editor registrar set, no custom editors to register");
1061            }
1062        }
1063    }
1064
1065    /**
1066     * Return whether validation should be performed given the state of the flow
1067     * request context. Default implementation always returns true.
1068     * @param context the request context, for accessing and setting data in
1069     * "flow scope" or "request scope"
1070     * @return whether or not validation is enabled
1071     */

1072    protected boolean validationEnabled(RequestContext context) {
1073        return true;
1074    }
1075}
Popular Tags