diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 970c068ff13..86f92deaffc 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -36,9 +36,11 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionException; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -326,6 +328,24 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra return null; } + public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException { + try { + PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); + if (pd != null) { + if (pd.getReadMethod() != null) { + return new TypeDescriptor(new MethodParameter(pd.getReadMethod(), -1)); + } + else if (pd.getWriteMethod() != null) { + return new TypeDescriptor(new MethodParameter(pd.getWriteMethod(), 0)); + } + } + } + catch (InvalidPropertyException ex) { + // Consider as not determinable. + } + return null; + } + public boolean isReadableProperty(String propertyName) { try { PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java index e16dc2df1f0..760b6f7fb0d 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.Map; import org.springframework.core.MethodParameter; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -86,6 +87,14 @@ public class DirectFieldAccessor extends AbstractPropertyAccessor { return null; } + public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException { + Field field = this.fieldMap.get(propertyName); + if (field != null) { + return new TypeDescriptor(field); + } + return null; + } + @Override public Object getPropertyValue(String propertyName) throws BeansException { Field field = this.fieldMap.get(propertyName); diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessor.java b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessor.java index b8c3ad73acd..e9a980c6ddb 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessor.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -18,6 +18,8 @@ package org.springframework.beans; import java.util.Map; +import org.springframework.core.convert.TypeDescriptor; + /** * Common interface for classes that can access named properties * (such as bean properties of an object or fields in an object) @@ -86,6 +88,17 @@ public interface PropertyAccessor { */ Class getPropertyType(String propertyName) throws BeansException; + /** + * Return a type descriptor for the specified property. + * @param propertyName the property to check + * (may be a nested path and/or an indexed/mapped property) + * @return the property type for the particular property, + * or null if not determinable + * @throws InvalidPropertyException if there is no such property or + * if the property isn't readable + */ + TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException; + /** * Get the current value of the specified property. * @param propertyName the name of the property to get the value of diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java b/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java index d530566e1a4..92ebc7c2a6a 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java @@ -20,6 +20,7 @@ import java.beans.PropertyDescriptor; import java.beans.PropertyEditor; import java.lang.reflect.Array; import java.lang.reflect.Field; +import java.lang.reflect.Constructor; import java.util.Collection; import java.util.Iterator; import java.util.Map; @@ -203,8 +204,18 @@ class TypeConverterDelegate { convertedValue = convertToTypedMap((Map) convertedValue, propertyName, methodParam); } else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) { - String strValue = ((String) convertedValue).trim(); - if (requiredType.isEnum() && "".equals(strValue)) { + try { + Constructor strCtor = requiredType.getConstructor(String.class); + return (T) BeanUtils.instantiateClass(strCtor, convertedValue); + } + catch (NoSuchMethodException ex) { + // proceed with field lookup + if (logger.isTraceEnabled()) { + logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex); + } + } + String trimmedValue = ((String) convertedValue).trim(); + if (requiredType.isEnum() && "".equals(trimmedValue)) { // It's an empty enum identifier: reset the enum value to null. return null; } @@ -212,7 +223,7 @@ class TypeConverterDelegate { // with values defined as static fields. Resulting value still needs // to be checked, hence we don't return it right away. try { - Field enumField = requiredType.getField(strValue); + Field enumField = requiredType.getField(trimmedValue); convertedValue = enumField.get(null); } catch (Throwable ex) { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java index 8df463db5cb..da252ee217e 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java @@ -17,6 +17,7 @@ package org.springframework.beans.factory.config; import java.beans.PropertyEditor; +import java.security.AccessControlContext; import org.springframework.beans.PropertyEditorRegistrar; import org.springframework.beans.PropertyEditorRegistry; @@ -249,6 +250,12 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single */ Scope getRegisteredScope(String scopeName); + /** + * Provides a security access control context relevant to this factory. + * @return the applicable AccessControlContext (never null) + */ + AccessControlContext getAccessControlContext(); + /** * Copy all relevant configuration from the given other factory. *

Should include all standard configuration settings as well as diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index bb5580de84a..f8a801b9b04 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -150,6 +150,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp /** Map from scope identifier String to corresponding Scope */ private final Map scopes = new HashMap(); + /** Security context used when running with a SecurityManager */ + private SecurityContextProvider securityContextProvider; + /** Map from bean name to merged RootBeanDefinition */ private final Map mergedBeanDefinitions = new ConcurrentHashMap(); @@ -161,9 +164,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp private final ThreadLocal prototypesCurrentlyInCreation = new NamedThreadLocal("Prototype beans currently in creation"); - /** security context used when running with a Security Manager */ - private volatile SecurityContextProvider securityProvider = new SimpleSecurityContextProvider(); - /** * Create a new AbstractBeanFactory. */ @@ -761,6 +761,26 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp return this.scopes.get(scopeName); } + /** + * Set the security context provider for this bean factory. If a security manager + * is set, interaction with the user code will be executed using the privileged + * of the provided security context. + */ + public void setSecurityContextProvider(SecurityContextProvider securityProvider) { + this.securityContextProvider = securityProvider; + } + + /** + * Delegate the creation of the access control context to the + * {@link #setSecurityContextProvider SecurityContextProvider}. + */ + @Override + public AccessControlContext getAccessControlContext() { + return (this.securityContextProvider != null ? + this.securityContextProvider.getAccessControlContext() : + AccessController.getContext()); + } + public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) { Assert.notNull(otherFactory, "BeanFactory must not be null"); setBeanClassLoader(otherFactory.getBeanClassLoader()); @@ -776,6 +796,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp this.hasDestructionAwareBeanPostProcessors = this.hasDestructionAwareBeanPostProcessors || otherAbstractFactory.hasDestructionAwareBeanPostProcessors; this.scopes.putAll(otherAbstractFactory.scopes); + this.securityContextProvider = otherAbstractFactory.securityContextProvider; } else { setTypeConverter(otherFactory.getTypeConverter()); @@ -1436,37 +1457,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp } } } - - /** - * {@inheritDoc} - * - * Delegate the creation of the security context to {@link #getSecurityContextProvider()}. - */ - @Override - protected AccessControlContext getAccessControlContext() { - SecurityContextProvider provider = getSecurityContextProvider(); - return (provider != null ? provider.getAccessControlContext(): AccessController.getContext()); - } - /** - * Return the security context provider for this bean factory. - * - * @return - */ - public SecurityContextProvider getSecurityContextProvider() { - return securityProvider; - } - - /** - * Set the security context provider for this bean factory. If a security manager - * is set, interaction with the user code will be executed using the privileged - * of the provided security context. - * - * @param securityProvider - */ - public void setSecurityContextProvider(SecurityContextProvider securityProvider) { - this.securityProvider = securityProvider; - } //--------------------------------------------------------------------- // Abstract methods to be implemented by subclasses @@ -1526,4 +1517,5 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp */ protected abstract Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException; + } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java index 5702fa9a11f..978daf72213 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java @@ -212,10 +212,10 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg * Returns the security context for this bean factory. If a security manager * is set, interaction with the user code will be executed using the privileged * of the security context returned by this method. - * - * @return + * @see AccessController#getContext() */ protected AccessControlContext getAccessControlContext() { return AccessController.getContext(); } -} \ No newline at end of file + +} diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java index 00e8ab2f471..ba09748b535 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java @@ -20,15 +20,16 @@ import java.security.AccessControlContext; /** * Provider of the security context of the code running inside the bean factory. - * + * * @author Costin Leau + * @since 3.0 */ public interface SecurityContextProvider { /** * Provides a security access control context relevant to a bean factory. - * * @return bean factory security control context */ AccessControlContext getAccessControlContext(); + } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java index 156fcf4fe80..14b9d883fe0 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java @@ -20,18 +20,19 @@ import java.security.AccessControlContext; import java.security.AccessController; /** - * Simple #SecurityContextProvider implementation. + * Simple {@link SecurityContextProvider} implementation. * * @author Costin Leau + * @since 3.0 */ public class SimpleSecurityContextProvider implements SecurityContextProvider { private final AccessControlContext acc; + /** - * Constructs a new SimpleSecurityContextProvider instance. - * - * The security context will be retrieved on each call from the current + * Construct a new SimpleSecurityContextProvider instance. + *

The security context will be retrieved on each call from the current * thread. */ public SimpleSecurityContextProvider() { @@ -39,20 +40,19 @@ public class SimpleSecurityContextProvider implements SecurityContextProvider { } /** - * Constructs a new SimpleSecurityContextProvider instance. - * - * If the given control context is null, the security context will be + * Construct a new SimpleSecurityContextProvider instance. + *

If the given control context is null, the security context will be * retrieved on each call from the current thread. - * + * @param acc access control context (can be null) * @see AccessController#getContext() - * @param acc - * access control context (can be null) */ public SimpleSecurityContextProvider(AccessControlContext acc) { this.acc = acc; } + public AccessControlContext getAccessControlContext() { - return (acc == null ? AccessController.getContext() : acc); + return (this.acc != null ? acc : AccessController.getContext()); } + } diff --git a/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java index e00ff721d2c..27407621e3b 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java @@ -44,6 +44,13 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life */ String CONFIG_LOCATION_DELIMITERS = ",; \t\n"; + /** + * Name of the ConversionService bean in the factory. + * If none is supplied, default conversion rules apply. + * @see org.springframework.core.convert.ConversionService + */ + String CONVERSION_SERVICE_BEAN_NAME = "conversionService"; + /** * Name of the LoadTimeWeaver bean in the factory. If such a bean is supplied, * the context will use a temporary ClassLoader for type matching, in order diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 53ac5b1e5b3..2114820abbb 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -63,6 +63,7 @@ import org.springframework.context.weaving.LoadTimeWeaverAwareProcessor; import org.springframework.core.OrderComparator; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; +import org.springframework.core.convert.ConversionService; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -367,6 +368,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); + // Initialize conversion service for this context. + initConversionService(); + // Initialize message source for this context. initMessageSource(); @@ -605,6 +609,16 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader } } + /** + * Initialize the BeanFactory's ConversionService. + */ + protected void initConversionService() { + ConfigurableListableBeanFactory beanFactory = getBeanFactory(); + if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME)) { + beanFactory.setConversionService(beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); + } + } + /** * Initialize the MessageSource. * Use parent's if none defined in this context. diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java index d44f52cf531..129d679db78 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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. @@ -22,9 +22,6 @@ import java.security.PrivilegedAction; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.AbstractBeanFactory; -import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ConfigurableApplicationContext; @@ -43,6 +40,7 @@ import org.springframework.context.ResourceLoaderAware; * underlying bean factory. Applications do not use this directly. * * @author Juergen Hoeller + * @author Costin Leau * @since 10.10.2003 * @see org.springframework.context.ResourceLoaderAware * @see org.springframework.context.MessageSourceAware @@ -52,37 +50,33 @@ import org.springframework.context.ResourceLoaderAware; */ class ApplicationContextAwareProcessor implements BeanPostProcessor { - private final ApplicationContext applicationContext; + private final ConfigurableApplicationContext applicationContext; /** * Create a new ApplicationContextAwareProcessor for the given context. */ - public ApplicationContextAwareProcessor(ApplicationContext applicationContext) { + public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) { this.applicationContext = applicationContext; } public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException { AccessControlContext acc = null; - - if (System.getSecurityManager() != null) { - if (applicationContext instanceof ConfigurableApplicationContext) { - ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); - if (factory instanceof AbstractBeanFactory) { - acc = ((AbstractBeanFactory) factory).getSecurityContextProvider().getAccessControlContext(); + + if (System.getSecurityManager() != null && + (bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware || + bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) { + acc = this.applicationContext.getBeanFactory().getAccessControlContext(); + } + + if (acc != null) { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + doProcess(bean); + return null; } - } - // optimize - check the bean class before creating the inner class + native call - if (bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware - || bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware) { - AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { - doProcess(bean); - return null; - } - }, acc); - } + }, acc); } else { doProcess(bean); @@ -109,4 +103,5 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor { public Object postProcessAfterInitialization(Object bean, String name) { return bean; } -} \ No newline at end of file + +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/context/support/UiApplicationContextUtils.java b/org.springframework.context/src/main/java/org/springframework/ui/context/support/UiApplicationContextUtils.java index d3263d541ac..3669b47574b 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/context/support/UiApplicationContextUtils.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/context/support/UiApplicationContextUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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. @@ -55,7 +55,7 @@ public abstract class UiApplicationContextUtils { */ public static ThemeSource initThemeSource(ApplicationContext context) { if (context.containsLocalBean(THEME_SOURCE_BEAN_NAME)) { - ThemeSource themeSource = (ThemeSource) context.getBean(THEME_SOURCE_BEAN_NAME, ThemeSource.class); + ThemeSource themeSource = context.getBean(THEME_SOURCE_BEAN_NAME, ThemeSource.class); // Make ThemeSource aware of parent ThemeSource. if (context.getParent() instanceof ThemeSource && themeSource instanceof HierarchicalThemeSource) { HierarchicalThemeSource hts = (HierarchicalThemeSource) themeSource; diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java b/org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java index 05bf91ba6ec..23e4885ddad 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java @@ -1,25 +1,30 @@ /* - * Copyright 2004-2009 the original author or authors. - * + * Copyright 2002-2009 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.ui.format; import java.lang.annotation.Annotation; /** - * A factory that creates {@link Formatter formatters} to format property values on properties annotated with a particular format {@link Annotation}. - * For example, a CurrencyAnnotationFormatterFactory might create a Formatter that formats a BigDecimal value set on a property annotated with @CurrencyFormat. + * A factory that creates {@link Formatter formatters} to format property values on properties + * annotated with a particular format {@link Annotation}. + * + *

For example, a CurrencyAnnotationFormatterFactory might create a Formatter + * that formats a BigDecimal value set on a property annotated with @CurrencyFormat. + * * @author Keith Donald * @since 3.0 * @param The type of Annotation this factory uses to create Formatter instances @@ -34,4 +39,5 @@ public interface AnnotationFormatterFactory { * @return the Formatter to use to format values of properties annotated with the annotation. */ Formatter getFormatter(A annotation); -} \ No newline at end of file + +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java b/org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java index e717697f4fd..82370ae9580 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2009 the original author or authors. + * Copyright 2002-2009 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.ui.format; import java.lang.annotation.Documented; @@ -23,6 +24,7 @@ import java.lang.annotation.Target; /** * A type that can be formatted as a String for display in a user interface. + * * @author Keith Donald * @since 3.0 */ @@ -30,9 +32,10 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Formatted { - + /** - * The Formatter that handles the formatting. + * The Formatter that handles the formatting for the annotated element. */ Class value(); + } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/Formatter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/Formatter.java index e9342f6169c..7dffdf9a287 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/Formatter.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/Formatter.java @@ -1,18 +1,19 @@ /* - * Copyright 2004-2009 the original author or authors. - * + * Copyright 2002-2009 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.ui.format; import java.text.ParseException; @@ -20,6 +21,7 @@ import java.util.Locale; /** * Formats objects of type T for display. + * * @author Keith Donald * @since 3.0 * @param the type of object this formatter can format @@ -42,4 +44,5 @@ public interface Formatter { * @throws ParseException when a parse exception occurs */ T parse(String formatted, Locale locale) throws ParseException; + } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java index 2ba39676a21..d9d5a7e63b3 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2009 the original author or authors. + * Copyright 2002-2009 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.ui.format; import java.lang.annotation.Annotation; @@ -21,6 +22,7 @@ import org.springframework.core.convert.TypeDescriptor; /** * A shared registry of Formatters. + * * @author Keith Donald * @since 3.0 */ @@ -42,21 +44,19 @@ public interface FormatterRegistry { * On parse, the decorator first delegates to the formatter to parse a <T>, then coerses the parsed value to type. * @param type the object type * @param targetFormatter the target formatter - * @param the type of object the target formatter formats */ - void add(Class type, Formatter targetFormatter); + void add(Class type, Formatter targetFormatter); /** * Adds a AnnotationFormatterFactory that returns the Formatter for properties annotated with a specific annotation. * @param factory the annotation formatter factory */ - void add(AnnotationFormatterFactory factory); + void add(AnnotationFormatterFactory factory); /** * Get the Formatter for the type descriptor. * @return the Formatter, or null if no suitable one is registered */ - @SuppressWarnings("unchecked") - Formatter getFormatter(TypeDescriptor type); + Formatter getFormatter(TypeDescriptor type); } diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/date/DateFormatter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/date/DateFormatter.java index 416770d2182..296dfd2febf 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/date/DateFormatter.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/date/DateFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2009 the original author or authors. + * Copyright 2002-2009 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.ui.format.date; import java.text.DateFormat; @@ -21,37 +22,60 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.ui.format.Formatter; /** * A formatter for {@link java.util.Date} types. * Allows the configuration of an explicit date pattern and locale. + * * @author Keith Donald + * @author Juergen Hoeller * @since 3.0 * @see SimpleDateFormat */ public final class DateFormatter implements Formatter { - private static Log logger = LogFactory.getLog(DateFormatter.class); - - /** - * The default date pattern. - */ - private static final String DEFAULT_PATTERN = "yyyy-MM-dd"; - private String pattern; + private int style = DateFormat.DEFAULT; + + /** - * Sets the pattern to use to format date values. - * If not specified, the default pattern 'yyyy-MM-dd' is used. - * @param pattern the date formatting pattern + * Create a new default DateFormatter. + */ + public DateFormatter() { + } + + /** + * Create a new DateFormatter for the given date pattern. + */ + public DateFormatter(String pattern) { + this.pattern = pattern; + } + + + /** + * Set the pattern to use to format date values. + *

If not specified, DateFormat's default style will be used. */ public void setPattern(String pattern) { this.pattern = pattern; } + /** + * Set the style to use to format date values. + *

If not specified, DateFormat's default style will be used. + * @see DateFormat#DEFAULT + * @see DateFormat#SHORT + * @see DateFormat#MEDIUM + * @see DateFormat#LONG + * @see DateFormat#FULL + */ + public void setStyle(int style) { + this.style = style; + } + + public String format(Date date, Locale locale) { if (date == null) { return ""; @@ -66,23 +90,17 @@ public final class DateFormatter implements Formatter { return getDateFormat(locale).parse(formatted); } - // internal helpers - private DateFormat getDateFormat(Locale locale) { - DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT, locale); - format.setLenient(false); - if (format instanceof SimpleDateFormat) { - String pattern = determinePattern(this.pattern); - ((SimpleDateFormat) format).applyPattern(pattern); - } else { - logger.warn("Unable to apply format pattern '" + pattern - + "'; Returned DateFormat is not a SimpleDateFormat"); + protected DateFormat getDateFormat(Locale locale) { + DateFormat dateFormat; + if (this.pattern != null) { + dateFormat = new SimpleDateFormat(this.pattern, locale); } - return format; + else { + dateFormat = DateFormat.getDateInstance(this.style, locale); + } + dateFormat.setLenient(false); + return dateFormat; } - private String determinePattern(String pattern) { - return pattern != null ? pattern : DEFAULT_PATTERN; - } - -} \ No newline at end of file +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingConversionServiceAdapter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingConversionServiceAdapter.java new file mode 100644 index 00000000000..837f699a09d --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingConversionServiceAdapter.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2009 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.ui.format.support; + +import java.text.ParseException; + +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.ui.format.Formatter; +import org.springframework.ui.format.FormatterRegistry; +import org.springframework.ui.format.support.GenericFormatterRegistry; +import org.springframework.util.Assert; + +/** + * Adapter that exposes a {@link ConversionService} reference for a given + * {@link org.springframework.ui.format.FormatterRegistry}, retrieving the current + * Locale from {@link org.springframework.context.i18n.LocaleContextHolder}. + * + * @author Juergen Hoeller + * @since 3.0 + */ +public class FormattingConversionServiceAdapter implements ConversionService { + + private final FormatterRegistry formatterRegistry; + + private final ConversionService targetConversionService; + + + /** + * Create a new FormattingConversionServiceAdapter for the given FormatterRegistry. + * @param formatterRegistry the FormatterRegistry to wrap + */ + public FormattingConversionServiceAdapter(FormatterRegistry formatterRegistry) { + Assert.notNull(formatterRegistry, "FormatterRegistry must not be null"); + this.formatterRegistry = formatterRegistry; + if (formatterRegistry instanceof GenericFormatterRegistry) { + this.targetConversionService = ((GenericFormatterRegistry) formatterRegistry).getConversionService(); + } + else { + this.targetConversionService = new DefaultConversionService(); + } + } + + + public boolean canConvert(Class sourceType, Class targetType) { + return canConvert(sourceType, TypeDescriptor.valueOf(targetType)); + } + + public boolean canConvert(Class sourceType, TypeDescriptor targetType) { + return (this.formatterRegistry.getFormatter(targetType) != null || + this.targetConversionService.canConvert(sourceType, targetType)); + } + + @SuppressWarnings("unchecked") + public T convert(Object source, Class targetType) { + return (T) convert(source, TypeDescriptor.valueOf(targetType)); + } + + public Object convert(Object source, TypeDescriptor targetType) { + if (source instanceof String) { + Formatter formatter = this.formatterRegistry.getFormatter(targetType); + if (formatter != null) { + try { + return formatter.parse((String) source, LocaleContextHolder.getLocale()); + } + catch (ParseException ex) { + throw new ConversionFailedException(source, String.class, targetType.getType(), ex); + } + } + } + return this.targetConversionService.convert(source, targetType); + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingPropertyEditorAdapter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingPropertyEditorAdapter.java new file mode 100644 index 00000000000..039679fa1c7 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingPropertyEditorAdapter.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2009 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.ui.format.support; + +import java.beans.PropertyEditorSupport; +import java.text.ParseException; + +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.ui.format.Formatter; +import org.springframework.util.Assert; + +/** + * Adapter that exposes a {@link java.beans.PropertyEditor} for any given + * {@link org.springframework.ui.format.Formatter}, retrieving the current + * Locale from {@link org.springframework.context.i18n.LocaleContextHolder}. + * + * @author Juergen Hoeller + * @since 3.0 + */ +public class FormattingPropertyEditorAdapter extends PropertyEditorSupport { + + private final Formatter formatter; + + + /** + * Create a new FormattingPropertyEditorAdapter for the given Formatter. + * @param formatter the Formatter to wrap + */ + public FormattingPropertyEditorAdapter(Formatter formatter) { + Assert.notNull(formatter, "Formatter must not be null"); + this.formatter = formatter; + } + + + @Override + public void setAsText(String text) throws IllegalArgumentException { + try { + setValue(this.formatter.parse(text, LocaleContextHolder.getLocale())); + } + catch (ParseException ex) { + throw new IllegalArgumentException("Failed to parse formatted value", ex); + } + } + + @Override + public String getAsText() { + return this.formatter.format(getValue(), LocaleContextHolder.getLocale()); + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/ui/format/support/GenericFormatterRegistry.java similarity index 65% rename from org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java rename to org.springframework.context/src/main/java/org/springframework/ui/format/support/GenericFormatterRegistry.java index 8663233187e..6015bd3f67c 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/support/GenericFormatterRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2009 the original author or authors. + * Copyright 2002-2009 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. @@ -13,62 +13,74 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.ui.format; + +package org.springframework.ui.format.support; import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.text.ParseException; -import java.util.HashMap; import java.util.LinkedList; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.GenericTypeResolver; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.util.Assert; +import org.springframework.ui.format.FormatterRegistry; +import org.springframework.ui.format.Formatter; +import org.springframework.ui.format.AnnotationFormatterFactory; +import org.springframework.ui.format.Formatted; /** - * A generic implementation of {@link FormatterRegistry} suitable for use in most environments. + * A generic implementation of {@link org.springframework.ui.format.FormatterRegistry} suitable for use in most environments. + * * @author Keith Donald + * @author Juergen Hoeller * @since 3.0 * @see #setConversionService(ConversionService) - * @see #add(Formatter) - * @see #add(Class, Formatter) - * @see #add(AnnotationFormatterFactory) + * @see #add(org.springframework.ui.format.Formatter) + * @see #add(Class, org.springframework.ui.format.Formatter) + * @see #add(org.springframework.ui.format.AnnotationFormatterFactory) */ -@SuppressWarnings("unchecked") -public class GenericFormatterRegistry implements FormatterRegistry { +public class GenericFormatterRegistry implements FormatterRegistry, BeanFactoryAware, Cloneable { - private Map typeFormatters = new ConcurrentHashMap(); + private final Map typeFormatters = new ConcurrentHashMap(); - private Map annotationFormatters = new HashMap(); + private final Map annotationFormatters = + new ConcurrentHashMap(); private ConversionService conversionService = new DefaultConversionService(); + private boolean shared = true; + + /** - * Sets the type conversion service that will be used to coerse objects to the types required for formatting. - * Defaults to a {@link DefaultConversionService}. - * @param conversionService the conversion service - * @see #add(Class, Formatter) + * Registers the formatters in the set provided. + * JavaBean-friendly alternative to calling {@link #add(Formatter)}. + * @see #add(Formatter) */ - public void setConversionService(ConversionService conversionService) { - this.conversionService = conversionService; + public void setFormatters(Set> formatters) { + for (Formatter formatter : formatters) { + add(formatter); + } } - + /** * Registers the formatters in the map provided by type. * JavaBean-friendly alternative to calling {@link #add(Class, Formatter)}. - * @param formatters the formatters map * @see #add(Class, Formatter) */ - public void setFormatters(Map, Formatter> formatters) { + public void setFormatterMap(Map, Formatter> formatters) { for (Map.Entry, Formatter> entry : formatters.entrySet()) { add(entry.getKey(), entry.getValue()); } @@ -85,36 +97,103 @@ public class GenericFormatterRegistry implements FormatterRegistry { } } + /** + * Specify the type conversion service that will be used to coerce objects to the + * types required for formatting. Defaults to a {@link DefaultConversionService}. + * @see #add(Class, Formatter) + */ + public void setConversionService(ConversionService conversionService) { + Assert.notNull(conversionService, "ConversionService must not be null"); + this.conversionService = conversionService; + } + + /** + * Return the type conversion service which this FormatterRegistry delegates to. + */ + public ConversionService getConversionService() { + return this.conversionService; + } + + /** + * Take the context's default ConversionService if none specified locally. + */ + public void setBeanFactory(BeanFactory beanFactory) { + if (this.conversionService == null && + beanFactory.containsBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)) { + this.conversionService = beanFactory.getBean( + ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, ConversionService.class); + } + } + + + // cloning support + + /** + * Specify whether this FormatterRegistry is shared, in which case newly + * registered Formatters will be visible to other callers as well. + *

A new GenericFormatterRegistry is considered as shared by default, + * whereas a cloned GenericFormatterRegistry will be non-shared by default. + * @see #clone() + */ + public void setShared(boolean shared) { + this.shared = shared; + } + + /** + * Return whether this FormatterRegistry is shared, in which case newly + * registered Formatters will be visible to other callers as well. + */ + public boolean isShared() { + return this.shared; + } + + /** + * Create an independent clone of this FormatterRegistry. + * @see #setShared + */ + @Override + public GenericFormatterRegistry clone() { + GenericFormatterRegistry clone = new GenericFormatterRegistry(); + clone.typeFormatters.putAll(this.typeFormatters); + clone.annotationFormatters.putAll(this.annotationFormatters); + clone.conversionService = this.conversionService; + clone.shared = false; + return clone; + } + + // implementing FormatterRegistry public void add(Formatter formatter) { - typeFormatters.put(getFormattedObjectType(formatter.getClass()), formatter); + this.typeFormatters.put(getFormattedObjectType(formatter.getClass()), formatter); } - public void add(Class type, Formatter formatter) { + public void add(Class type, Formatter formatter) { Class formattedObjectType = getFormattedObjectType(formatter.getClass()); - if (!conversionService.canConvert(formattedObjectType, type)) { + if (!this.conversionService.canConvert(formattedObjectType, type)) { throw new IllegalArgumentException("Unable to register formatter " + formatter + " for type [" + type.getName() + "]; not able to convert from [" + formattedObjectType.getName() + "] to parse"); } - if (!conversionService.canConvert(type, formattedObjectType)) { + if (!this.conversionService.canConvert(type, formattedObjectType)) { throw new IllegalArgumentException("Unable to register formatter " + formatter + " for type [" + type.getName() + "]; not able to convert to [" + formattedObjectType.getName() + "] to format"); } - typeFormatters.put(type, formatter); + this.typeFormatters.put(type, formatter); } - public void add(AnnotationFormatterFactory factory) { - annotationFormatters.put(getAnnotationType(factory.getClass()), factory); + public void add(AnnotationFormatterFactory factory) { + this.annotationFormatters.put(getAnnotationType(factory.getClass()), factory); } - public Formatter getFormatter(TypeDescriptor type) { + @SuppressWarnings("unchecked") + public Formatter getFormatter(TypeDescriptor type) { Assert.notNull(type, "The TypeDescriptor is required"); - Formatter formatter = getAnnotationFormatter(type); + Formatter formatter = getAnnotationFormatter(type); if (formatter == null) { formatter = getTypeFormatter(type.getType()); } return formatter; } + // internal helpers private Class getFormattedObjectType(Class formatterClass) { @@ -175,7 +254,8 @@ public class GenericFormatterRegistry implements FormatterRegistry { + factoryClass.getName() + "]; does the factory parameterize the generic type?"); } - private Formatter getAnnotationFormatter(TypeDescriptor type) { + @SuppressWarnings("unchecked") + private Formatter getAnnotationFormatter(TypeDescriptor type) { Annotation[] annotations = type.getAnnotations(); for (Annotation a : annotations) { AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType()); @@ -186,17 +266,19 @@ public class GenericFormatterRegistry implements FormatterRegistry { return null; } - private Formatter getTypeFormatter(Class type) { + private Formatter getTypeFormatter(Class type) { Assert.notNull(type, "The Class of the object to format is required"); Formatter formatter = findFormatter(type); if (formatter != null) { Class formattedObjectType = getFormattedObjectType(formatter.getClass()); if (type.isAssignableFrom(formattedObjectType)) { return formatter; - } else { + } + else { return new ConvertingFormatter(type, formattedObjectType, formatter); } - } else { + } + else { return getDefaultFormatter(type); } } @@ -236,11 +318,13 @@ public class GenericFormatterRegistry implements FormatterRegistry { throw new IllegalStateException( "Formatter referenced by @Formatted annotation does not have public constructor", e); } - } else { + } + else { return null; } } + private class ConvertingFormatter implements Formatter { private Class type; @@ -255,6 +339,7 @@ public class GenericFormatterRegistry implements FormatterRegistry { this.targetFormatter = targetFormatter; } + @SuppressWarnings("unchecked") public String format(Object object, Locale locale) { object = conversionService.convert(object, formattedObjectType); return targetFormatter.format(object, locale); @@ -268,4 +353,4 @@ public class GenericFormatterRegistry implements FormatterRegistry { } -} \ No newline at end of file +} diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/support/package-info.java b/org.springframework.context/src/main/java/org/springframework/ui/format/support/package-info.java new file mode 100644 index 00000000000..af2f5de6958 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/support/package-info.java @@ -0,0 +1,6 @@ +/** + * Support classes for the formatting package, providing + * common implementations as well as adapters. + */ +package org.springframework.ui.format; + diff --git a/org.springframework.context/src/main/java/org/springframework/validation/AbstractBindingResult.java b/org.springframework.context/src/main/java/org/springframework/validation/AbstractBindingResult.java index 6add9ee9e26..4432aad6044 100644 --- a/org.springframework.context/src/main/java/org/springframework/validation/AbstractBindingResult.java +++ b/org.springframework.context/src/main/java/org/springframework/validation/AbstractBindingResult.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Set; import org.springframework.beans.PropertyEditorRegistry; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -65,6 +66,7 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi * @see DefaultMessageCodesResolver */ public void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) { + Assert.notNull(messageCodesResolver, "MessageCodesResolver must not be null"); this.messageCodesResolver = messageCodesResolver; } diff --git a/org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java b/org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java index 2acff157959..0766361c627 100644 --- a/org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java +++ b/org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -22,6 +22,13 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.ConfigurablePropertyAccessor; import org.springframework.beans.PropertyAccessorUtils; import org.springframework.beans.PropertyEditorRegistry; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.ui.format.Formatter; +import org.springframework.ui.format.FormatterRegistry; +import org.springframework.ui.format.support.FormattingConversionServiceAdapter; +import org.springframework.ui.format.support.FormattingPropertyEditorAdapter; +import org.springframework.util.Assert; /** * Abstract base class for {@link BindingResult} implementations that work with @@ -37,6 +44,9 @@ import org.springframework.beans.PropertyEditorRegistry; */ public abstract class AbstractPropertyBindingResult extends AbstractBindingResult { + private FormatterRegistry formatterRegistry; + + /** * Create a new AbstractPropertyBindingResult instance. * @param objectName the name of the target object @@ -47,6 +57,12 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul } + public void initFormatterLookup(FormatterRegistry formatterRegistry) { + Assert.notNull(formatterRegistry, "FormatterRegistry must not be null"); + this.formatterRegistry = formatterRegistry; + getPropertyAccessor().setConversionService(new FormattingConversionServiceAdapter(formatterRegistry)); + } + /** * Returns the underlying PropertyAccessor. * @see #getPropertyAccessor() @@ -89,7 +105,13 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul */ @Override protected Object formatFieldValue(String field, Object value) { - PropertyEditor customEditor = getCustomEditor(field); + String fixedField = fixedField(field); + TypeDescriptor td = getPropertyAccessor().getPropertyTypeDescriptor(fixedField); + Formatter formatter = (this.formatterRegistry != null ? this.formatterRegistry.getFormatter(td) : null); + if (formatter != null) { + return formatter.format(value, LocaleContextHolder.getLocale()); + } + PropertyEditor customEditor = getCustomEditor(fixedField); if (customEditor != null) { customEditor.setValue(value); String textValue = customEditor.getAsText(); @@ -104,11 +126,10 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul /** * Retrieve the custom PropertyEditor for the given field, if any. - * @param field the field name + * @param fixedField the fully qualified field name * @return the custom PropertyEditor, or null */ - protected PropertyEditor getCustomEditor(String field) { - String fixedField = fixedField(field); + protected PropertyEditor getCustomEditor(String fixedField) { Class targetType = getPropertyAccessor().getPropertyType(fixedField); PropertyEditor editor = getPropertyAccessor().findCustomEditor(targetType, fixedField); if (editor == null) { @@ -117,6 +138,22 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul return editor; } + /** + * This implementation exposes a PropertyEditor adapter for a Formatter, + * if applicable. + */ + @Override + public PropertyEditor findEditor(String field, Class valueType) { + TypeDescriptor td = (valueType != null ? TypeDescriptor.valueOf(valueType) : + getPropertyAccessor().getPropertyTypeDescriptor(fixedField(field))); + final Formatter formatter = + (this.formatterRegistry != null ? this.formatterRegistry.getFormatter(td) : null); + if (formatter != null) { + return new FormattingPropertyEditorAdapter(formatter); + } + return super.findEditor(field, valueType); + } + /** * Provide the PropertyAccessor to work with, according to the diff --git a/org.springframework.context/src/main/java/org/springframework/validation/BindingResult.java b/org.springframework.context/src/main/java/org/springframework/validation/BindingResult.java index 9f981813f18..c6815621655 100644 --- a/org.springframework.context/src/main/java/org/springframework/validation/BindingResult.java +++ b/org.springframework.context/src/main/java/org/springframework/validation/BindingResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -88,10 +88,10 @@ public interface BindingResult extends Errors { /** * Find a custom property editor for the given type and property. - * @param valueType the type of the property (can be null if a property - * is given but should be specified in any case for consistency checking) * @param field the path of the property (name or nested path), or * null if looking for an editor for all properties of the given type + * @param valueType the type of the property (can be null if a property + * is given but should be specified in any case for consistency checking) * @return the registered editor, or null if none */ PropertyEditor findEditor(String field, Class valueType); diff --git a/org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java b/org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java index 149ef53d65d..67b45026147 100644 --- a/org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java +++ b/org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java @@ -35,6 +35,9 @@ import org.springframework.beans.SimpleTypeConverter; import org.springframework.beans.TypeConverter; import org.springframework.beans.TypeMismatchException; import org.springframework.core.MethodParameter; +import org.springframework.ui.format.FormatterRegistry; +import org.springframework.ui.format.support.GenericFormatterRegistry; +import org.springframework.ui.format.support.FormattingConversionServiceAdapter; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.PatternMatchUtils; @@ -132,6 +135,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor(); + private FormatterRegistry formatterRegistry; + /** * Create a new DataBinder instance, with default object name. @@ -178,6 +183,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { Assert.isNull(this.bindingResult, "DataBinder is already initialized - call initBeanPropertyAccess before any other configuration methods"); this.bindingResult = new BeanPropertyBindingResult(getTarget(), getObjectName()); + if (this.formatterRegistry != null) { + this.bindingResult.initFormatterLookup(this.formatterRegistry); + } } /** @@ -189,6 +197,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { Assert.isNull(this.bindingResult, "DataBinder is already initialized - call initDirectFieldAccess before any other configuration methods"); this.bindingResult = new DirectFieldBindingResult(getTarget(), getObjectName()); + if (this.formatterRegistry != null) { + this.bindingResult.initFormatterLookup(this.formatterRegistry); + } } /** @@ -215,6 +226,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { protected SimpleTypeConverter getSimpleTypeConverter() { if (this.typeConverter == null) { this.typeConverter = new SimpleTypeConverter(); + if (this.formatterRegistry != null) { + this.typeConverter.setConversionService(new FormattingConversionServiceAdapter(this.formatterRegistry)); + } } return this.typeConverter; } @@ -418,6 +432,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { * @see DefaultBindingErrorProcessor */ public void setBindingErrorProcessor(BindingErrorProcessor bindingErrorProcessor) { + Assert.notNull(bindingErrorProcessor, "BindingErrorProcessor must not be null"); this.bindingErrorProcessor = bindingErrorProcessor; } @@ -428,6 +443,31 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { return this.bindingErrorProcessor; } + /** + * Set the FormatterRegistry to use for obtaining Formatters in preference + * to JavaBeans PropertyEditors. + */ + public void setFormatterRegistry(FormatterRegistry formatterRegistry) { + this.formatterRegistry = formatterRegistry; + } + + /** + * Return the FormatterRegistry to use for obtaining Formatters in preference + * to JavaBeans PropertyEditors. + * @return the FormatterRegistry (never null), which may also be + * used to register further Formatters for this DataBinder + */ + public FormatterRegistry getFormatterRegistry() { + if (this.formatterRegistry == null) { + this.formatterRegistry = new GenericFormatterRegistry(); + } + else if (this.formatterRegistry instanceof GenericFormatterRegistry && + ((GenericFormatterRegistry) this.formatterRegistry).isShared()) { + this.formatterRegistry = ((GenericFormatterRegistry) this.formatterRegistry).clone(); + } + return this.formatterRegistry; + } + //--------------------------------------------------------------------- // Implementation of PropertyEditorRegistry/TypeConverter interface diff --git a/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java b/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java index ec5de930fc7..f4e9726f628 100644 --- a/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java +++ b/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java @@ -19,6 +19,7 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.style.ToStringCreator; import org.springframework.ui.format.number.CurrencyFormatter; import org.springframework.ui.format.number.IntegerFormatter; +import org.springframework.ui.format.support.GenericFormatterRegistry; public class GenericFormatterRegistryTests { diff --git a/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java b/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java index fc667c7ee9b..21f6762feda 100644 --- a/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java +++ b/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java @@ -17,6 +17,7 @@ package org.springframework.validation; import java.beans.PropertyEditorSupport; +import java.beans.PropertyEditor; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; @@ -27,7 +28,6 @@ import java.util.Set; import java.util.TreeSet; import junit.framework.TestCase; -import junit.framework.Assert; import org.springframework.beans.BeanWithObjectProperty; import org.springframework.beans.DerivedTestBean; @@ -42,6 +42,8 @@ import org.springframework.beans.propertyeditors.CustomCollectionEditor; import org.springframework.beans.propertyeditors.CustomNumberEditor; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.context.support.StaticMessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.ui.format.number.DecimalFormatter; import org.springframework.util.StringUtils; /** @@ -289,7 +291,35 @@ public class DataBinderTests extends TestCase { MutablePropertyValues pvs = new MutablePropertyValues(); pvs.addPropertyValue("object", "1"); binder.bind(pvs); - Assert.assertEquals(new Integer(1), tb.getObject()); + assertEquals(new Integer(1), tb.getObject()); + } + + public void testBindingWithFormatter() { + TestBean tb = new TestBean(); + DataBinder binder = new DataBinder(tb); + binder.getFormatterRegistry().add(Float.class, new DecimalFormatter()); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.addPropertyValue("myFloat", "1,2"); + + LocaleContextHolder.setLocale(Locale.GERMAN); + try { + binder.bind(pvs); + assertEquals(new Float(1.2), tb.getMyFloat()); + assertEquals("1,2", binder.getBindingResult().getFieldValue("myFloat")); + + PropertyEditor editor = binder.getBindingResult().findEditor("myFloat", Float.class); + assertNotNull(editor); + editor.setValue(new Float(1.4)); + assertEquals("1,4", editor.getAsText()); + + editor = binder.getBindingResult().findEditor("myFloat", null); + assertNotNull(editor); + editor.setAsText("1,6"); + assertEquals(new Float(1.6), editor.getValue()); + } + finally { + LocaleContextHolder.resetLocaleContext(); + } } public void testBindingWithAllowedFields() throws Exception { @@ -673,36 +703,36 @@ public class DataBinderTests extends TestCase { assertEquals(2, errors.getGlobalErrorCount()); assertEquals("NAME_TOUCHY_MISMATCH", errors.getGlobalError().getCode()); - assertEquals("NAME_TOUCHY_MISMATCH", ((ObjectError) errors.getGlobalErrors().get(0)).getCode()); - assertEquals("NAME_TOUCHY_MISMATCH.tb", ((ObjectError) errors.getGlobalErrors().get(0)).getCodes()[0]); - assertEquals("NAME_TOUCHY_MISMATCH", ((ObjectError) errors.getGlobalErrors().get(0)).getCodes()[1]); - assertEquals("tb", ((ObjectError) errors.getGlobalErrors().get(0)).getObjectName()); - assertEquals("GENERAL_ERROR", ((ObjectError) errors.getGlobalErrors().get(1)).getCode()); - assertEquals("GENERAL_ERROR.tb", ((ObjectError) errors.getGlobalErrors().get(1)).getCodes()[0]); - assertEquals("GENERAL_ERROR", ((ObjectError) errors.getGlobalErrors().get(1)).getCodes()[1]); - assertEquals("msg", ((ObjectError) errors.getGlobalErrors().get(1)).getDefaultMessage()); - assertEquals("arg", ((ObjectError) errors.getGlobalErrors().get(1)).getArguments()[0]); + assertEquals("NAME_TOUCHY_MISMATCH", (errors.getGlobalErrors().get(0)).getCode()); + assertEquals("NAME_TOUCHY_MISMATCH.tb", (errors.getGlobalErrors().get(0)).getCodes()[0]); + assertEquals("NAME_TOUCHY_MISMATCH", (errors.getGlobalErrors().get(0)).getCodes()[1]); + assertEquals("tb", (errors.getGlobalErrors().get(0)).getObjectName()); + assertEquals("GENERAL_ERROR", (errors.getGlobalErrors().get(1)).getCode()); + assertEquals("GENERAL_ERROR.tb", (errors.getGlobalErrors().get(1)).getCodes()[0]); + assertEquals("GENERAL_ERROR", (errors.getGlobalErrors().get(1)).getCodes()[1]); + assertEquals("msg", (errors.getGlobalErrors().get(1)).getDefaultMessage()); + assertEquals("arg", (errors.getGlobalErrors().get(1)).getArguments()[0]); assertTrue(errors.hasFieldErrors()); assertEquals(4, errors.getFieldErrorCount()); assertEquals("TOO_YOUNG", errors.getFieldError().getCode()); - assertEquals("TOO_YOUNG", ((FieldError) errors.getFieldErrors().get(0)).getCode()); - assertEquals("age", ((FieldError) errors.getFieldErrors().get(0)).getField()); - assertEquals("AGE_NOT_ODD", ((FieldError) errors.getFieldErrors().get(1)).getCode()); - assertEquals("age", ((FieldError) errors.getFieldErrors().get(1)).getField()); - assertEquals("NOT_ROD", ((FieldError) errors.getFieldErrors().get(2)).getCode()); - assertEquals("name", ((FieldError) errors.getFieldErrors().get(2)).getField()); - assertEquals("TOO_YOUNG", ((FieldError) errors.getFieldErrors().get(3)).getCode()); - assertEquals("spouse.age", ((FieldError) errors.getFieldErrors().get(3)).getField()); + assertEquals("TOO_YOUNG", (errors.getFieldErrors().get(0)).getCode()); + assertEquals("age", (errors.getFieldErrors().get(0)).getField()); + assertEquals("AGE_NOT_ODD", (errors.getFieldErrors().get(1)).getCode()); + assertEquals("age", (errors.getFieldErrors().get(1)).getField()); + assertEquals("NOT_ROD", (errors.getFieldErrors().get(2)).getCode()); + assertEquals("name", (errors.getFieldErrors().get(2)).getField()); + assertEquals("TOO_YOUNG", (errors.getFieldErrors().get(3)).getCode()); + assertEquals("spouse.age", (errors.getFieldErrors().get(3)).getField()); assertTrue(errors.hasFieldErrors("age")); assertEquals(2, errors.getFieldErrorCount("age")); assertEquals("TOO_YOUNG", errors.getFieldError("age").getCode()); - assertEquals("TOO_YOUNG", ((FieldError) errors.getFieldErrors("age").get(0)).getCode()); - assertEquals("tb", ((FieldError) errors.getFieldErrors("age").get(0)).getObjectName()); - assertEquals("age", ((FieldError) errors.getFieldErrors("age").get(0)).getField()); - assertEquals(new Integer(0), ((FieldError) errors.getFieldErrors("age").get(0)).getRejectedValue()); - assertEquals("AGE_NOT_ODD", ((FieldError) errors.getFieldErrors("age").get(1)).getCode()); + assertEquals("TOO_YOUNG", (errors.getFieldErrors("age").get(0)).getCode()); + assertEquals("tb", (errors.getFieldErrors("age").get(0)).getObjectName()); + assertEquals("age", (errors.getFieldErrors("age").get(0)).getField()); + assertEquals(new Integer(0), (errors.getFieldErrors("age").get(0)).getRejectedValue()); + assertEquals("AGE_NOT_ODD", (errors.getFieldErrors("age").get(1)).getCode()); assertTrue(errors.hasFieldErrors("name")); assertEquals(1, errors.getFieldErrorCount("name")); @@ -711,14 +741,14 @@ public class DataBinderTests extends TestCase { assertEquals("NOT_ROD.name", errors.getFieldError("name").getCodes()[1]); assertEquals("NOT_ROD.java.lang.String", errors.getFieldError("name").getCodes()[2]); assertEquals("NOT_ROD", errors.getFieldError("name").getCodes()[3]); - assertEquals("name", ((FieldError) errors.getFieldErrors("name").get(0)).getField()); - assertEquals(null, ((FieldError) errors.getFieldErrors("name").get(0)).getRejectedValue()); + assertEquals("name", (errors.getFieldErrors("name").get(0)).getField()); + assertEquals(null, (errors.getFieldErrors("name").get(0)).getRejectedValue()); assertTrue(errors.hasFieldErrors("spouse.age")); assertEquals(1, errors.getFieldErrorCount("spouse.age")); assertEquals("TOO_YOUNG", errors.getFieldError("spouse.age").getCode()); - assertEquals("tb", ((FieldError) errors.getFieldErrors("spouse.age").get(0)).getObjectName()); - assertEquals(new Integer(0), ((FieldError) errors.getFieldErrors("spouse.age").get(0)).getRejectedValue()); + assertEquals("tb", (errors.getFieldErrors("spouse.age").get(0)).getObjectName()); + assertEquals(new Integer(0), (errors.getFieldErrors("spouse.age").get(0)).getRejectedValue()); } public void testValidatorWithErrorsAndCodesPrefix() { @@ -744,36 +774,36 @@ public class DataBinderTests extends TestCase { assertEquals(2, errors.getGlobalErrorCount()); assertEquals("validation.NAME_TOUCHY_MISMATCH", errors.getGlobalError().getCode()); - assertEquals("validation.NAME_TOUCHY_MISMATCH", ((ObjectError) errors.getGlobalErrors().get(0)).getCode()); - assertEquals("validation.NAME_TOUCHY_MISMATCH.tb", ((ObjectError) errors.getGlobalErrors().get(0)).getCodes()[0]); - assertEquals("validation.NAME_TOUCHY_MISMATCH", ((ObjectError) errors.getGlobalErrors().get(0)).getCodes()[1]); - assertEquals("tb", ((ObjectError) errors.getGlobalErrors().get(0)).getObjectName()); - assertEquals("validation.GENERAL_ERROR", ((ObjectError) errors.getGlobalErrors().get(1)).getCode()); - assertEquals("validation.GENERAL_ERROR.tb", ((ObjectError) errors.getGlobalErrors().get(1)).getCodes()[0]); - assertEquals("validation.GENERAL_ERROR", ((ObjectError) errors.getGlobalErrors().get(1)).getCodes()[1]); - assertEquals("msg", ((ObjectError) errors.getGlobalErrors().get(1)).getDefaultMessage()); - assertEquals("arg", ((ObjectError) errors.getGlobalErrors().get(1)).getArguments()[0]); + assertEquals("validation.NAME_TOUCHY_MISMATCH", (errors.getGlobalErrors().get(0)).getCode()); + assertEquals("validation.NAME_TOUCHY_MISMATCH.tb", (errors.getGlobalErrors().get(0)).getCodes()[0]); + assertEquals("validation.NAME_TOUCHY_MISMATCH", (errors.getGlobalErrors().get(0)).getCodes()[1]); + assertEquals("tb", (errors.getGlobalErrors().get(0)).getObjectName()); + assertEquals("validation.GENERAL_ERROR", (errors.getGlobalErrors().get(1)).getCode()); + assertEquals("validation.GENERAL_ERROR.tb", (errors.getGlobalErrors().get(1)).getCodes()[0]); + assertEquals("validation.GENERAL_ERROR", (errors.getGlobalErrors().get(1)).getCodes()[1]); + assertEquals("msg", (errors.getGlobalErrors().get(1)).getDefaultMessage()); + assertEquals("arg", (errors.getGlobalErrors().get(1)).getArguments()[0]); assertTrue(errors.hasFieldErrors()); assertEquals(4, errors.getFieldErrorCount()); assertEquals("validation.TOO_YOUNG", errors.getFieldError().getCode()); - assertEquals("validation.TOO_YOUNG", ((FieldError) errors.getFieldErrors().get(0)).getCode()); - assertEquals("age", ((FieldError) errors.getFieldErrors().get(0)).getField()); - assertEquals("validation.AGE_NOT_ODD", ((FieldError) errors.getFieldErrors().get(1)).getCode()); - assertEquals("age", ((FieldError) errors.getFieldErrors().get(1)).getField()); - assertEquals("validation.NOT_ROD", ((FieldError) errors.getFieldErrors().get(2)).getCode()); - assertEquals("name", ((FieldError) errors.getFieldErrors().get(2)).getField()); - assertEquals("validation.TOO_YOUNG", ((FieldError) errors.getFieldErrors().get(3)).getCode()); - assertEquals("spouse.age", ((FieldError) errors.getFieldErrors().get(3)).getField()); + assertEquals("validation.TOO_YOUNG", (errors.getFieldErrors().get(0)).getCode()); + assertEquals("age", (errors.getFieldErrors().get(0)).getField()); + assertEquals("validation.AGE_NOT_ODD", (errors.getFieldErrors().get(1)).getCode()); + assertEquals("age", (errors.getFieldErrors().get(1)).getField()); + assertEquals("validation.NOT_ROD", (errors.getFieldErrors().get(2)).getCode()); + assertEquals("name", (errors.getFieldErrors().get(2)).getField()); + assertEquals("validation.TOO_YOUNG", (errors.getFieldErrors().get(3)).getCode()); + assertEquals("spouse.age", (errors.getFieldErrors().get(3)).getField()); assertTrue(errors.hasFieldErrors("age")); assertEquals(2, errors.getFieldErrorCount("age")); assertEquals("validation.TOO_YOUNG", errors.getFieldError("age").getCode()); - assertEquals("validation.TOO_YOUNG", ((FieldError) errors.getFieldErrors("age").get(0)).getCode()); - assertEquals("tb", ((FieldError) errors.getFieldErrors("age").get(0)).getObjectName()); - assertEquals("age", ((FieldError) errors.getFieldErrors("age").get(0)).getField()); - assertEquals(new Integer(0), ((FieldError) errors.getFieldErrors("age").get(0)).getRejectedValue()); - assertEquals("validation.AGE_NOT_ODD", ((FieldError) errors.getFieldErrors("age").get(1)).getCode()); + assertEquals("validation.TOO_YOUNG", (errors.getFieldErrors("age").get(0)).getCode()); + assertEquals("tb", (errors.getFieldErrors("age").get(0)).getObjectName()); + assertEquals("age", (errors.getFieldErrors("age").get(0)).getField()); + assertEquals(new Integer(0), (errors.getFieldErrors("age").get(0)).getRejectedValue()); + assertEquals("validation.AGE_NOT_ODD", (errors.getFieldErrors("age").get(1)).getCode()); assertTrue(errors.hasFieldErrors("name")); assertEquals(1, errors.getFieldErrorCount("name")); @@ -782,14 +812,14 @@ public class DataBinderTests extends TestCase { assertEquals("validation.NOT_ROD.name", errors.getFieldError("name").getCodes()[1]); assertEquals("validation.NOT_ROD.java.lang.String", errors.getFieldError("name").getCodes()[2]); assertEquals("validation.NOT_ROD", errors.getFieldError("name").getCodes()[3]); - assertEquals("name", ((FieldError) errors.getFieldErrors("name").get(0)).getField()); - assertEquals(null, ((FieldError) errors.getFieldErrors("name").get(0)).getRejectedValue()); + assertEquals("name", (errors.getFieldErrors("name").get(0)).getField()); + assertEquals(null, (errors.getFieldErrors("name").get(0)).getRejectedValue()); assertTrue(errors.hasFieldErrors("spouse.age")); assertEquals(1, errors.getFieldErrorCount("spouse.age")); assertEquals("validation.TOO_YOUNG", errors.getFieldError("spouse.age").getCode()); - assertEquals("tb", ((FieldError) errors.getFieldErrors("spouse.age").get(0)).getObjectName()); - assertEquals(new Integer(0), ((FieldError) errors.getFieldErrors("spouse.age").get(0)).getRejectedValue()); + assertEquals("tb", (errors.getFieldErrors("spouse.age").get(0)).getObjectName()); + assertEquals(new Integer(0), (errors.getFieldErrors("spouse.age").get(0)).getRejectedValue()); } public void testValidatorWithNestedObjectNull() { @@ -806,8 +836,8 @@ public class DataBinderTests extends TestCase { assertTrue(errors.hasFieldErrors("spouse")); assertEquals(1, errors.getFieldErrorCount("spouse")); assertEquals("SPOUSE_NOT_AVAILABLE", errors.getFieldError("spouse").getCode()); - assertEquals("tb", ((FieldError) errors.getFieldErrors("spouse").get(0)).getObjectName()); - assertEquals(null, ((FieldError) errors.getFieldErrors("spouse").get(0)).getRejectedValue()); + assertEquals("tb", (errors.getFieldErrors("spouse").get(0)).getObjectName()); + assertEquals(null, (errors.getFieldErrors("spouse").get(0)).getRejectedValue()); } public void testNestedValidatorWithoutNestedPath() { @@ -820,7 +850,7 @@ public class DataBinderTests extends TestCase { assertTrue(errors.hasGlobalErrors()); assertEquals(1, errors.getGlobalErrorCount()); assertEquals("SPOUSE_NOT_AVAILABLE", errors.getGlobalError().getCode()); - assertEquals("tb", ((ObjectError) errors.getGlobalErrors().get(0)).getObjectName()); + assertEquals("tb", (errors.getGlobalErrors().get(0)).getObjectName()); } public void testBindingStringArrayToIntegerSet() { diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java index eec9d530995..26d3bae6de7 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import org.springframework.core.GenericTypeResolver; import org.springframework.core.convert.ConversionService; @@ -58,6 +59,28 @@ public class GenericConversionService implements ConversionService, ConverterReg private final Map> sourceTypeConverters = new HashMap>(); + /** + * Registers the converters in the set provided. + * JavaBean-friendly alternative to calling {@link #add(Converter)}. + * @see #add(Converter) + */ + public void setConverters(Set converters) { + for (Converter converter : converters) { + add(converter); + } + } + + /** + * Registers the converters in the set provided. + * JavaBean-friendly alternative to calling {@link #add(ConverterFactory)}. + * @see #add(ConverterFactory) + */ + public void setConverterFactories(Set converters) { + for (ConverterFactory converterFactory : converters) { + add(converterFactory); + } + } + /** * Set the parent of this conversion service. This is optional. */ @@ -100,16 +123,6 @@ public class GenericConversionService implements ConversionService, ConverterReg sourceMap.remove(targetType); } - public void removeConverterFactory(ConverterFactory converter) { - List typeInfo = getRequiredTypeInfo(converter); - Class sourceType = (Class) typeInfo.get(0); - Class targetType = (Class) typeInfo.get(1); - Map sourceMap = getSourceMap(sourceType); - ConverterFactory existing = (ConverterFactory) sourceMap.get(targetType); - if (converter == existing) { - sourceMap.remove(targetType); - } - } // implementing ConversionService @@ -444,4 +457,4 @@ public class GenericConversionService implements ConversionService, ConverterReg } } -} \ No newline at end of file +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java index 41c762370f7..ed076edf888 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java @@ -16,11 +16,11 @@ package org.springframework.web.servlet.mvc.annotation; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; -import java.io.IOException; import java.lang.reflect.Method; import java.security.Principal; import java.util.ArrayList; @@ -33,7 +33,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.ServletException; import javax.servlet.ServletRequest; @@ -54,7 +53,6 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; -import org.springframework.http.converter.BufferedImageHttpMessageConverter; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; @@ -72,9 +70,9 @@ import org.springframework.util.ObjectUtils; import org.springframework.util.PathMatcher; import org.springframework.util.StringUtils; import org.springframework.validation.support.BindingAwareModelMap; +import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpSessionRequiredException; -import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.bind.WebDataBinder; @@ -119,11 +117,11 @@ import org.springframework.web.util.WebUtils; * * @author Juergen Hoeller * @author Arjen Poutsma + * @since 2.5 * @see #setPathMatcher * @see #setMethodNameResolver * @see #setWebBindingInitializer * @see #setSessionAttributeStore - * @since 2.5 */ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter { @@ -165,19 +163,20 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen new ConcurrentHashMap, ServletHandlerMethodResolver>(); private HttpMessageConverter[] messageConverters = - new HttpMessageConverter[]{new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(), + new HttpMessageConverter[] {new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(), new FormHttpMessageConverter(), new SourceHttpMessageConverter()}; + public AnnotationMethodHandlerAdapter() { // no restriction of HTTP methods by default super(false); } + /** * Set if URL lookup should always use the full path within the current servlet context. Else, the path within the * current servlet mapping is used if applicable (that is, in the case of a ".../*" servlet mapping in web.xml). *

Default is "false". - * * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath */ public void setAlwaysUseFullPath(boolean alwaysUseFullPath) { @@ -188,7 +187,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen * Set if context path and request URI should be URL-decoded. Both are returned undecoded by the Servlet API, in * contrast to the servlet path.

Uses either the request encoding or the default encoding according to the Servlet * spec (ISO-8859-1). - * * @see org.springframework.web.util.UrlPathHelper#setUrlDecode */ public void setUrlDecode(boolean urlDecode) { @@ -207,7 +205,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen /** * Set the PathMatcher implementation to use for matching URL paths against registered URL patterns. Default is * AntPathMatcher. - * * @see org.springframework.util.AntPathMatcher */ public void setPathMatcher(PathMatcher pathMatcher) { @@ -217,7 +214,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen /** * Set the MethodNameResolver to use for resolving default handler methods (carrying an empty - * @RequestMapping annotation).

Will only kick in when the handler method cannot be resolved uniquely + * @RequestMapping annotation). + *

Will only kick in when the handler method cannot be resolved uniquely * through the annotation metadata already. */ public void setMethodNameResolver(MethodNameResolver methodNameResolver) { @@ -225,15 +223,16 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen } /** - * Specify a WebBindingInitializer which will apply pre-configured configuration to every DataBinder that this - * controller uses. + * Specify a WebBindingInitializer which will apply pre-configured configuration to every + * DataBinder that this controller uses. */ public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) { this.webBindingInitializer = webBindingInitializer; } /** - * Specify the strategy to store session attributes with.

Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore}, + * Specify the strategy to store session attributes with. + *

Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore}, * storing session attributes in the HttpSession, using the same attribute name as in the model. */ public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) { @@ -243,10 +242,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen /** * Cache content produced by @SessionAttributes annotated handlers for the given number of seconds. - * Default is 0, preventing caching completely.

In contrast to the "cacheSeconds" property which will apply to all + * Default is 0, preventing caching completely. + *

In contrast to the "cacheSeconds" property which will apply to all * general handlers (but not to @SessionAttributes annotated handlers), this setting will apply to * @SessionAttributes annotated handlers only. - * * @see #setCacheSeconds * @see org.springframework.web.bind.annotation.SessionAttributes */ @@ -255,15 +254,17 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen } /** - * Set if controller execution should be synchronized on the session, to serialize parallel invocations from the same - * client.

More specifically, the execution of each handler method will get synchronized if this flag is "true". The - * best available session mutex will be used for the synchronization; ideally, this will be a mutex exposed by - * HttpSessionMutexListener.

The session mutex is guaranteed to be the same object during the entire lifetime of the - * session, available under the key defined by the SESSION_MUTEX_ATTRIBUTE constant. It serves as a safe - * reference to synchronize on for locking on the current session.

In many cases, the HttpSession reference itself - * is a safe mutex as well, since it will always be the same object reference for the same active logical session. - * However, this is not guaranteed across different servlet containers; the only 100% safe way is a session mutex. - * + * Set if controller execution should be synchronized on the session, to serialize + * parallel invocations from the same client. + *

More specifically, the execution of each handler method will get synchronized if this + * flag is "true". The best available session mutex will be used for the synchronization; + * ideally, this will be a mutex exposed by HttpSessionMutexListener. + *

The session mutex is guaranteed to be the same object during the entire lifetime of the + * session, available under the key defined by the SESSION_MUTEX_ATTRIBUTE constant. + * It serves as a safe reference to synchronize on for locking on the current session. + *

In many cases, the HttpSession reference itself a safe mutex as well, since it will + * always be the same object reference for the same active logical session. However, this is + * not guaranteed across different servlet containers; the only 100% safe way is a session mutex. * @see org.springframework.web.util.HttpSessionMutexListener * @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession) */ @@ -273,7 +274,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen /** * Set the ParameterNameDiscoverer to use for resolving method parameter names if needed (e.g. for default attribute - * names).

Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}. + * names). + *

Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}. */ public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { this.parameterNameDiscoverer = parameterNameDiscoverer; @@ -316,10 +318,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen * responses. */ public void setMessageConverters(HttpMessageConverter[] messageConverters) { - Assert.notEmpty(messageConverters, "'messageConverters' must not be empty"); this.messageConverters = messageConverters; } + public boolean supports(Object handler) { return getMethodResolver(handler).hasHandlerMethods(); } @@ -372,12 +374,12 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen } /** - * Template method for creating a new ServletRequestDataBinder instance.

The default implementation creates a - * standard ServletRequestDataBinder. This can be overridden for custom ServletRequestDataBinder subclasses. - * + * Template method for creating a new ServletRequestDataBinder instance. + *

The default implementation creates a standard ServletRequestDataBinder. + * This can be overridden for custom ServletRequestDataBinder subclasses. * @param request current HTTP request - * @param target the target object to bind onto (or null if the binder is just used to convert a plain - * parameter value) + * @param target the target object to bind onto (or null + * if the binder is just used to convert a plain parameter value) * @param objectName the objectName of the target object * @return the ServletRequestDataBinder instance to use * @throws Exception in case of invalid state or arguments @@ -390,7 +392,9 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen return new ServletRequestDataBinder(target, objectName); } - /** Build a HandlerMethodResolver for the given handler type. */ + /** + * Build a HandlerMethodResolver for the given handler type. + */ private ServletHandlerMethodResolver getMethodResolver(Object handler) { Class handlerClass = ClassUtils.getUserClass(handler); ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass); @@ -401,7 +405,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen return resolver; } - /** Servlet-specific subclass of {@link HandlerMethodResolver}. */ + + /** + * Servlet-specific subclass of {@link HandlerMethodResolver}. + */ private class ServletHandlerMethodResolver extends HandlerMethodResolver { private ServletHandlerMethodResolver(Class handlerType) { @@ -587,7 +594,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen } } - /** Servlet-specific subclass of {@link HandlerMethodInvoker}. */ + + /** + * Servlet-specific subclass of {@link HandlerMethodInvoker}. + */ private class ServletHandlerMethodInvoker extends HandlerMethodInvoker { private boolean responseArgumentUsed = false; @@ -780,16 +790,18 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse()); Class returnValueType = returnValue.getClass(); List allSupportedMediaTypes = new ArrayList(); - for (HttpMessageConverter messageConverter : messageConverters) { - allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); - if (messageConverter.supports(returnValueType)) { - for (Object o : messageConverter.getSupportedMediaTypes()) { - MediaType supportedMediaType = (MediaType) o; - for (MediaType acceptedMediaType : acceptedMediaTypes) { - if (supportedMediaType.includes(acceptedMediaType)) { - messageConverter.write(returnValue, outputMessage); - responseArgumentUsed = true; - return; + if (messageConverters != null) { + for (HttpMessageConverter messageConverter : messageConverters) { + allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); + if (messageConverter.supports(returnValueType)) { + for (Object o : messageConverter.getSupportedMediaTypes()) { + MediaType supportedMediaType = (MediaType) o; + for (MediaType acceptedMediaType : acceptedMediaTypes) { + if (supportedMediaType.includes(acceptedMediaType)) { + messageConverter.write(returnValue, outputMessage); + responseArgumentUsed = true; + return; + } } } } @@ -799,6 +811,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen } } + static class RequestMappingInfo { String[] paths = new String[0]; diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java index 0bfffa8f36e..a402872276b 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java @@ -5,7 +5,7 @@ * 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 + * http://www.apache.org/licensesch/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, @@ -16,14 +16,6 @@ package org.springframework.web.servlet.mvc.annotation; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.io.IOException; import java.io.Serializable; import java.io.Writer; @@ -43,7 +35,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; - import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -52,7 +43,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import static org.junit.Assert.*; import org.junit.Test; + import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.aop.interceptor.SimpleTraceInterceptor; import org.springframework.aop.support.DefaultPointcutAdvisor; @@ -80,6 +73,8 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.ExtendedModelMap; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; +import org.springframework.ui.format.support.GenericFormatterRegistry; +import org.springframework.ui.format.date.DateFormatter; import org.springframework.util.SerializationTestUtils; import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; @@ -95,6 +90,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.bind.support.WebArgumentResolver; import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.context.WebApplicationContext; @@ -405,7 +401,7 @@ public class ServletAnnotationControllerTests { } @Test - public void commandProvidingFormController() throws Exception { + public void commandProvidingFormControllerWithCustomEditor() throws Exception { @SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() { @Override protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { @@ -431,6 +427,37 @@ public class ServletAnnotationControllerTests { assertEquals("myView-String:myDefaultName-typeMismatch-tb1-myOriginalValue", response.getContentAsString()); } + @Test + public void commandProvidingFormControllerWithFormatter() throws Exception { + @SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() { + @Override + protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { + GenericWebApplicationContext wac = new GenericWebApplicationContext(); + wac.registerBeanDefinition("controller", + new RootBeanDefinition(MyCommandProvidingFormController.class)); + wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)); + RootBeanDefinition registryDef = new RootBeanDefinition(GenericFormatterRegistry.class); + registryDef.getPropertyValues().addPropertyValue("formatters", new DateFormatter("yyyy-MM-dd")); + RootBeanDefinition initializerDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class); + initializerDef.getPropertyValues().addPropertyValue("formatterRegistry", registryDef); + RootBeanDefinition adapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class); + adapterDef.getPropertyValues().addPropertyValue("webBindingInitializer", initializerDef); + wac.registerBeanDefinition("handlerAdapter", adapterDef); + wac.refresh(); + return wac; + } + }; + servlet.init(new MockServletConfig()); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do"); + request.addParameter("defaultName", "myDefaultName"); + request.addParameter("age", "value2"); + request.addParameter("date", "2007-10-02"); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("myView-String:myDefaultName-typeMismatch-tb1-myOriginalValue", response.getContentAsString()); + } + @Test public void typedCommandProvidingFormController() throws Exception { @SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() { diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java index 0f8628bdf8c..6137a4adc0c 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java +++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java @@ -75,8 +75,8 @@ import org.springframework.web.multipart.MultipartRequest; * * @author Juergen Hoeller * @author Arjen Poutsma - * @see #invokeHandlerMethod * @since 2.5.2 + * @see #invokeHandlerMethod */ public class HandlerMethodInvoker { @@ -93,18 +93,17 @@ public class HandlerMethodInvoker { private final WebArgumentResolver[] customArgumentResolvers; - private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus(); - private final HttpMessageConverter[] messageConverters; + private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus(); + public HandlerMethodInvoker(HandlerMethodResolver methodResolver) { this(methodResolver, null); } public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer) { - this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null, new WebArgumentResolver[0], - new HttpMessageConverter[0]); + this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null, null, null); } public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer, @@ -379,7 +378,7 @@ public class HandlerMethodInvoker { MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception { - Class paramType = methodParam.getParameterType(); + Class paramType = methodParam.getParameterType(); if (paramName.length() == 0) { paramName = getRequiredParameterName(methodParam); } @@ -411,7 +410,7 @@ public class HandlerMethodInvoker { MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception { - Class paramType = methodParam.getParameterType(); + Class paramType = methodParam.getParameterType(); if (headerName.length() == 0) { headerName = getRequiredParameterName(methodParam); } @@ -455,12 +454,14 @@ public class HandlerMethodInvoker { "Cannot extract @RequestBody parameter (" + builder.toString() + "): no Content-Type found"); } List allSupportedMediaTypes = new ArrayList(); - for (HttpMessageConverter messageConverter : messageConverters) { - allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); - if (messageConverter.supports(paramType)) { - for (MediaType supportedMediaType : messageConverter.getSupportedMediaTypes()) { - if (supportedMediaType.includes(contentType)) { - return messageConverter.read(paramType, inputMessage); + if (this.messageConverters != null) { + for (HttpMessageConverter messageConverter : this.messageConverters) { + allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); + if (messageConverter.supports(paramType)) { + for (MediaType supportedMediaType : messageConverter.getSupportedMediaTypes()) { + if (supportedMediaType.includes(contentType)) { + return messageConverter.read(paramType, inputMessage); + } } } } @@ -480,7 +481,7 @@ public class HandlerMethodInvoker { MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception { - Class paramType = methodParam.getParameterType(); + Class paramType = methodParam.getParameterType(); if (cookieName.length() == 0) { cookieName = getRequiredParameterName(methodParam); } @@ -512,7 +513,7 @@ public class HandlerMethodInvoker { private Object resolvePathVariable(String pathVarName, MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception { - Class paramType = methodParam.getParameterType(); + Class paramType = methodParam.getParameterType(); if (pathVarName.length() == 0) { pathVarName = getRequiredParameterName(methodParam); } @@ -565,8 +566,8 @@ public class HandlerMethodInvoker { if ("".equals(name)) { name = Conventions.getVariableNameForParameter(methodParam); } - Class paramType = methodParam.getParameterType(); - Object bindObject = null; + Class paramType = methodParam.getParameterType(); + Object bindObject; if (implicitModel.containsKey(name)) { bindObject = implicitModel.get(name); } diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java index 27a1361b0d2..cb4c09b3ace 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java +++ b/org.springframework.web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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. @@ -17,6 +17,7 @@ package org.springframework.web.bind.support; import org.springframework.beans.PropertyEditorRegistrar; +import org.springframework.ui.format.FormatterRegistry; import org.springframework.validation.BindingErrorProcessor; import org.springframework.validation.MessageCodesResolver; import org.springframework.web.bind.WebDataBinder; @@ -42,6 +43,8 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer private BindingErrorProcessor bindingErrorProcessor; + private FormatterRegistry formatterRegistry; + private PropertyEditorRegistrar[] propertyEditorRegistrars; @@ -91,24 +94,35 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer } /** - * Specify a single PropertyEditorRegistrar to be applied - * to every DataBinder that this controller uses. + * Specify a FormatterRegistry which will apply to every DataBinder. + */ + public final void setFormatterRegistry(FormatterRegistry formatterRegistry) { + this.formatterRegistry = formatterRegistry; + } + + /** + * Return a FormatterRegistry which will apply to every DataBinder. + */ + public final FormatterRegistry getFormatterRegistry() { + return this.formatterRegistry; + } + + /** + * Specify a single PropertyEditorRegistrar to be applied to every DataBinder. */ public final void setPropertyEditorRegistrar(PropertyEditorRegistrar propertyEditorRegistrar) { this.propertyEditorRegistrars = new PropertyEditorRegistrar[] {propertyEditorRegistrar}; } /** - * Specify multiple PropertyEditorRegistrars to be applied - * to every DataBinder that this controller uses. + * Specify multiple PropertyEditorRegistrars to be applied to every DataBinder. */ public final void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) { this.propertyEditorRegistrars = propertyEditorRegistrars; } /** - * Return the PropertyEditorRegistrars to be applied - * to every DataBinder that this controller uses. + * Return the PropertyEditorRegistrars to be applied to every DataBinder. */ public final PropertyEditorRegistrar[] getPropertyEditorRegistrars() { return this.propertyEditorRegistrars; @@ -125,9 +139,12 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer if (this.bindingErrorProcessor != null) { binder.setBindingErrorProcessor(this.bindingErrorProcessor); } + if (this.formatterRegistry != null) { + binder.setFormatterRegistry(this.formatterRegistry); + } if (this.propertyEditorRegistrars != null) { - for (int i = 0; i < this.propertyEditorRegistrars.length; i++) { - this.propertyEditorRegistrars[i].registerCustomEditors(binder); + for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) { + propertyEditorRegistrar.registerCustomEditors(binder); } } }