All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.webflow.action.FormAction Maven / Gradle / Ivy

There is a newer version: 1.0.6
Show newest version
/*
 * Copyright 2002-2006 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.webflow.action;

import java.lang.reflect.Method;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.propertyeditors.PropertiesEditor;
import org.springframework.core.style.StylerUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.DataBinder;
import org.springframework.validation.Errors;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ScopeType;
import org.springframework.webflow.util.DispatchMethodInvoker;
import org.springframework.webflow.util.ReflectionUtils;

/**
 * Multi-action that implements common logic dealing with input forms. This
 * class leverages the Spring Web data binding code to do binding and
 * validation.
 * 

* Several action execution methods are provided: *

    *
  • {@link #setupForm(RequestContext)} - Prepares the form object for * display on a form, {@link #createFormObject(RequestContext) creating it} and * an associated {@link Errors errors object} if necessary, then caching them in * the configured {@link #getFormObjectScope() form object scope} and * {@link #getFormErrorsScope() form errors scope}, respectively. Also * {@link #registerPropertyEditors(PropertyEditorRegistry) installs} any custom * property editors for formatting form object field values. This action method * will return the "success" event unless an exception is thrown.
  • *
  • {@link #bindAndValidate(RequestContext)} - Binds all incoming request * parameters to the form object and then validates the form object using a * {@link #setValidator(Validator) registered validator}. This action method * will return the "success" event if there are no binding or validation errors, * otherwise it will return the "error" event.
  • *
  • {@link #bind(RequestContext)} - Binds all incoming request parameters to * the form object. No additional validation is performed. This action method * will return the "success" event if there are no binding errors, otherwise it * will return the "error" event.
  • *
  • {@link #validate(RequestContext)} - Validates the form object using a * registered validator. No data binding is performed. This action method will * return the "success" event if there are no validation errors, otherwise it * will return the "error" event.
  • *
  • {@link #resetForm(RequestContext)} - Resets the form by reloading the * backing form object and reinstalling any custom property editors. Returns * "success" on completion, an exception is thrown when a failure occurs.
  • *
*

* Since this is a multi-action a subclass could add any number of additional * action execution methods, e.g. "setupReferenceData(RequestContext)", or * "processSubmit(RequestContext)". *

* Using this action, it becomes very easy to implement form preparation and * submission logic in your flow. One way to do this follows: *

    *
  1. Create a view state to display the form. In a render action of that * state, invoke {@link #setupForm(RequestContext) setupForm} to prepare the new * form for display.
  2. *
  3. On a matching "submit" transition execute an action that invokes * {@link #bindAndValidate(RequestContext) bindAndValidate} to bind incoming * request parameters to the form object and validate the form object.
  4. *
  5. If there are binding or validation errors, the transition will not be * allowed and the view state will automatically be re-entered. *
  6. If binding and validation is successful go to an action state called * "processSubmit" (or any other appropriate name). This will invoke an action * method called "processSubmit" you must provide on a subclass to process form * submission, e.g. interacting with the business logic.
  7. *
  8. If business processing is ok, continue to a view state to display the * success view.
  9. *
*

* Here is an example implementation of such a compact form flow: * *

 *     <view-state id="displayCriteria" view="searchCriteria">
 *         <render-actions>
 *             <action bean="formAction" method="setupForm"/>
 *         </render-actions>
 *         <transition on="search" to="executeSearch">
 *             <action bean="formAction" method="bindAndValidate"/>
 *         </transition>
 *     </view-state>
 *                                                                                
 *     <action-state id="executeSearch">
 *         <action bean="formAction" method="executeSearch"/>
 *         <transition on="success" to="displayResults"/>
 *     </action-state>
 * 
* *

* When you need additional flexibility consider splitting the view state above * acting as a single logical form state into multiple states. For * example, you could have one action state handle form setup, a view state * trigger form display, another action state handle data binding and * validation, and another process form submission. This would be a bit more * verbose but would also give you more control over how you respond to specific * results of fine-grained actions that occur within the flow. *

* Subclassing hooks: *

    *
  • A important hook is * {@link #createFormObject(RequestContext) createFormObject}. You may override * this to customize where the backing form object instance comes from (e.g * instantiated transiently in memory or loaded from a database).
  • *
  • An optional hook method provided by this class is * {@link #initBinder(RequestContext, DataBinder) initBinder}. This is called * after a new data binder is created. *
  • Another optional ook method is * {@link #registerPropertyEditors(PropertyEditorRegistry)}. By overriding it * you can register any required property editors for your form. Instead of * overriding this method, consider setting an explicit * {@link org.springframework.beans.PropertyEditorRegistrar} strategy as a more * reusable way to encapsulate custom PropertyEditor installation logic.
  • *
  • Override {@link #validationEnabled(RequestContext)} to dynamically * decide whether or not to do validation based on data available in the request * context. *
*

* Note that this action does not provide a referenceData() hook method * similar to that of Spring MVC's SimpleFormController. If you * wish to expose reference data to populate form drop downs you can define a * custom action method in your FormAction subclass that does just that. Simply * invoke it as either a chained action as part of the setupForm state, or as a * fine grained state definition itself. *

* For example, you might create this method in your subclass: * *

 * public Event setupReferenceData(RequestContext context) throws Exception {
 *     MutableAttributeMap requestScope = context.getRequestScope();
 *     requestScope.put("refData", lookupService.getSupportingFormData());
 *     return success();
 * }
 * 
* * ... and then invoke it like this: * *
 *     <view-state id="displayCriteria" view="searchCriteria">
 *         <render-actions>
 *             <action bean="searchFormAction" method="setupForm"/>
 *             <action bean="searchFormAction" method="setupReferenceData"/>
 *         </render-actions>
 *         ...
 *     </view-state>
 * 
* * This style of calling multiple action methods in a chain (Chain of * Responsibility) is preferred to overridding a single action method. In * general, action method overriding is discouraged. *

* When it comes to validating submitted input data using a registered * {@link org.springframework.validation.Validator}, this class offers the * following options: *

    *
  • If you don't want validation at all, just call * {@link #bind(RequestContext)} instead of * {@link #bindAndValidate(RequestContext)} or don't register a validator.
  • *
  • If you want piecemeal validation, e.g. in a multi-page wizard, call * {@link #bindAndValidate(RequestContext)} or {@link #validate(RequestContext)} * and specify a {@link #VALIDATOR_METHOD_ATTRIBUTE validatorMethod} action * execution attribute. This will invoke the identified custom validator method * on the validator. The validator method signature should follow the following * pattern: * *
     *     public void ${validateMethodName}(${formObjectClass}, Errors)
     * 
    * * For instance, having a action definition like this: * *
     *     <action bean="searchFormAction" method="bindAndValidate">
     *         <attribute name="validatorMethod" value="validateSearchCriteria"/>
     *     </action>
     * 
    * * Would result in the * public void validateSearchCriteria(SearchCriteria, Errors) method * of the registered validator being called if the form object class would be * SearchCriteria.
  • *
  • If you want to do full validation using the * {@link org.springframework.validation.Validator#validate(java.lang.Object, org.springframework.validation.Errors) validate} * method of the registered validator, call * {@link #bindAndValidate(RequestContext)} or {@link #validate(RequestContext)} * without specifying a "validatorMethod" action execution attribute.
  • *
* *

* FormAction configurable properties
*

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
namedefaultdescription
formObjectNameformObjectThe name of the form object. The form object will be set in the * configured scope using this name.
formObjectClassnullThe form object class for this action. An instance of this class will * get populated and validated. Required when using a validator.
formObjectScope{@link org.springframework.webflow.execution.ScopeType#FLOW flow}The scope in which the form object will be put. If put in flow scope the * object will be cached and reused over the life of the flow, preserving * previous values. Request scope will cause a new fresh form object instance to * be created on each request into the flow execution.
formErrorsScope{@link org.springframework.webflow.execution.ScopeType#FLASH flash}The scope in which the form object errors instance will be put. If put * in flash scope form errors will be cached until the next user event is signaled. *
propertyEditorRegistrarnullThe strategy used to register custom property editors with the data * binder. This is an alternative to overriding the * {@link #registerPropertyEditors(PropertyEditorRegistry)} hook method.
validatornullThe validator for this action. The validator must support the specified * form object class.
messageCodesResolvernullSet the strategy to use for resolving errors into message codes.
* * @see org.springframework.beans.PropertyEditorRegistrar * @see org.springframework.validation.DataBinder * @see ScopeType * * @author Erwin Vervaet * @author Keith Donald */ public class FormAction extends MultiAction implements InitializingBean { /* * Implementation note: Uses deprecated DataBinder.getErrors() to remain * compatible with Spring 1.2.x. */ /* * Implementation note: Introspects BindException at class init time to * preserve 1.2.x compatability. */ private static boolean hasPropertyEditorRegistryAccessor; static { hasPropertyEditorRegistryAccessor = ClassUtils .hasMethod(BindException.class, "getPropertyEditorRegistry", null); } /** * The default form object name ("formObject"). */ public static final String DEFAULT_FORM_OBJECT_NAME = "formObject"; /** * Optional attribute that identifies the method that should be invoked on * the configured validator instance, to support piecemeal wizard page * validation ("validatorMethod"). */ public static final String VALIDATOR_METHOD_ATTRIBUTE = "validatorMethod"; /** * The name the form object should be exposed under. Default is * {@link #DEFAULT_FORM_OBJECT_NAME}. */ private String formObjectName = DEFAULT_FORM_OBJECT_NAME; /** * The type of form object, typically an instantiable class. Required if * {@link #createFormObject(RequestContext)} is not overidden or when * a validator is used. */ private Class formObjectClass; /** * The scope in which the form object should be exposed. Default is * {@link ScopeType#FLOW}. */ private ScopeType formObjectScope = ScopeType.FLOW; /** * The scope in which the form object errors holder should be exposed. * Default is {@link ScopeType#FLASH}. */ private ScopeType formErrorsScope = ScopeType.FLASH; /** * A centralized service for property editor registration, for applying type * conversion during form object data binding. Can be used as an alternative * to overriding {@link #registerPropertyEditors(PropertyEditorRegistry)}. */ private PropertyEditorRegistrar propertyEditorRegistrar; /** * A validator for the form's form object. */ private Validator validator; /** * Strategy for resolving error message codes. */ private MessageCodesResolver messageCodesResolver; /** * A cache for dispatched validator methods. */ private DispatchMethodInvoker validateMethodInvoker; /** * Bean-style default constructor; creates a initially unconfigured * FormAction instance relying on default property values. Clients invoking * this constructor directly must set the {@link #formObjectClass} property * or override {@link #createFormObject(RequestContext)}. * @see #setFormObjectClass(Class) */ public FormAction() { } /** * Creates a new form action that manages instance(s) of the specified form * object class. * @param formObjectClass the class of the form object (must be instantiable) */ public FormAction(Class formObjectClass) { setFormObjectClass(formObjectClass); } /** * Return the name of the form object in the configured scope. */ public String getFormObjectName() { return formObjectName; } /** * Set the name of the form object in the configured scope. The form object * will be included in the configured scope under this name. */ public void setFormObjectName(String formObjectName) { this.formObjectName = formObjectName; } /** * Return the form object class for this action. */ public Class getFormObjectClass() { return formObjectClass; } /** * Set the form object class for this action. An instance of this class will * get populated and validated. This is a required property if you register * a validator with the form action ({@link #setValidator(Validator)})! *

* If no form object name is set at the moment this method is called, a * form object name will be automatically generated based on the provided * form object class using * {@link ClassUtils#getShortNameAsProperty(java.lang.Class)}. */ public void setFormObjectClass(Class formObjectClass) { this.formObjectClass = formObjectClass; // generate a default form object name if ((formObjectName == null || formObjectName == DEFAULT_FORM_OBJECT_NAME) && formObjectClass != null) { formObjectName = ClassUtils.getShortNameAsProperty(formObjectClass); } } /** * Get the scope in which the form object will be placed. */ public ScopeType getFormObjectScope() { return formObjectScope; } /** * Set the scope in which the form object will be placed. The default * if not set is {@link ScopeType#FLOW flow scope}. */ public void setFormObjectScope(ScopeType scopeType) { this.formObjectScope = scopeType; } /** * Get the scope in which the Errors object will be placed. */ public ScopeType getFormErrorsScope() { return formErrorsScope; } /** * Set the scope in which the Errors object will be placed. The default * if not set is {@link ScopeType#FLASH flash scope}. */ public void setFormErrorsScope(ScopeType errorsScope) { this.formErrorsScope = errorsScope; } /** * Get the property editor registration strategy for this action's data * binders. */ public PropertyEditorRegistrar getPropertyEditorRegistrar() { return propertyEditorRegistrar; } /** * Set a property editor registration strategy for this action's data * binders. This is an alternative to overriding the * {@link #registerPropertyEditors(PropertyEditorRegistry)} method. */ public void setPropertyEditorRegistrar(PropertyEditorRegistrar propertyEditorRegistrar) { this.propertyEditorRegistrar = propertyEditorRegistrar; } /** * Returns the validator for this action. */ public Validator getValidator() { return validator; } /** * Set the validator for this action. When setting a validator, you must also * set the {@link #setFormObjectClass(Class) form object class}. The validator * must support the specified form object class. */ public void setValidator(Validator validator) { this.validator = validator; } /** * Return the strategy to use for resolving errors into message codes. */ public MessageCodesResolver getMessageCodesResolver() { return messageCodesResolver; } /** * Set the strategy to use for resolving errors into message codes. Applies * the given strategy to all data binders used by this action. *

* Default is null, i.e. using the default strategy of the data binder. * @see #createBinder(RequestContext, Object) * @see org.springframework.validation.DataBinder#setMessageCodesResolver(org.springframework.validation.MessageCodesResolver) */ public void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) { this.messageCodesResolver = messageCodesResolver; } protected void initAction() { if (getValidator() != null) { Assert.notNull(getFormObjectClass(), "When using a validator, the form object class is required"); if (!getValidator().supports(getFormObjectClass())) { throw new IllegalArgumentException("Validator [" + getValidator() + "] does not support form object class [" + getFormObjectClass() + "]"); } // signature: public void ${validateMethodName}(${formObjectClass}, Errors) validateMethodInvoker = new DispatchMethodInvoker(validator, new Class[] { getFormObjectClass(), Errors.class }); } } // action methods /** * Prepares a form object for display in a new form, creating it and caching * it in the {@link #getFormObjectScope()} if necessary. Also installs * custom property editors for formatting form object values in UI controls * such as text fields. *

* NOTE: This is action method is not designed to be overidden and might * become final in a future version of Spring Web Flow. If * you need to execute custom form setup logic have your flow call this * method along with your own custom methods as part of a single action * chain. * @param context the action execution context, for accessing and setting * data in "flow scope" or "request scope" * @return "success" when binding and validation is successful * @throws Exception an unrecoverable exception occurs, either * checked or unchecked * @see #createFormObject(RequestContext) */ public Event setupForm(RequestContext context) throws Exception { if (logger.isDebugEnabled()) { logger.debug("Executing setupForm"); } // retrieve the form object, creating it if necessary Object formObject = getFormObject(context); // ensure the form errors collection is created and exposed to the flow if (!formErrorsExposed(context, formObject)) { // initialize and expose a fresh errors instance to the flow with // editors applied initFormErrors(context, formObject); } else { // reapply property editors against the existing errors instance reinstallPropertyEditors(context); } return success(); } /** * Bind incoming request parameters to allowed fields of the form object and * then validate the bound form object if a validator is configured. *

* NOTE: This action method is not designed to be overidden and might * become final in a future version of Spring Web Flow. If * you need to execute custom bind and validate logic have your flow call * this method along with your own custom methods as part of a single action * chain. Alternatively, override the * {@link #doBind(RequestContext, DataBinder)} or * {@link #doValidate(RequestContext, Object, Errors)} hooks. * @param context the action execution context, for accessing and setting * data in "flow scope" or "request scope" * @return "success" when binding and validation is successful, "error" if * there were binding and/or validation errors * @throws Exception an unrecoverable exception occured, either * checked or unchecked */ public Event bindAndValidate(RequestContext context) throws Exception { if (logger.isDebugEnabled()) { logger.debug("Executing bind"); } Object formObject = getFormObject(context); DataBinder binder = createBinder(context, formObject); doBind(context, binder); if (getValidator() != null && validationEnabled(context)) { if (logger.isDebugEnabled()) { logger.debug("Executing validation"); } doValidate(context, formObject, binder.getErrors()); } else { if (logger.isDebugEnabled()) { if (validator == null) { logger.debug("No validator is configured, no validation will occur after binding"); } else { logger.debug("Validation was disabled for this bindAndValidate request"); } } } putFormErrors(context, binder.getErrors()); return binder.getErrors().hasErrors() ? error() : success(); } /** * Bind incoming request parameters to allowed fields of the form object. *

* NOTE: This is action method is not designed to be overidden and might * become final in a future version of Spring Web Flow. If * you need to execute custom data binding logic have your flow call this * method along with your own custom methods as part of a single action * chain. Alternatively, override the * {@link #doBind(RequestContext, DataBinder)} hook. * @param context the action execution context, for accessing and setting * data in "flow scope" or "request scope" * @return "success" if there are no binding errors, "error" otherwise * @throws Exception an unrecoverable exception occured, either * checked or unchecked */ public Event bind(RequestContext context) throws Exception { if (logger.isDebugEnabled()) { logger.debug("Executing bind"); } Object formObject = getFormObject(context); DataBinder binder = createBinder(context, formObject); doBind(context, binder); putFormErrors(context, binder.getErrors()); return binder.getErrors().hasErrors() ? error() : success(); } /** * Validate the form object by invoking the validator if configured. *

* NOTE: This is action method is not designed to be overidden and might * become final in a future version of Spring Web Flow. If * you need to execute custom validation logic have your flow call this * method along with your own custom methods as part of a single action * chain. Alternatively, override the * {@link #doValidate(RequestContext, Object, Errors)} hook. * @param context the action execution context, for accessing and setting * data in "flow scope" or "request scope" * @return "success" if there are no validation errors, "error" otherwise * @throws Exception an unrecoverable exception occured, either * checked or unchecked * @see #getValidator() */ public Event validate(RequestContext context) throws Exception { if (getValidator() != null && validationEnabled(context)) { if (logger.isDebugEnabled()) { logger.debug("Executing validation"); } Object formObject = getFormObject(context); Errors errors = getFormErrors(context); doValidate(context, formObject, errors); return errors.hasErrors() ? error() : success(); } else { if (logger.isDebugEnabled()) { if (validator == null) { logger.debug("No validator is configured, no validation will occur"); } else { logger.debug("Validation was disabled for this request"); } } return success(); } } /** * Resets the form by clearing out the form object in the specified scope * and recreating it. *

* NOTE: This is action method is not designed to be overidden and might * become final in a future version of Spring Web Flow. If * you need to execute custom reset logic have your flow call this method * along with your own custom methods as part of a single action chain. * @param context the request context * @return "success" if the reset action completed successfully * @throws Exception if an exception occured * @see #createFormObject(RequestContext) */ public Event resetForm(RequestContext context) throws Exception { Object formObject = initFormObject(context); initFormErrors(context, formObject); return success(); } // internal helpers /** * Create the new form object and put it in the configured * {@link #getFormObjectScope() scope}. * @param context the flow execution request context * @return the new form object * @throws Exception an exception occured creating the form object */ private Object initFormObject(RequestContext context) throws Exception { if (logger.isDebugEnabled()) { logger.debug("Creating new form object with name '" + getFormObjectName() + "'"); } Object formObject = createFormObject(context); putFormObject(context, formObject); return formObject; } /** * Put given form object in the configured scope of given context. */ private void putFormObject(RequestContext context, Object formObject) { if (logger.isDebugEnabled()) { logger.debug("Putting form object of type [" + formObject.getClass() + "] in scope " + getFormObjectScope() + " with name '" + getFormObjectName() + "'"); } getFormObjectAccessor(context).putFormObject(formObject, getFormObjectName(), getFormObjectScope()); } /** * Initialize a new form object {@link Errors errors} instance in the * configured {@link #getFormErrorsScope() scope}. This method also * registers any {@link PropertiesEditor property editors} used to format * form object property values. * @param context the current flow execution request context * @param formObject the form object for which errors will be tracked */ private Errors initFormErrors(RequestContext context, Object formObject) { if (logger.isDebugEnabled()) { logger.debug("Creating new form errors for object with name '" + getFormObjectName() + "'"); } Errors errors = createBinder(context, formObject).getErrors(); putFormErrors(context, errors); return errors; } /** * Put given errors instance in the configured scope of given context. */ private void putFormErrors(RequestContext context, Errors errors) { if (logger.isDebugEnabled()) { logger.debug("Putting form errors instance in scope " + getFormErrorsScope()); } getFormObjectAccessor(context).putFormErrors(errors, getFormErrorsScope()); } /** * Check if there is a valid Errors instance available in given * context for given form object. */ private boolean formErrorsExposed(RequestContext context, Object formObject) { Errors errors = getFormObjectAccessor(context).getFormErrors(getFormObjectName(), getFormErrorsScope()); if (errors instanceof BindException) { // make sure the existing form errors are consistent with the form // object BindException be = (BindException)errors; if (be.getTarget() != formObject) { if (logger.isInfoEnabled()) { logger.info("Inconsistency detected: the Errors instance in '" + getFormErrorsScope() + "' does NOT wrap the current form object '" + formObject + "' of class " + formObject.getClass() + "; instead this Errors instance unexpectedly wraps the target object '" + be.getTarget() + "' of class: " + be.getTarget().getClass() + ". "); } return false; // a new Errors instance will be created } } return errors != null; } /** * Re-registers property editors against the current form errors instance. * @param context the flow execution request context */ private void reinstallPropertyEditors(RequestContext context) { BindException errors = (BindException) getFormObjectAccessor(context).getFormErrors(getFormObjectName(), getFormErrorsScope()); registerPropertyEditors(context, getPropertyEditorRegistry(errors)); } /** * Obtain a property editor registry from given bind exception (errors * instance). */ private PropertyEditorRegistry getPropertyEditorRegistry(BindException errors) { Method accessor; try { if (hasPropertyEditorRegistryAccessor) { accessor = errors.getClass().getMethod("getPropertyEditorRegistry", null); } else { // only way to get at the registry in 1.2.8 or <. accessor = errors.getClass().getDeclaredMethod("getBeanWrapper", null); accessor.setAccessible(true); } } catch (NoSuchMethodException e) { throw new IllegalStateException( "Unable to resolve property editor registry accessor method as expected - this should not happen"); } return (PropertyEditorRegistry)ReflectionUtils.invokeMethod(accessor, errors); } /** * Invoke specified validator method on the validator registered with this * action. The validator method for piecemeal validation should have the * following signature: *

	 *     public void ${validateMethodName}(${formObjectClass}, Errors)
	 * 
* @param validatorMethod the name of the validator method to invoke * @param formObject the form object * @param errors possible binding errors * @throws Exception when an unrecoverable exception occurs */ private void invokeValidatorMethod(String validatorMethod, Object formObject, Errors errors) throws Exception { if (logger.isDebugEnabled()) { logger.debug("Invoking piecemeal validator method '" + validatorMethod + "(" + getFormObjectClass() + ", Errors)'"); } getValidateMethodInvoker().invoke(validatorMethod, new Object[] { formObject, errors }); } // accessible helpers (subclasses could override if necessary) /** * Convenience method that returns the form object for this form action. If * not found in the configured scope, a new form object will be created by a * call to {@link #createFormObject(RequestContext)} and exposed in the * configured {@link #getFormObjectScope() scope}. *

* The returned form object will become the * {@link FormObjectAccessor#setCurrentFormObject(Object, ScopeType) current} * form object. * @param context the flow execution request context * @return the form object * @throws Exception when an unrecoverable exception occurs */ protected final Object getFormObject(RequestContext context) throws Exception { FormObjectAccessor accessor = getFormObjectAccessor(context); Object formObject = accessor.getFormObject(getFormObjectName(), getFormObjectScope()); if (formObject == null) { formObject = initFormObject(context); } else { if (logger.isDebugEnabled()) { logger.debug("Found existing form object with name '" + getFormObjectName() + "' of type [" + formObject.getClass() + "] in scope " + getFormObjectScope()); } accessor.setCurrentFormObject(formObject, getFormObjectScope()); } return formObject; } /** * Convenience method that returns the form object errors for this form * action. If not found in the configured scope, a new form object errors * will be created, initialized, and exposed in the confgured * {@link #getFormErrorsScope() scope}. *

* Keep in mind that an Errors instance wraps a form object, so a form * object will also be created if required * (see {@link #getFormObject(RequestContext)}). * @param context the flow request context * @return the form errors * @throws Exception when an unrecoverable exception occurs */ protected final Errors getFormErrors(RequestContext context) throws Exception { Object formObject = getFormObject(context); if (!formErrorsExposed(context, formObject)) { initFormErrors(context, formObject); } return getFormObjectAccessor(context).getFormErrors(getFormObjectName(), getFormErrorsScope()); } /** * Create a new binder instance for the given form object and request * context. Can be overridden to plug in custom DataBinder subclasses. *

* Default implementation creates a standard WebDataBinder, and invokes * {@link #initBinder(RequestContext, DataBinder)} and * {@link #registerPropertyEditors(PropertyEditorRegistry)}. * @param context the action execution context, for accessing and setting * data in "flow scope" or "request scope" * @param formObject the form object to bind onto * @return the new binder instance * @see #initBinder(RequestContext, DataBinder) * @see #setMessageCodesResolver(MessageCodesResolver) */ protected DataBinder createBinder(RequestContext context, Object formObject) { DataBinder binder = new WebDataBinder(formObject, getFormObjectName()); if (messageCodesResolver != null) { binder.setMessageCodesResolver(messageCodesResolver); } initBinder(context, binder); registerPropertyEditors(context, binder); return binder; } /** * Bind allowed parameters in the external context request parameter map to * the form object using given binder. * @param context the action execution context, for accessing and setting * data in "flow scope" or "request scope" * @param binder the data binder to use */ protected void doBind(RequestContext context, DataBinder binder) { if (logger.isDebugEnabled()) { logger.debug("Binding allowed request parameters in " + StylerUtils.style(context.getExternalContext().getRequestParameterMap()) + " to form object with name '" + binder.getObjectName() + "', pre-bind formObject toString = " + binder.getTarget()); if (binder.getAllowedFields() != null && binder.getAllowedFields().length > 0) { logger.debug("(Allowed fields are " + StylerUtils.style(binder.getAllowedFields()) + ")"); } else { logger.debug("(Any field is allowed)"); } } binder.bind(new MutablePropertyValues(context.getRequestParameters().asMap())); if (logger.isDebugEnabled()) { logger.debug("Binding completed for form object with name '" + binder.getObjectName() + "', post-bind formObject toString = " + binder.getTarget()); logger.debug("There are [" + binder.getErrors().getErrorCount() + "] errors, details: " + binder.getErrors().getAllErrors()); } } /** * Validate given form object using a registered validator. If a * "validatorMethod" action property is specified for the currently * executing action, the identified validator method will be invoked. When * no such property is found, the defualt validate() method * is invoked. * @param context the action execution context, for accessing and setting * data in "flow scope" or "request scope" * @param formObject the form object * @param errors the errors instance to record validation errors in * @throws Exception when an unrecoverable exception occurs */ protected void doValidate(RequestContext context, Object formObject, Errors errors) throws Exception { Assert.notNull(validator, "The validator must not be null when attempting validation -- programmer error"); String validatorMethodName = context.getAttributes().getString(VALIDATOR_METHOD_ATTRIBUTE); if (StringUtils.hasText(validatorMethodName)) { if (logger.isDebugEnabled()) { logger.debug("Invoking validation method '" + validatorMethodName + "' on validator " + validator); } invokeValidatorMethod(validatorMethodName, formObject, errors); } else { if (logger.isDebugEnabled()) { logger.debug("Invoking validator " + validator); } getValidator().validate(formObject, errors); } if (logger.isDebugEnabled()) { logger.debug("Validation completed for form object"); logger.debug("There are [" + errors.getErrorCount() + "] errors, details: " + errors.getAllErrors()); } } /** * Returns a dispatcher to invoke validation methods. Subclasses could * override this to return a custom dispatcher. */ protected DispatchMethodInvoker getValidateMethodInvoker() { return validateMethodInvoker; } /** * Factory method that returns a new form object accessor for accessing form * objects in the provided request context. * @param context the flow request context * @return the accessor */ protected FormObjectAccessor getFormObjectAccessor(RequestContext context) { return new FormObjectAccessor(context); } // common subclassing hook methods /** * Create the backing form object instance that should be managed by this * {@link FormAction form action}. By default, will attempt to instantiate * a new form object instance of type {@link #getFormObjectClass()} * transiently in memory. *

* Subclasses should override if they need to load the form object from a * specific location or resource such as a database or filesystem. *

* Subclasses should override if they need to customize how a transient form * object is assembled during creation. * @param context the action execution context for accessing flow data * @return the form object * @throws IllegalStateException if the {@link #getFormObjectClass()} * property is not set and this method has not been overridden * @throws Exception when an unrecoverable exception occurs */ protected Object createFormObject(RequestContext context) throws Exception { if (formObjectClass == null) { throw new IllegalStateException("Cannot create form object without formObjectClass property being set -- " + "either set formObjectClass or override createFormObject"); } if (logger.isDebugEnabled()) { logger.debug("Creating new instance of form object class [" + formObjectClass + "]"); } return formObjectClass.newInstance(); } /** * Initialize the new binder instance. This hook allows customization of * binder settings such as the {@link DataBinder#getAllowedFields() allowed fields} * and {@link DataBinder#getRequiredFields() required fields}. Called by * {@link #createBinder(RequestContext, Object)} *

* Note that registration of custom property editors should be done in * {@link #registerPropertyEditors(PropertyEditorRegistry)}, not here! This * method will only be called when a new data binder is created, e.g. when * {@link #bind(RequestContext) data binding} needs to be done. In other cases, * e.g. {@link #setupForm(RequestContext) form setup}, it will not be * called since no data binder is required. * @param context the action execution context, for accessing and setting * data in "flow scope" or "request scope" * @param binder new binder instance * @see #createBinder(RequestContext, Object) */ protected void initBinder(RequestContext context, DataBinder binder) { } /** * Register custom editors to perform type conversion on fields of your form * object during data binding and form display. This method is called on * form errors initialization and * {@link #initBinder(RequestContext, DataBinder) data binder} initialization. *

* Property editors give you full control over how objects are transformed * to and from a formatted String form for display on a user interface such * as a HTML page. *

* This default implementation will call the * {@link #registerPropertyEditors(PropertyEditorRegistry) simpler form} of * the method not taking a RequestContext. * @param context the action execution context, for accessing and setting * data in "flow scope" or "request scope" * @param registry the property editor registry to register editors in * @see #registerPropertyEditors(PropertyEditorRegistry) */ protected void registerPropertyEditors(RequestContext context, PropertyEditorRegistry registry) { registerPropertyEditors(registry); } /** * Register custom editors to perform type conversion on fields of your form * object during data binding and form display. This method is called on * form errors initialization and * {@link #initBinder(RequestContext, DataBinder) data binder} initialization. *

* Property editors give you full control over how objects are transformed * to and from a formatted String form for display on a user interface such * as a HTML page. *

* This default implementation will simply call registerCustomEditors * on the {@link #getPropertyEditorRegistrar() propertyEditorRegistrar} object * that has been set for the action, if any. * @param registry the property editor registry to register editors in */ protected void registerPropertyEditors(PropertyEditorRegistry registry) { if (propertyEditorRegistrar != null) { if (logger.isDebugEnabled()) { logger.debug("Registering custom property editors using configured registrar"); } propertyEditorRegistrar.registerCustomEditors(registry); } else { if (logger.isDebugEnabled()) { logger.debug("No property editor registrar set, no custom editors to register"); } } } /** * Return whether validation should be performed given the state of the flow * request context. Default implementation always returns true. * @param context the request context, for accessing and setting data in * "flow scope" or "request scope" * @return whether or not validation is enabled */ protected boolean validationEnabled(RequestContext context) { return true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy