0001: /*
0002: * Copyright 2004-2007 the original author or authors.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.springframework.webflow.action;
0017:
0018: import java.lang.reflect.Method;
0019:
0020: import org.springframework.beans.MutablePropertyValues;
0021: import org.springframework.beans.PropertyEditorRegistrar;
0022: import org.springframework.beans.PropertyEditorRegistry;
0023: import org.springframework.beans.factory.InitializingBean;
0024: import org.springframework.beans.propertyeditors.PropertiesEditor;
0025: import org.springframework.core.style.StylerUtils;
0026: import org.springframework.util.Assert;
0027: import org.springframework.util.ClassUtils;
0028: import org.springframework.util.StringUtils;
0029: import org.springframework.validation.BindException;
0030: import org.springframework.validation.DataBinder;
0031: import org.springframework.validation.Errors;
0032: import org.springframework.validation.MessageCodesResolver;
0033: import org.springframework.validation.Validator;
0034: import org.springframework.web.bind.WebDataBinder;
0035: import org.springframework.webflow.execution.Event;
0036: import org.springframework.webflow.execution.RequestContext;
0037: import org.springframework.webflow.execution.ScopeType;
0038: import org.springframework.webflow.util.DispatchMethodInvoker;
0039: import org.springframework.webflow.util.ReflectionUtils;
0040:
0041: /**
0042: * Multi-action that implements common logic dealing with input forms. This
0043: * class leverages the Spring Web data binding code to do binding and
0044: * validation.
0045: * <p>
0046: * Several action execution methods are provided:
0047: * <ul>
0048: * <li> {@link #setupForm(RequestContext)} - Prepares the form object for
0049: * display on a form, {@link #createFormObject(RequestContext) creating it} and
0050: * an associated {@link Errors errors object} if necessary, then caching them in
0051: * the configured {@link #getFormObjectScope() form object scope} and
0052: * {@link #getFormErrorsScope() form errors scope}, respectively. Also
0053: * {@link #registerPropertyEditors(PropertyEditorRegistry) installs} any custom
0054: * property editors for formatting form object field values. This action method
0055: * will return the "success" event unless an exception is thrown. </li>
0056: * <li> {@link #bindAndValidate(RequestContext)} - Binds all incoming request
0057: * parameters to the form object and then validates the form object using a
0058: * {@link #setValidator(Validator) registered validator}. This action method
0059: * will return the "success" event if there are no binding or validation errors,
0060: * otherwise it will return the "error" event. </li>
0061: * <li> {@link #bind(RequestContext)} - Binds all incoming request parameters to
0062: * the form object. No additional validation is performed. This action method
0063: * will return the "success" event if there are no binding errors, otherwise it
0064: * will return the "error" event. </li>
0065: * <li> {@link #validate(RequestContext)} - Validates the form object using a
0066: * registered validator. No data binding is performed. This action method will
0067: * return the "success" event if there are no validation errors, otherwise it
0068: * will return the "error" event. </li>
0069: * <li> {@link #resetForm(RequestContext)} - Resets the form by reloading the
0070: * backing form object and reinstalling any custom property editors. Returns
0071: * "success" on completion, an exception is thrown when a failure occurs. </li>
0072: * </ul>
0073: * <p>
0074: * Since this is a multi-action a subclass could add any number of additional
0075: * action execution methods, e.g. "setupReferenceData(RequestContext)", or
0076: * "processSubmit(RequestContext)".
0077: * <p>
0078: * Using this action, it becomes very easy to implement form preparation and
0079: * submission logic in your flow. One way to do this follows:
0080: * <ol>
0081: * <li> Create a view state to display the form. In a render action of that
0082: * state, invoke {@link #setupForm(RequestContext) setupForm} to prepare the new
0083: * form for display. </li>
0084: * <li> On a matching "submit" transition execute an action that invokes
0085: * {@link #bindAndValidate(RequestContext) bindAndValidate} to bind incoming
0086: * request parameters to the form object and validate the form object. </li>
0087: * <li> If there are binding or validation errors, the transition will not be
0088: * allowed and the view state will automatically be re-entered.
0089: * <li> If binding and validation is successful go to an action state called
0090: * "processSubmit" (or any other appropriate name). This will invoke an action
0091: * method called "processSubmit" you must provide on a subclass to process form
0092: * submission, e.g. interacting with the business logic. </li>
0093: * <li> If business processing is ok, continue to a view state to display the
0094: * success view. </li>
0095: * </ol>
0096: * <p>
0097: * Here is an example implementation of such a compact form flow:
0098: *
0099: * <pre>
0100: * <view-state id="displayCriteria" view="searchCriteria">
0101: * <render-actions>
0102: * <action bean="formAction" method="setupForm"/>
0103: * </render-actions>
0104: * <transition on="search" to="executeSearch">
0105: * <action bean="formAction" method="bindAndValidate"/>
0106: * </transition>
0107: * </view-state>
0108: *
0109: * <action-state id="executeSearch">
0110: * <action bean="formAction" method="executeSearch"/>
0111: * <transition on="success" to="displayResults"/>
0112: * </action-state>
0113: * </pre>
0114: *
0115: * <p>
0116: * When you need additional flexibility consider splitting the view state above
0117: * acting as a single logical <i>form state</i> into multiple states. For
0118: * example, you could have one action state handle form setup, a view state
0119: * trigger form display, another action state handle data binding and
0120: * validation, and another process form submission. This would be a bit more
0121: * verbose but would also give you more control over how you respond to specific
0122: * results of fine-grained actions that occur within the flow.
0123: * <p>
0124: * <b>Subclassing hooks:</b>
0125: * <ul>
0126: * <li>A important hook is
0127: * {@link #createFormObject(RequestContext) createFormObject}. You may override
0128: * this to customize where the backing form object instance comes from (e.g
0129: * instantiated transiently in memory or loaded from a database).</li>
0130: * <li>An optional hook method provided by this class is
0131: * {@link #initBinder(RequestContext, DataBinder) initBinder}. This is called
0132: * after a new data binder is created.
0133: * <li>Another optional ook method is
0134: * {@link #registerPropertyEditors(PropertyEditorRegistry)}. By overriding it
0135: * you can register any required property editors for your form. Instead of
0136: * overriding this method, consider setting an explicit
0137: * {@link org.springframework.beans.PropertyEditorRegistrar} strategy as a more
0138: * reusable way to encapsulate custom PropertyEditor installation logic.</li>
0139: * <li>Override {@link #validationEnabled(RequestContext)} to dynamically
0140: * decide whether or not to do validation based on data available in the request
0141: * context.
0142: * </ul>
0143: * <p>
0144: * Note that this action does not provide a <i>referenceData()</i> hook method
0145: * similar to that of Spring MVC's <code>SimpleFormController</code>. If you
0146: * wish to expose reference data to populate form drop downs you can define a
0147: * custom action method in your FormAction subclass that does just that. Simply
0148: * invoke it as either a chained action as part of the setupForm state, or as a
0149: * fine grained state definition itself.
0150: * <p>
0151: * For example, you might create this method in your subclass:
0152: *
0153: * <pre>
0154: * public Event setupReferenceData(RequestContext context) throws Exception {
0155: * MutableAttributeMap requestScope = context.getRequestScope();
0156: * requestScope.put("refData", lookupService.getSupportingFormData());
0157: * return success();
0158: * }
0159: * </pre>
0160: *
0161: * ... and then invoke it like this:
0162: *
0163: * <pre>
0164: * <view-state id="displayCriteria" view="searchCriteria">
0165: * <render-actions>
0166: * <action bean="searchFormAction" method="setupForm"/>
0167: * <action bean="searchFormAction" method="setupReferenceData"/>
0168: * </render-actions>
0169: * ...
0170: * </view-state>
0171: * </pre>
0172: *
0173: * This style of calling multiple action methods in a chain (Chain of
0174: * Responsibility) is preferred to overridding a single action method. In
0175: * general, action method overriding is discouraged.
0176: * <p>
0177: * When it comes to validating submitted input data using a registered
0178: * {@link org.springframework.validation.Validator}, this class offers the
0179: * following options:
0180: * <ul>
0181: * <li>If you don't want validation at all, just call
0182: * {@link #bind(RequestContext)} instead of
0183: * {@link #bindAndValidate(RequestContext)} or don't register a validator.</li>
0184: * <li>If you want piecemeal validation, e.g. in a multi-page wizard, call
0185: * {@link #bindAndValidate(RequestContext)} or {@link #validate(RequestContext)}
0186: * and specify a {@link #VALIDATOR_METHOD_ATTRIBUTE validatorMethod} action
0187: * execution attribute. This will invoke the identified custom validator method
0188: * on the validator. The validator method signature should follow the following
0189: * pattern:
0190: *
0191: * <pre>
0192: * public void ${validateMethodName}(${formObjectClass}, Errors)
0193: * </pre>
0194: *
0195: * For instance, having a action definition like this:
0196: *
0197: * <pre>
0198: * <action bean="searchFormAction" method="bindAndValidate">
0199: * <attribute name="validatorMethod" value="validateSearchCriteria"/>
0200: * </action>
0201: * </pre>
0202: *
0203: * Would result in the
0204: * <tt>public void validateSearchCriteria(SearchCriteria, Errors)</tt> method
0205: * of the registered validator being called if the form object class would be
0206: * <code>SearchCriteria</code>.</li>
0207: * <li>If you want to do full validation using the
0208: * {@link org.springframework.validation.Validator#validate(java.lang.Object, org.springframework.validation.Errors) validate}
0209: * method of the registered validator, call
0210: * {@link #bindAndValidate(RequestContext)} or {@link #validate(RequestContext)}
0211: * without specifying a "validatorMethod" action execution attribute.</li>
0212: * </ul>
0213: *
0214: * <p>
0215: * <b>FormAction configurable properties</b><br>
0216: * <table border="1">
0217: * <tr>
0218: * <td><b>name</b></td>
0219: * <td><b>default</b></td>
0220: * <td><b>description</b></td>
0221: * </tr>
0222: * <tr>
0223: * <td>formObjectName</td>
0224: * <td>formObject</td>
0225: * <td>The name of the form object. The form object will be set in the
0226: * configured scope using this name.</td>
0227: * </tr>
0228: * <tr>
0229: * <td>formObjectClass</td>
0230: * <td>null</td>
0231: * <td>The form object class for this action. An instance of this class will
0232: * get populated and validated. Required when using a validator.</td>
0233: * </tr>
0234: * <tr>
0235: * <td>formObjectScope</td>
0236: * <td>{@link org.springframework.webflow.execution.ScopeType#FLOW flow}</td>
0237: * <td>The scope in which the form object will be put. If put in flow scope the
0238: * object will be cached and reused over the life of the flow, preserving
0239: * previous values. Request scope will cause a new fresh form object instance to
0240: * be created on each request into the flow execution.</td>
0241: * </tr>
0242: * <tr>
0243: * <td>formErrorsScope</td>
0244: * <td>{@link org.springframework.webflow.execution.ScopeType#FLASH flash}</td>
0245: * <td>The scope in which the form object errors instance will be put. If put
0246: * in flash scope form errors will be cached until the next user event is signaled.
0247: * </td>
0248: * </tr>
0249: * <tr>
0250: * <td>propertyEditorRegistrar</td>
0251: * <td>null</td>
0252: * <td>The strategy used to register custom property editors with the data
0253: * binder. This is an alternative to overriding the
0254: * {@link #registerPropertyEditors(PropertyEditorRegistry)} hook method. </td>
0255: * </tr>
0256: * <tr>
0257: * <td>validator</td>
0258: * <td>null</td>
0259: * <td>The validator for this action. The validator must support the specified
0260: * form object class. </td>
0261: * </tr>
0262: * <tr>
0263: * <td>messageCodesResolver</td>
0264: * <td>null</td>
0265: * <td>Set the strategy to use for resolving errors into message codes. </td>
0266: * </tr>
0267: * </table>
0268: *
0269: * @see org.springframework.beans.PropertyEditorRegistrar
0270: * @see org.springframework.validation.DataBinder
0271: * @see ScopeType
0272: *
0273: * @author Erwin Vervaet
0274: * @author Keith Donald
0275: */
0276: public class FormAction extends MultiAction implements InitializingBean {
0277:
0278: /*
0279: * Implementation note: Uses deprecated DataBinder.getErrors() to remain
0280: * compatible with Spring 1.2.x.
0281: */
0282:
0283: /*
0284: * Implementation note: Introspects BindException at class init time to
0285: * preserve 1.2.x compatability.
0286: */
0287: private static boolean hasPropertyEditorRegistryAccessor;
0288:
0289: static {
0290: hasPropertyEditorRegistryAccessor = ClassUtils.hasMethod(
0291: BindException.class, "getPropertyEditorRegistry", null);
0292: }
0293:
0294: /**
0295: * The default form object name ("formObject").
0296: */
0297: public static final String DEFAULT_FORM_OBJECT_NAME = "formObject";
0298:
0299: /**
0300: * Optional attribute that identifies the method that should be invoked on
0301: * the configured validator instance, to support piecemeal wizard page
0302: * validation ("validatorMethod").
0303: */
0304: public static final String VALIDATOR_METHOD_ATTRIBUTE = "validatorMethod";
0305:
0306: /**
0307: * The name the form object should be exposed under. Default is
0308: * {@link #DEFAULT_FORM_OBJECT_NAME}.
0309: */
0310: private String formObjectName = DEFAULT_FORM_OBJECT_NAME;
0311:
0312: /**
0313: * The type of form object, typically an instantiable class. Required if
0314: * {@link #createFormObject(RequestContext)} is not overidden or when
0315: * a validator is used.
0316: */
0317: private Class formObjectClass;
0318:
0319: /**
0320: * The scope in which the form object should be exposed. Default is
0321: * {@link ScopeType#FLOW}.
0322: */
0323: private ScopeType formObjectScope = ScopeType.FLOW;
0324:
0325: /**
0326: * The scope in which the form object errors holder should be exposed.
0327: * Default is {@link ScopeType#FLASH}.
0328: */
0329: private ScopeType formErrorsScope = ScopeType.FLASH;
0330:
0331: /**
0332: * A centralized service for property editor registration, for applying type
0333: * conversion during form object data binding. Can be used as an alternative
0334: * to overriding {@link #registerPropertyEditors(PropertyEditorRegistry)}.
0335: */
0336: private PropertyEditorRegistrar propertyEditorRegistrar;
0337:
0338: /**
0339: * A validator for the form's form object.
0340: */
0341: private Validator validator;
0342:
0343: /**
0344: * Strategy for resolving error message codes.
0345: */
0346: private MessageCodesResolver messageCodesResolver;
0347:
0348: /**
0349: * A cache for dispatched validator methods.
0350: */
0351: private DispatchMethodInvoker validateMethodInvoker;
0352:
0353: /**
0354: * Bean-style default constructor; creates a initially unconfigured
0355: * FormAction instance relying on default property values. Clients invoking
0356: * this constructor directly must set the formObjectClass property
0357: * or override {@link #createFormObject(RequestContext)}.
0358: * @see #setFormObjectClass(Class)
0359: */
0360: public FormAction() {
0361: }
0362:
0363: /**
0364: * Creates a new form action that manages instance(s) of the specified form
0365: * object class.
0366: * @param formObjectClass the class of the form object (must be instantiable)
0367: */
0368: public FormAction(Class formObjectClass) {
0369: setFormObjectClass(formObjectClass);
0370: }
0371:
0372: /**
0373: * Return the name of the form object in the configured scope.
0374: */
0375: public String getFormObjectName() {
0376: return formObjectName;
0377: }
0378:
0379: /**
0380: * Set the name of the form object in the configured scope. The form object
0381: * will be included in the configured scope under this name.
0382: */
0383: public void setFormObjectName(String formObjectName) {
0384: this .formObjectName = formObjectName;
0385: }
0386:
0387: /**
0388: * Return the form object class for this action.
0389: */
0390: public Class getFormObjectClass() {
0391: return formObjectClass;
0392: }
0393:
0394: /**
0395: * Set the form object class for this action. An instance of this class will
0396: * get populated and validated. This is a required property if you register
0397: * a validator with the form action ({@link #setValidator(Validator)})!
0398: * <p>
0399: * If no form object name is set at the moment this method is called, a
0400: * form object name will be automatically generated based on the provided
0401: * form object class using
0402: * {@link ClassUtils#getShortNameAsProperty(java.lang.Class)}.
0403: */
0404: public void setFormObjectClass(Class formObjectClass) {
0405: this .formObjectClass = formObjectClass;
0406: // generate a default form object name
0407: if ((getFormObjectName() == null || getFormObjectName() == DEFAULT_FORM_OBJECT_NAME)
0408: && formObjectClass != null) {
0409: setFormObjectName(ClassUtils
0410: .getShortNameAsProperty(formObjectClass));
0411: }
0412: }
0413:
0414: /**
0415: * Get the scope in which the form object will be placed.
0416: */
0417: public ScopeType getFormObjectScope() {
0418: return formObjectScope;
0419: }
0420:
0421: /**
0422: * Set the scope in which the form object will be placed. The default
0423: * if not set is {@link ScopeType#FLOW flow scope}.
0424: */
0425: public void setFormObjectScope(ScopeType scopeType) {
0426: this .formObjectScope = scopeType;
0427: }
0428:
0429: /**
0430: * Get the scope in which the Errors object will be placed.
0431: */
0432: public ScopeType getFormErrorsScope() {
0433: return formErrorsScope;
0434: }
0435:
0436: /**
0437: * Set the scope in which the Errors object will be placed. The default
0438: * if not set is {@link ScopeType#FLASH flash scope}.
0439: */
0440: public void setFormErrorsScope(ScopeType errorsScope) {
0441: this .formErrorsScope = errorsScope;
0442: }
0443:
0444: /**
0445: * Get the property editor registration strategy for this action's data
0446: * binders.
0447: */
0448: public PropertyEditorRegistrar getPropertyEditorRegistrar() {
0449: return propertyEditorRegistrar;
0450: }
0451:
0452: /**
0453: * Set a property editor registration strategy for this action's data
0454: * binders. This is an alternative to overriding the
0455: * {@link #registerPropertyEditors(PropertyEditorRegistry)} method.
0456: */
0457: public void setPropertyEditorRegistrar(
0458: PropertyEditorRegistrar propertyEditorRegistrar) {
0459: this .propertyEditorRegistrar = propertyEditorRegistrar;
0460: }
0461:
0462: /**
0463: * Returns the validator for this action.
0464: */
0465: public Validator getValidator() {
0466: return validator;
0467: }
0468:
0469: /**
0470: * Set the validator for this action. When setting a validator, you must also
0471: * set the {@link #setFormObjectClass(Class) form object class}. The validator
0472: * must support the specified form object class.
0473: */
0474: public void setValidator(Validator validator) {
0475: this .validator = validator;
0476: }
0477:
0478: /**
0479: * Return the strategy to use for resolving errors into message codes.
0480: */
0481: public MessageCodesResolver getMessageCodesResolver() {
0482: return messageCodesResolver;
0483: }
0484:
0485: /**
0486: * Set the strategy to use for resolving errors into message codes. Applies
0487: * the given strategy to all data binders used by this action.
0488: * <p>
0489: * Default is null, i.e. using the default strategy of the data binder.
0490: * @see #createBinder(RequestContext, Object)
0491: * @see org.springframework.validation.DataBinder#setMessageCodesResolver(org.springframework.validation.MessageCodesResolver)
0492: */
0493: public void setMessageCodesResolver(
0494: MessageCodesResolver messageCodesResolver) {
0495: this .messageCodesResolver = messageCodesResolver;
0496: }
0497:
0498: protected void initAction() {
0499: if (getValidator() != null) {
0500: if (getFormObjectClass() != null
0501: && !getValidator().supports(getFormObjectClass())) {
0502: throw new IllegalArgumentException("Validator ["
0503: + getValidator()
0504: + "] does not support form object class ["
0505: + getFormObjectClass() + "]");
0506: }
0507: // signature: public void ${validateMethodName}(${formObjectClass}, Errors)
0508: validateMethodInvoker = new DispatchMethodInvoker(
0509: getValidator(), new Class[] { getFormObjectClass(),
0510: Errors.class });
0511: }
0512: }
0513:
0514: // action methods
0515:
0516: /**
0517: * Prepares a form object for display in a new form, creating it and caching
0518: * it in the {@link #getFormObjectScope()} if necessary. Also installs
0519: * custom property editors for formatting form object values in UI controls
0520: * such as text fields.
0521: * <p>
0522: * A new form object instance will only be created (or more generally
0523: * acquired) with a call to {@link #createFormObject(RequestContext)},
0524: * if the form object does not yet exist in the configured
0525: * {@link #getFormObjectScope() scope}. If you want to reset the form
0526: * handling machinery, including creation or loading of a fresh form object
0527: * instance, call {@link #resetForm(RequestContext)} instead.
0528: * <p>
0529: * NOTE: This action method is not designed to be overidden and might
0530: * become <code>final</code> in a future version of Spring Web Flow. If
0531: * you need to execute custom form setup logic have your flow call this
0532: * method along with your own custom methods as part of a single action
0533: * chain.
0534: * @param context the action execution context, for accessing and setting
0535: * data in "flow scope" or "request scope"
0536: * @return "success" when binding and validation is successful
0537: * @throws Exception an <b>unrecoverable</b> exception occurs, either
0538: * checked or unchecked
0539: * @see #createFormObject(RequestContext)
0540: */
0541: public Event setupForm(RequestContext context) throws Exception {
0542: if (logger.isDebugEnabled()) {
0543: logger.debug("Executing setupForm");
0544: }
0545: // retrieve the form object, creating it if necessary
0546: Object formObject = getFormObject(context);
0547: ensureFormErrorsExposed(context, formObject);
0548: return success();
0549: }
0550:
0551: /**
0552: * Bind incoming request parameters to allowed fields of the form object and
0553: * then validate the bound form object if a validator is configured.
0554: * <p>
0555: * NOTE: This action method is not designed to be overidden and might
0556: * become <code>final</code> in a future version of Spring Web Flow. If
0557: * you need to execute custom bind and validate logic have your flow call
0558: * this method along with your own custom methods as part of a single action
0559: * chain. Alternatively, override the
0560: * {@link #doBind(RequestContext, DataBinder)} or
0561: * {@link #doValidate(RequestContext, Object, Errors)} hooks.
0562: * @param context the action execution context, for accessing and setting
0563: * data in "flow scope" or "request scope"
0564: * @return "success" when binding and validation is successful, "error" if
0565: * there were binding and/or validation errors
0566: * @throws Exception an <b>unrecoverable</b> exception occured, either
0567: * checked or unchecked
0568: */
0569: public Event bindAndValidate(RequestContext context)
0570: throws Exception {
0571: if (logger.isDebugEnabled()) {
0572: logger.debug("Executing bind");
0573: }
0574: Object formObject = getFormObject(context);
0575: DataBinder binder = createBinder(context, formObject);
0576: doBind(context, binder);
0577: if (getValidator() != null && validationEnabled(context)) {
0578: if (logger.isDebugEnabled()) {
0579: logger.debug("Executing validation");
0580: }
0581: doValidate(context, formObject, binder.getErrors());
0582: } else {
0583: if (logger.isDebugEnabled()) {
0584: if (getValidator() == null) {
0585: logger
0586: .debug("No validator is configured, no validation will occur after binding");
0587: } else {
0588: logger
0589: .debug("Validation was disabled for this bindAndValidate request");
0590: }
0591: }
0592: }
0593: putFormErrors(context, binder.getErrors());
0594: return binder.getErrors().hasErrors() ? error() : success();
0595: }
0596:
0597: /**
0598: * Bind incoming request parameters to allowed fields of the form object.
0599: * <p>
0600: * NOTE: This action method is not designed to be overidden and might
0601: * become <code>final</code> in a future version of Spring Web Flow. If
0602: * you need to execute custom data binding logic have your flow call this
0603: * method along with your own custom methods as part of a single action
0604: * chain. Alternatively, override the
0605: * {@link #doBind(RequestContext, DataBinder)} hook.
0606: * @param context the action execution context, for accessing and setting
0607: * data in "flow scope" or "request scope"
0608: * @return "success" if there are no binding errors, "error" otherwise
0609: * @throws Exception an <b>unrecoverable</b> exception occured, either
0610: * checked or unchecked
0611: */
0612: public Event bind(RequestContext context) throws Exception {
0613: if (logger.isDebugEnabled()) {
0614: logger.debug("Executing bind");
0615: }
0616: Object formObject = getFormObject(context);
0617: DataBinder binder = createBinder(context, formObject);
0618: doBind(context, binder);
0619: putFormErrors(context, binder.getErrors());
0620: return binder.getErrors().hasErrors() ? error() : success();
0621: }
0622:
0623: /**
0624: * Validate the form object by invoking the validator if configured.
0625: * <p>
0626: * NOTE: This action method is not designed to be overidden and might
0627: * become <code>final</code> in a future version of Spring Web Flow. If
0628: * you need to execute custom validation logic have your flow call this
0629: * method along with your own custom methods as part of a single action
0630: * chain. Alternatively, override the
0631: * {@link #doValidate(RequestContext, Object, Errors)} hook.
0632: * @param context the action execution context, for accessing and setting
0633: * data in "flow scope" or "request scope"
0634: * @return "success" if there are no validation errors, "error" otherwise
0635: * @throws Exception an <b>unrecoverable</b> exception occured, either
0636: * checked or unchecked
0637: * @see #getValidator()
0638: */
0639: public Event validate(RequestContext context) throws Exception {
0640: if (getValidator() != null && validationEnabled(context)) {
0641: if (logger.isDebugEnabled()) {
0642: logger.debug("Executing validation");
0643: }
0644: Object formObject = getFormObject(context);
0645: Errors errors = getFormErrors(context);
0646: doValidate(context, formObject, errors);
0647: return errors.hasErrors() ? error() : success();
0648: } else {
0649: if (logger.isDebugEnabled()) {
0650: if (getValidator() == null) {
0651: logger
0652: .debug("No validator is configured, no validation will occur");
0653: } else {
0654: logger
0655: .debug("Validation was disabled for this request");
0656: }
0657: }
0658: return success();
0659: }
0660: }
0661:
0662: /**
0663: * Resets the form by clearing out the form object in the specified scope
0664: * and recreating it.
0665: * <p>
0666: * NOTE: This action method is not designed to be overidden and might
0667: * become <code>final</code> in a future version of Spring Web Flow. If
0668: * you need to execute custom reset logic have your flow call this method
0669: * along with your own custom methods as part of a single action chain.
0670: * @param context the request context
0671: * @return "success" if the reset action completed successfully
0672: * @throws Exception if an exception occured
0673: * @see #createFormObject(RequestContext)
0674: */
0675: public Event resetForm(RequestContext context) throws Exception {
0676: Object formObject = initFormObject(context);
0677: initFormErrors(context, formObject);
0678: return success();
0679: }
0680:
0681: // internal helpers
0682:
0683: /**
0684: * Create the new form object and put it in the configured
0685: * {@link #getFormObjectScope() scope}.
0686: * @param context the flow execution request context
0687: * @return the new form object
0688: * @throws Exception an exception occured creating the form object
0689: */
0690: private Object initFormObject(RequestContext context)
0691: throws Exception {
0692: if (logger.isDebugEnabled()) {
0693: logger.debug("Creating new form object with name '"
0694: + getFormObjectName() + "'");
0695: }
0696: Object formObject = createFormObject(context);
0697: putFormObject(context, formObject);
0698: return formObject;
0699: }
0700:
0701: /**
0702: * Put given form object in the configured scope of given context.
0703: */
0704: private void putFormObject(RequestContext context, Object formObject) {
0705: if (logger.isDebugEnabled()) {
0706: logger.debug("Putting form object of type ["
0707: + formObject.getClass() + "] in scope "
0708: + getFormObjectScope() + " with name '"
0709: + getFormObjectName() + "'");
0710: }
0711: getFormObjectAccessor(context).putFormObject(formObject,
0712: getFormObjectName(), getFormObjectScope());
0713: }
0714:
0715: /**
0716: * Initialize a new form object {@link Errors errors} instance in the
0717: * configured {@link #getFormErrorsScope() scope}. This method also
0718: * registers any {@link PropertiesEditor property editors} used to format
0719: * form object property values.
0720: * @param context the current flow execution request context
0721: * @param formObject the form object for which errors will be tracked
0722: */
0723: private Errors initFormErrors(RequestContext context,
0724: Object formObject) throws Exception {
0725: if (logger.isDebugEnabled()) {
0726: logger
0727: .debug("Creating new form errors for object with name '"
0728: + getFormObjectName() + "'");
0729: }
0730: Errors errors = createBinder(context, formObject).getErrors();
0731: putFormErrors(context, errors);
0732: return errors;
0733: }
0734:
0735: /**
0736: * Put given errors instance in the configured scope of given context.
0737: */
0738: private void putFormErrors(RequestContext context, Errors errors) {
0739: if (logger.isDebugEnabled()) {
0740: logger.debug("Putting form errors instance in scope "
0741: + getFormErrorsScope());
0742: }
0743: getFormObjectAccessor(context).putFormErrors(errors,
0744: getFormErrorsScope());
0745: }
0746:
0747: /**
0748: * Make sure a <i>valid</i> Errors instance for given form object is exposed
0749: * in given context.
0750: */
0751: private void ensureFormErrorsExposed(RequestContext context,
0752: Object formObject) throws Exception {
0753: if (!formErrorsExposed(context)) {
0754: // initialize and expose a fresh errors instance to the flow with
0755: // editors applied
0756: initFormErrors(context, formObject);
0757: } else {
0758: // trying to reuse an existing errors instance
0759: if (formErrorsValid(context, formObject)) {
0760: // reapply property editors against the existing errors instance
0761: reinstallPropertyEditors(context);
0762: } else {
0763: // the existing errors instance seems to be invalid
0764: // initialize a new errors instance, but copy over error information
0765: if (logger.isInfoEnabled()) {
0766: logger
0767: .info("Fixing inconsistent Errors instance: initializing a new Errors instance "
0768: + "wrapping from object '"
0769: + formObject
0770: + "' in scope '"
0771: + getFormErrorsScope()
0772: + "' and copying over all existing error information.");
0773: }
0774: Errors invalidExistingErrors = getFormObjectAccessor(
0775: context).getFormErrors(getFormObjectName(),
0776: getFormErrorsScope());
0777: Errors newErrors = initFormErrors(context, formObject);
0778: newErrors.addAllErrors(invalidExistingErrors);
0779: }
0780: }
0781: }
0782:
0783: /**
0784: * Check if there is an Errors instance available in given
0785: * context for given form object.
0786: */
0787: private boolean formErrorsExposed(RequestContext context) {
0788: return getFormObjectAccessor(context).getFormErrors(
0789: getFormObjectName(), getFormErrorsScope()) != null;
0790: }
0791:
0792: /**
0793: * Check if the Errors instance available in given context is valid for
0794: * given form object.
0795: */
0796: private boolean formErrorsValid(RequestContext context,
0797: Object formObject) {
0798: Errors errors = getFormObjectAccessor(context).getFormErrors(
0799: getFormObjectName(), getFormErrorsScope());
0800: if (errors instanceof BindException) {
0801: BindException be = (BindException) errors;
0802: if (be.getTarget() != formObject) {
0803: if (logger.isInfoEnabled()) {
0804: logger
0805: .info("Inconsistency detected: the Errors instance in '"
0806: + getFormErrorsScope()
0807: + "' does NOT wrap the current form object '"
0808: + formObject
0809: + "' of class "
0810: + formObject.getClass()
0811: + "; instead this Errors instance unexpectedly wraps the target object '"
0812: + be.getTarget()
0813: + "' of class: "
0814: + be.getTarget().getClass() + ".");
0815: }
0816: return false;
0817: } else {
0818: return true;
0819: }
0820: } else {
0821: return true;
0822: }
0823: }
0824:
0825: /**
0826: * Re-registers property editors against the current form errors instance.
0827: * @param context the flow execution request context
0828: */
0829: private void reinstallPropertyEditors(RequestContext context) {
0830: BindException errors = (BindException) getFormObjectAccessor(
0831: context).getFormErrors(getFormObjectName(),
0832: getFormErrorsScope());
0833: registerPropertyEditors(context,
0834: getPropertyEditorRegistry(errors));
0835: }
0836:
0837: /**
0838: * Obtain a property editor registry from given bind exception (errors
0839: * instance).
0840: */
0841: private PropertyEditorRegistry getPropertyEditorRegistry(
0842: BindException errors) {
0843: Method accessor;
0844: try {
0845: if (hasPropertyEditorRegistryAccessor) {
0846: accessor = errors.getClass().getMethod(
0847: "getPropertyEditorRegistry", null);
0848: } else {
0849: // only way to get at the registry in 1.2.8 or <.
0850: accessor = errors.getClass().getDeclaredMethod(
0851: "getBeanWrapper", null);
0852: accessor.setAccessible(true);
0853: }
0854: } catch (NoSuchMethodException e) {
0855: throw new IllegalStateException(
0856: "Unable to resolve property editor registry accessor method as expected - this should not happen");
0857: }
0858: return (PropertyEditorRegistry) ReflectionUtils.invokeMethod(
0859: accessor, errors);
0860: }
0861:
0862: /**
0863: * Invoke specified validator method on the validator registered with this
0864: * action. The validator method for piecemeal validation should have the
0865: * following signature:
0866: * <pre>
0867: * public void ${validateMethodName}(${formObjectClass}, Errors)
0868: * </pre>
0869: * @param validatorMethod the name of the validator method to invoke
0870: * @param formObject the form object
0871: * @param errors possible binding errors
0872: * @throws Exception when an unrecoverable exception occurs
0873: */
0874: private void invokeValidatorMethod(String validatorMethod,
0875: Object formObject, Errors errors) throws Exception {
0876: if (logger.isDebugEnabled()) {
0877: logger.debug("Invoking piecemeal validator method '"
0878: + validatorMethod + "(" + getFormObjectClass()
0879: + ", Errors)'");
0880: }
0881: getValidateMethodInvoker().invoke(validatorMethod,
0882: new Object[] { formObject, errors });
0883: }
0884:
0885: // accessible helpers (subclasses could override if necessary)
0886:
0887: /**
0888: * Convenience method that returns the form object for this form action. If
0889: * not found in the configured scope, a new form object will be created by a
0890: * call to {@link #createFormObject(RequestContext)} and exposed in the
0891: * configured {@link #getFormObjectScope() scope}.
0892: * <p>
0893: * The returned form object will become the
0894: * {@link FormObjectAccessor#setCurrentFormObject(Object, ScopeType) current}
0895: * form object.
0896: * @param context the flow execution request context
0897: * @return the form object
0898: * @throws Exception when an unrecoverable exception occurs
0899: */
0900: protected Object getFormObject(RequestContext context)
0901: throws Exception {
0902: FormObjectAccessor accessor = getFormObjectAccessor(context);
0903: Object formObject = accessor.getFormObject(getFormObjectName(),
0904: getFormObjectScope());
0905: if (formObject == null) {
0906: formObject = initFormObject(context);
0907: } else {
0908: if (logger.isDebugEnabled()) {
0909: logger.debug("Found existing form object with name '"
0910: + getFormObjectName() + "' of type ["
0911: + formObject.getClass() + "] in scope "
0912: + getFormObjectScope());
0913: }
0914: accessor.setCurrentFormObject(formObject,
0915: getFormObjectScope());
0916: }
0917: return formObject;
0918: }
0919:
0920: /**
0921: * Convenience method that returns the form object errors for this form
0922: * action. If not found in the configured scope, a new form object errors
0923: * will be created, initialized, and exposed in the confgured
0924: * {@link #getFormErrorsScope() scope}.
0925: * <p>
0926: * Keep in mind that an Errors instance wraps a form object, so a form
0927: * object will also be created if required
0928: * (see {@link #getFormObject(RequestContext)}).
0929: * @param context the flow request context
0930: * @return the form errors
0931: * @throws Exception when an unrecoverable exception occurs
0932: */
0933: protected Errors getFormErrors(RequestContext context)
0934: throws Exception {
0935: Object formObject = getFormObject(context);
0936: ensureFormErrorsExposed(context, formObject);
0937: return getFormObjectAccessor(context).getFormErrors(
0938: getFormObjectName(), getFormErrorsScope());
0939: }
0940:
0941: /**
0942: * Create a new binder instance for the given form object and request
0943: * context. Can be overridden to plug in custom DataBinder subclasses.
0944: * <p>
0945: * Default implementation creates a standard WebDataBinder, and invokes
0946: * {@link #initBinder(RequestContext, DataBinder)} and
0947: * {@link #registerPropertyEditors(PropertyEditorRegistry)}.
0948: * @param context the action execution context, for accessing and setting
0949: * data in "flow scope" or "request scope"
0950: * @param formObject the form object to bind onto
0951: * @return the new binder instance
0952: * @throws Exception when an unrecoverable exception occurs
0953: * @see WebDataBinder
0954: * @see #initBinder(RequestContext, DataBinder)
0955: * @see #setMessageCodesResolver(MessageCodesResolver)
0956: */
0957: protected DataBinder createBinder(RequestContext context,
0958: Object formObject) throws Exception {
0959: DataBinder binder = new WebDataBinder(formObject,
0960: getFormObjectName());
0961: if (getMessageCodesResolver() != null) {
0962: binder.setMessageCodesResolver(getMessageCodesResolver());
0963: }
0964: initBinder(context, binder);
0965: registerPropertyEditors(context, binder);
0966: return binder;
0967: }
0968:
0969: /**
0970: * Bind allowed parameters in the external context request parameter map to
0971: * the form object using given binder.
0972: * @param context the action execution context, for accessing and setting
0973: * data in "flow scope" or "request scope"
0974: * @param binder the data binder to use
0975: * @throws Exception when an unrecoverable exception occurs
0976: */
0977: protected void doBind(RequestContext context, DataBinder binder)
0978: throws Exception {
0979: if (logger.isDebugEnabled()) {
0980: logger.debug("Binding allowed request parameters in "
0981: + StylerUtils.style(context.getExternalContext()
0982: .getRequestParameterMap())
0983: + " to form object with name '"
0984: + binder.getObjectName()
0985: + "', pre-bind formObject toString = "
0986: + binder.getTarget());
0987: if (binder.getAllowedFields() != null
0988: && binder.getAllowedFields().length > 0) {
0989: logger.debug("(Allowed fields are "
0990: + StylerUtils.style(binder.getAllowedFields())
0991: + ")");
0992: } else {
0993: logger.debug("(Any field is allowed)");
0994: }
0995: }
0996: binder.bind(new MutablePropertyValues(context
0997: .getRequestParameters().asMap()));
0998: if (logger.isDebugEnabled()) {
0999: logger
1000: .debug("Binding completed for form object with name '"
1001: + binder.getObjectName()
1002: + "', post-bind formObject toString = "
1003: + binder.getTarget());
1004: logger.debug("There are ["
1005: + binder.getErrors().getErrorCount()
1006: + "] errors, details: "
1007: + binder.getErrors().getAllErrors());
1008: }
1009: }
1010:
1011: /**
1012: * Validate given form object using a registered validator. If a
1013: * "validatorMethod" action property is specified for the currently
1014: * executing action, the identified validator method will be invoked. When
1015: * no such property is found, the defualt <code>validate()</code> method
1016: * is invoked.
1017: * @param context the action execution context, for accessing and setting
1018: * data in "flow scope" or "request scope"
1019: * @param formObject the form object
1020: * @param errors the errors instance to record validation errors in
1021: * @throws Exception when an unrecoverable exception occurs
1022: */
1023: protected void doValidate(RequestContext context,
1024: Object formObject, Errors errors) throws Exception {
1025: Assert
1026: .notNull(getValidator(),
1027: "The validator must not be null when attempting validation -- programmer error");
1028: String validatorMethodName = context.getAttributes().getString(
1029: VALIDATOR_METHOD_ATTRIBUTE);
1030: if (StringUtils.hasText(validatorMethodName)) {
1031: if (logger.isDebugEnabled()) {
1032: logger.debug("Invoking validation method '"
1033: + validatorMethodName + "' on validator "
1034: + getValidator());
1035: }
1036: invokeValidatorMethod(validatorMethodName, formObject,
1037: errors);
1038: } else {
1039: if (logger.isDebugEnabled()) {
1040: logger.debug("Invoking validator " + getValidator());
1041: }
1042: getValidator().validate(formObject, errors);
1043: }
1044: if (logger.isDebugEnabled()) {
1045: logger.debug("Validation completed for form object");
1046: logger.debug("There are [" + errors.getErrorCount()
1047: + "] errors, details: " + errors.getAllErrors());
1048: }
1049: }
1050:
1051: /**
1052: * Returns a dispatcher to invoke validation methods. Subclasses could
1053: * override this to return a custom dispatcher.
1054: */
1055: protected DispatchMethodInvoker getValidateMethodInvoker() {
1056: return validateMethodInvoker;
1057: }
1058:
1059: /**
1060: * Factory method that returns a new form object accessor for accessing form
1061: * objects in the provided request context.
1062: * @param context the flow request context
1063: * @return the accessor
1064: */
1065: protected FormObjectAccessor getFormObjectAccessor(
1066: RequestContext context) {
1067: return new FormObjectAccessor(context);
1068: }
1069:
1070: // common subclassing hook methods
1071:
1072: /**
1073: * Create the backing form object instance that should be managed by this
1074: * {@link FormAction form action}. By default, will attempt to instantiate
1075: * a new form object instance of type {@link #getFormObjectClass()}
1076: * transiently in memory.
1077: * <p>
1078: * Subclasses should override if they need to load the form object from a
1079: * specific location or resource such as a database or filesystem.
1080: * <p>
1081: * Subclasses should override if they need to customize how a transient form
1082: * object is assembled during creation.
1083: * @param context the action execution context for accessing flow data
1084: * @return the form object
1085: * @throws IllegalStateException if the {@link #getFormObjectClass()}
1086: * property is not set and this method has not been overridden
1087: * @throws Exception when an unrecoverable exception occurs
1088: */
1089: protected Object createFormObject(RequestContext context)
1090: throws Exception {
1091: if (getFormObjectClass() == null) {
1092: throw new IllegalStateException(
1093: "Cannot create form object without formObjectClass property being set -- "
1094: + "either set formObjectClass or override createFormObject");
1095: }
1096: if (logger.isDebugEnabled()) {
1097: logger.debug("Creating new instance of form object class ["
1098: + getFormObjectClass() + "]");
1099: }
1100: return getFormObjectClass().newInstance();
1101: }
1102:
1103: /**
1104: * Initialize a new binder instance. This hook allows customization of
1105: * binder settings such as the {@link DataBinder#getAllowedFields() allowed fields},
1106: * {@link DataBinder#getRequiredFields() required fields} and
1107: * {@link DataBinder#initDirectFieldAccess() direct field access}. Called by
1108: * {@link #createBinder(RequestContext, Object)}.
1109: * <p>
1110: * Note that registration of custom property editors should be done in
1111: * {@link #registerPropertyEditors(PropertyEditorRegistry)}, not here! This
1112: * method will only be called when a <b>new</b> data binder is created.
1113: * @param context the action execution context, for accessing and setting
1114: * data in "flow scope" or "request scope"
1115: * @param binder new binder instance
1116: * @see #createBinder(RequestContext, Object)
1117: */
1118: protected void initBinder(RequestContext context, DataBinder binder) {
1119: }
1120:
1121: /**
1122: * Register custom editors to perform type conversion on fields of your form
1123: * object during data binding and form display. This method is called on
1124: * form errors initialization and
1125: * {@link #initBinder(RequestContext, DataBinder) data binder} initialization.
1126: * <p>
1127: * Property editors give you full control over how objects are transformed
1128: * to and from a formatted String form for display on a user interface such
1129: * as a HTML page.
1130: * <p>
1131: * This default implementation will call the
1132: * {@link #registerPropertyEditors(PropertyEditorRegistry) simpler form} of
1133: * the method not taking a <tt>RequestContext</tt> parameter.
1134: * @param context the action execution context, for accessing and setting
1135: * data in "flow scope" or "request scope"
1136: * @param registry the property editor registry to register editors in
1137: * @see #registerPropertyEditors(PropertyEditorRegistry)
1138: */
1139: protected void registerPropertyEditors(RequestContext context,
1140: PropertyEditorRegistry registry) {
1141: registerPropertyEditors(registry);
1142: }
1143:
1144: /**
1145: * Register custom editors to perform type conversion on fields of your form
1146: * object during data binding and form display. This method is called on
1147: * form errors initialization and
1148: * {@link #initBinder(RequestContext, DataBinder) data binder} initialization.
1149: * <p>
1150: * Property editors give you full control over how objects are transformed
1151: * to and from a formatted String form for display on a user interface such
1152: * as a HTML page.
1153: * <p>
1154: * This default implementation will simply call <tt>registerCustomEditors</tt>
1155: * on the {@link #getPropertyEditorRegistrar() propertyEditorRegistrar} object
1156: * that has been set for the action, if any.
1157: * @param registry the property editor registry to register editors in
1158: */
1159: protected void registerPropertyEditors(
1160: PropertyEditorRegistry registry) {
1161: if (getPropertyEditorRegistrar() != null) {
1162: if (logger.isDebugEnabled()) {
1163: logger
1164: .debug("Registering custom property editors using configured registrar");
1165: }
1166: getPropertyEditorRegistrar()
1167: .registerCustomEditors(registry);
1168: } else {
1169: if (logger.isDebugEnabled()) {
1170: logger
1171: .debug("No property editor registrar set, no custom editors to register");
1172: }
1173: }
1174: }
1175:
1176: /**
1177: * Return whether validation should be performed given the state of the flow
1178: * request context. Default implementation always returns true.
1179: * @param context the request context, for accessing and setting data in
1180: * "flow scope" or "request scope"
1181: * @return whether or not validation is enabled
1182: */
1183: protected boolean validationEnabled(RequestContext context) {
1184: return true;
1185: }
1186: }
|