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

org.springframework.web.struts.SpringBindingActionForm Maven / Gradle / Ivy

There is a newer version: 5.3.34
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.web.struts;

import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.ConvertUtilsBean;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.util.MessageResources;

import org.springframework.context.MessageSourceResolvable;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;

/**
 * A thin Struts ActionForm adapter that delegates to Spring's more complete
 * and advanced data binder and Errors object underneath the covers to bind
 * to POJOs and manage rejected values.
 *
 * 

Exposes Spring-managed errors to the standard Struts view tags, through * exposing a corresponding Struts ActionMessages object as request attribute. * Also exposes current field values in a Struts-compliant fashion, including * rejected values (which Spring's binding keeps even for non-String fields). * *

Consequently, Struts views can be written in a completely traditional * fashion (with standard html:form, html:errors, etc), * seamlessly accessing a Spring-bound POJO form object underneath. * *

Note this ActionForm is designed explicitly for use in request scope. * It expects to receive an expose call from the Action, passing * in the Errors object to expose plus the current HttpServletRequest. * *

Example definition in struts-config.xml: * *

 * <form-beans>
 *   <form-bean name="actionForm" type="org.springframework.web.struts.SpringBindingActionForm"/>
 * </form-beans>
* * Example code in a custom Struts Action: * *
 * public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception {
 *   SpringBindingActionForm form = (SpringBindingActionForm) actionForm;
 *   MyPojoBean bean = ...;
 *   ServletRequestDataBinder binder = new ServletRequestDataBinder(bean, "myPojo");
 *   binder.bind(request);
 *   form.expose(binder.getBindingResult(), request);
 *   return actionMapping.findForward("success");
 * }
* * This class is compatible with both Struts 1.2.x and Struts 1.1. * On Struts 1.2, default messages registered with Spring binding errors * are exposed when none of the error codes could be resolved. * On Struts 1.1, this is not possible due to a limitation in the Struts * message facility; hence, we expose the plain default error code there. * * @author Keith Donald * @author Juergen Hoeller * @since 1.2.2 * @see #expose(org.springframework.validation.Errors, javax.servlet.http.HttpServletRequest) */ public class SpringBindingActionForm extends ActionForm { private static final Log logger = LogFactory.getLog(SpringBindingActionForm.class); private static boolean defaultActionMessageAvailable = true; static { // Register special PropertyUtilsBean subclass that knows how to // extract field values from a SpringBindingActionForm. // As a consequence of the static nature of Commons BeanUtils, // we have to resort to this initialization hack here. ConvertUtilsBean convUtils = new ConvertUtilsBean(); PropertyUtilsBean propUtils = new SpringBindingAwarePropertyUtilsBean(); BeanUtilsBean beanUtils = new BeanUtilsBean(convUtils, propUtils); BeanUtilsBean.setInstance(beanUtils); // Determine whether the Struts 1.2 support for default messages // is available on ActionMessage: ActionMessage(String, boolean) // with "false" to be passed into the boolean flag. try { ActionMessage.class.getConstructor(new Class[] {String.class, boolean.class}); } catch (NoSuchMethodException ex) { defaultActionMessageAvailable = false; } } private Errors errors; private Locale locale; private MessageResources messageResources; /** * Set the Errors object that this SpringBindingActionForm is supposed * to wrap. The contained field values and errors will be exposed * to the view, accessible through Struts standard tags. * @param errors the Spring Errors object to wrap, usually taken from * a DataBinder that has been used for populating a POJO form object * @param request the HttpServletRequest to retrieve the attributes from * @see org.springframework.validation.DataBinder#getBindingResult() */ public void expose(Errors errors, HttpServletRequest request) { this.errors = errors; // Obtain the locale from Struts well-known location. this.locale = (Locale) request.getSession().getAttribute(Globals.LOCALE_KEY); // Obtain the MessageResources from Struts' well-known location. this.messageResources = (MessageResources) request.getAttribute(Globals.MESSAGES_KEY); if (errors != null && errors.hasErrors()) { // Add global ActionError instances from the Spring Errors object. ActionMessages actionMessages = (ActionMessages) request.getAttribute(Globals.ERROR_KEY); if (actionMessages == null) { request.setAttribute(Globals.ERROR_KEY, getActionMessages()); } else { actionMessages.add(getActionMessages()); } } } /** * Return an ActionMessages representation of this SpringBindingActionForm, * exposing all errors contained in the underlying Spring Errors object. * @see org.springframework.validation.Errors#getAllErrors() */ private ActionMessages getActionMessages() { ActionMessages actionMessages = new ActionMessages(); Iterator it = this.errors.getAllErrors().iterator(); while (it.hasNext()) { ObjectError objectError = (ObjectError) it.next(); String effectiveMessageKey = findEffectiveMessageKey(objectError); if (effectiveMessageKey == null && !defaultActionMessageAvailable) { // Need to specify default code despite it not being resolvable: // Struts 1.1 ActionMessage doesn't support default messages. effectiveMessageKey = objectError.getCode(); } ActionMessage message = (effectiveMessageKey != null) ? new ActionMessage(effectiveMessageKey, resolveArguments(objectError.getArguments())) : new ActionMessage(objectError.getDefaultMessage(), false); if (objectError instanceof FieldError) { FieldError fieldError = (FieldError) objectError; actionMessages.add(fieldError.getField(), message); } else { actionMessages.add(ActionMessages.GLOBAL_MESSAGE, message); } } if (logger.isDebugEnabled()) { logger.debug("Final ActionMessages used for binding: " + actionMessages); } return actionMessages; } private Object[] resolveArguments(Object[] arguments) { if (arguments == null || arguments.length == 0) { return arguments; } for (int i = 0; i < arguments.length; i++) { Object arg = arguments[i]; if (arg instanceof MessageSourceResolvable) { MessageSourceResolvable resolvable = (MessageSourceResolvable)arg; String[] codes = resolvable.getCodes(); boolean resolved = false; if (this.messageResources != null) { for (int j = 0; j < codes.length; j++) { String code = codes[j]; if (this.messageResources.isPresent(this.locale, code)) { arguments[i] = this.messageResources.getMessage( this.locale, code, resolveArguments(resolvable.getArguments())); resolved = true; break; } } } if (!resolved) { arguments[i] = resolvable.getDefaultMessage(); } } } return arguments; } /** * Find the most specific message key for the given error. * @param error the ObjectError to find a message key for * @return the most specific message key found */ private String findEffectiveMessageKey(ObjectError error) { if (this.messageResources != null) { String[] possibleMatches = error.getCodes(); for (int i = 0; i < possibleMatches.length; i++) { if (logger.isDebugEnabled()) { logger.debug("Looking for error code '" + possibleMatches[i] + "'"); } if (this.messageResources.isPresent(this.locale, possibleMatches[i])) { if (logger.isDebugEnabled()) { logger.debug("Found error code '" + possibleMatches[i] + "' in resource bundle"); } return possibleMatches[i]; } } } if (logger.isDebugEnabled()) { logger.debug("Could not find a suitable message error code, returning default message"); } return null; } /** * Get the formatted value for the property at the provided path. * The formatted value is a string value for display, converted * via a registered property editor. * @param propertyPath the property path * @return the formatted property value * @throws NoSuchMethodException if called during Struts binding * (without Spring Errors object being exposed), to indicate no * available property to Struts */ private Object getFieldValue(String propertyPath) throws NoSuchMethodException { if (this.errors == null) { throw new NoSuchMethodException( "No bean properties exposed to Struts binding - performing Spring binding later on"); } return this.errors.getFieldValue(propertyPath); } /** * Special subclass of PropertyUtilsBean that it is aware of SpringBindingActionForm * and uses it for retrieving field values. The field values will be taken from * the underlying POJO form object that the Spring Errors object was created for. */ private static class SpringBindingAwarePropertyUtilsBean extends PropertyUtilsBean { public Object getNestedProperty(Object bean, String propertyPath) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { // Extract Spring-managed field value in case of SpringBindingActionForm. if (bean instanceof SpringBindingActionForm) { SpringBindingActionForm form = (SpringBindingActionForm) bean; return form.getFieldValue(propertyPath); } // Else fall back to default PropertyUtils behavior. return super.getNestedProperty(bean, propertyPath); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy