Browse Source

support for default "conversionService" bean in an ApplicationContext; revised formatting package, now integrated with DataBinder and AnnotationMethodHandlerAdapter; revised AccessControlContext access from BeanFactory

conversation
Juergen Hoeller 16 years ago
parent
commit
fee838a65e
  1. 20
      org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java
  2. 9
      org.springframework.beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java
  3. 15
      org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessor.java
  4. 17
      org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java
  5. 7
      org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java
  6. 58
      org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java
  7. 6
      org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java
  8. 5
      org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java
  9. 22
      org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java
  10. 7
      org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java
  11. 14
      org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
  12. 43
      org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java
  13. 4
      org.springframework.context/src/main/java/org/springframework/ui/context/support/UiApplicationContextUtils.java
  14. 20
      org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java
  15. 9
      org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java
  16. 11
      org.springframework.context/src/main/java/org/springframework/ui/format/Formatter.java
  17. 12
      org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java
  18. 72
      org.springframework.context/src/main/java/org/springframework/ui/format/date/DateFormatter.java
  19. 91
      org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingConversionServiceAdapter.java
  20. 64
      org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingPropertyEditorAdapter.java
  21. 155
      org.springframework.context/src/main/java/org/springframework/ui/format/support/GenericFormatterRegistry.java
  22. 6
      org.springframework.context/src/main/java/org/springframework/ui/format/support/package-info.java
  23. 2
      org.springframework.context/src/main/java/org/springframework/validation/AbstractBindingResult.java
  24. 47
      org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java
  25. 6
      org.springframework.context/src/main/java/org/springframework/validation/BindingResult.java
  26. 40
      org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java
  27. 1
      org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java
  28. 144
      org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java
  29. 35
      org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
  30. 101
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
  31. 49
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java
  32. 35
      org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
  33. 35
      org.springframework.web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java

20
org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java

@ -36,9 +36,11 @@ import java.util.Set; @@ -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 @@ -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);

9
org.springframework.beans/src/main/java/org/springframework/beans/DirectFieldAccessor.java

@ -22,6 +22,7 @@ import java.util.HashMap; @@ -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 { @@ -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);

15
org.springframework.beans/src/main/java/org/springframework/beans/PropertyAccessor.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 { @@ -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 <code>null</code> 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

17
org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java

@ -20,6 +20,7 @@ import java.beans.PropertyDescriptor; @@ -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 { @@ -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 { @@ -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) {

7
org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java

@ -17,6 +17,7 @@ @@ -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 @@ -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 <code>null</code>)
*/
AccessControlContext getAccessControlContext();
/**
* Copy all relevant configuration from the given other factory.
* <p>Should include all standard configuration settings as well as

58
org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java

@ -150,6 +150,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp @@ -150,6 +150,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
/** Map from scope identifier String to corresponding Scope */
private final Map<String, Scope> scopes = new HashMap<String, Scope>();
/** Security context used when running with a SecurityManager */
private SecurityContextProvider securityContextProvider;
/** Map from bean name to merged RootBeanDefinition */
private final Map<String, RootBeanDefinition> mergedBeanDefinitions =
new ConcurrentHashMap<String, RootBeanDefinition>();
@ -161,9 +164,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp @@ -161,9 +164,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<Object>("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 @@ -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 @@ -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 @@ -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 @@ -1526,4 +1517,5 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
*/
protected abstract Object createBean(String beanName, RootBeanDefinition mbd, Object[] args)
throws BeanCreationException;
}

6
org.springframework.beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java

@ -212,10 +212,10 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg @@ -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();
}
}
}

5
org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SecurityContextProvider.java

@ -20,15 +20,16 @@ import java.security.AccessControlContext; @@ -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();
}

22
org.springframework.beans/src/main/java/org/springframework/beans/factory/support/SimpleSecurityContextProvider.java

@ -20,18 +20,19 @@ import java.security.AccessControlContext; @@ -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 <code>SimpleSecurityContextProvider</code> instance.
*
* The security context will be retrieved on each call from the current
* Construct a new <code>SimpleSecurityContextProvider</code> instance.
* <p>The security context will be retrieved on each call from the current
* thread.
*/
public SimpleSecurityContextProvider() {
@ -39,20 +40,19 @@ public class SimpleSecurityContextProvider implements SecurityContextProvider { @@ -39,20 +40,19 @@ public class SimpleSecurityContextProvider implements SecurityContextProvider {
}
/**
* Constructs a new <code>SimpleSecurityContextProvider</code> instance.
*
* If the given control context is null, the security context will be
* Construct a new <code>SimpleSecurityContextProvider</code> instance.
* <p>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 <code>null</code>)
* @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());
}
}

7
org.springframework.context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java

@ -44,6 +44,13 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life @@ -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

14
org.springframework.context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java

@ -63,6 +63,7 @@ import org.springframework.context.weaving.LoadTimeWeaverAwareProcessor; @@ -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 @@ -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 @@ -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.

43
org.springframework.context/src/main/java/org/springframework/context/support/ApplicationContextAwareProcessor.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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; @@ -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<Object>() {
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<Object>() {
public Object run() {
doProcess(bean);
return null;
}
}, acc);
}
}, acc);
}
else {
doProcess(bean);
@ -109,4 +103,5 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor { @@ -109,4 +103,5 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String name) {
return bean;
}
}
}

4
org.springframework.context/src/main/java/org/springframework/ui/context/support/UiApplicationContextUtils.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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;

20
org.springframework.context/src/main/java/org/springframework/ui/format/AnnotationFormatterFactory.java

@ -1,25 +1,30 @@ @@ -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 <code>CurrencyAnnotationFormatterFactory</code> might create a <code>Formatter</code> that formats a <code>BigDecimal</code> value set on a property annotated with <code>@CurrencyFormat</code>.
* A factory that creates {@link Formatter formatters} to format property values on properties
* annotated with a particular format {@link Annotation}.
*
* <p>For example, a <code>CurrencyAnnotationFormatterFactory</code> might create a <code>Formatter</code>
* that formats a <code>BigDecimal</code> value set on a property annotated with <code>@CurrencyFormat</code>.
*
* @author Keith Donald
* @since 3.0
* @param <A> The type of Annotation this factory uses to create Formatter instances
@ -34,4 +39,5 @@ public interface AnnotationFormatterFactory<A extends Annotation, T> { @@ -34,4 +39,5 @@ public interface AnnotationFormatterFactory<A extends Annotation, T> {
* @return the Formatter to use to format values of properties annotated with the annotation.
*/
Formatter<T> getFormatter(A annotation);
}
}

9
org.springframework.context/src/main/java/org/springframework/ui/format/Formatted.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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; @@ -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; @@ -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();
}

11
org.springframework.context/src/main/java/org/springframework/ui/format/Formatter.java

@ -1,18 +1,19 @@ @@ -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; @@ -20,6 +21,7 @@ import java.util.Locale;
/**
* Formats objects of type T for display.
*
* @author Keith Donald
* @since 3.0
* @param <T> the type of object this formatter can format
@ -42,4 +44,5 @@ public interface Formatter<T> { @@ -42,4 +44,5 @@ public interface Formatter<T> {
* @throws ParseException when a parse exception occurs
*/
T parse(String formatted, Locale locale) throws ParseException;
}

12
org.springframework.context/src/main/java/org/springframework/ui/format/FormatterRegistry.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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; @@ -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 { @@ -42,21 +44,19 @@ public interface FormatterRegistry {
* On parse, the decorator first delegates to the formatter to parse a &lt;T&gt;, then coerses the parsed value to type.
* @param type the object type
* @param targetFormatter the target formatter
* @param <T> the type of object the target formatter formats
*/
<T> void add(Class<?> type, Formatter<T> 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
*/
<A extends Annotation, T> void add(AnnotationFormatterFactory<A, T> factory);
void add(AnnotationFormatterFactory<?, ?> factory);
/**
* Get the Formatter for the type descriptor.
* @return the Formatter, or <code>null</code> if no suitable one is registered
*/
@SuppressWarnings("unchecked")
Formatter getFormatter(TypeDescriptor type);
Formatter<Object> getFormatter(TypeDescriptor type);
}

72
org.springframework.context/src/main/java/org/springframework/ui/format/date/DateFormatter.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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; @@ -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<Date> {
private static Log logger = LogFactory.getLog(DateFormatter.class);
private String pattern;
private int style = DateFormat.DEFAULT;
/**
* The default date pattern.
* Create a new default DateFormatter.
*/
private static final String DEFAULT_PATTERN = "yyyy-MM-dd";
public DateFormatter() {
}
/**
* Create a new DateFormatter for the given date pattern.
*/
public DateFormatter(String pattern) {
this.pattern = pattern;
}
private String pattern;
/**
* 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
* Set the pattern to use to format date values.
* <p>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.
* <p>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<Date> { @@ -66,23 +90,17 @@ public final class DateFormatter implements Formatter<Date> {
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");
}
return format;
}
private String determinePattern(String pattern) {
return pattern != null ? pattern : DEFAULT_PATTERN;
protected DateFormat getDateFormat(Locale locale) {
DateFormat dateFormat;
if (this.pattern != null) {
dateFormat = new SimpleDateFormat(this.pattern, locale);
}
else {
dateFormat = DateFormat.getDateInstance(this.style, locale);
}
dateFormat.setLenient(false);
return dateFormat;
}
}
}

91
org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingConversionServiceAdapter.java

@ -0,0 +1,91 @@ @@ -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> T convert(Object source, Class<T> 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);
}
}

64
org.springframework.context/src/main/java/org/springframework/ui/format/support/FormattingPropertyEditorAdapter.java

@ -0,0 +1,64 @@ @@ -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<Object> formatter;
/**
* Create a new FormattingPropertyEditorAdapter for the given Formatter.
* @param formatter the Formatter to wrap
*/
public FormattingPropertyEditorAdapter(Formatter<Object> 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());
}
}

155
org.springframework.context/src/main/java/org/springframework/ui/format/GenericFormatterRegistry.java → org.springframework.context/src/main/java/org/springframework/ui/format/support/GenericFormatterRegistry.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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<Class, Formatter> typeFormatters = new ConcurrentHashMap<Class, Formatter>();
private final Map<Class, Formatter> typeFormatters = new ConcurrentHashMap<Class, Formatter>();
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
private final Map<Class, AnnotationFormatterFactory> annotationFormatters =
new ConcurrentHashMap<Class, AnnotationFormatterFactory>();
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<Formatter<?>> 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<Class<?>, Formatter<?>> formatters) {
public void setFormatterMap(Map<Class<?>, Formatter<?>> formatters) {
for (Map.Entry<Class<?>, Formatter<?>> entry : formatters.entrySet()) {
add(entry.getKey(), entry.getValue());
}
@ -85,36 +97,103 @@ public class GenericFormatterRegistry implements FormatterRegistry { @@ -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.
* <p>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 <T> void add(Formatter<T> formatter) {
typeFormatters.put(getFormattedObjectType(formatter.getClass()), formatter);
this.typeFormatters.put(getFormattedObjectType(formatter.getClass()), formatter);
}
public <T> void add(Class<?> type, Formatter<T> 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 <A extends Annotation, T> void add(AnnotationFormatterFactory<A, T> 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<Object> getFormatter(TypeDescriptor type) {
Assert.notNull(type, "The TypeDescriptor is required");
Formatter formatter = getAnnotationFormatter(type);
Formatter<Object> 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 { @@ -175,7 +254,8 @@ public class GenericFormatterRegistry implements FormatterRegistry {
+ factoryClass.getName() + "]; does the factory parameterize the <A> 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 { @@ -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 { @@ -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 { @@ -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 { @@ -268,4 +353,4 @@ public class GenericFormatterRegistry implements FormatterRegistry {
}
}
}

6
org.springframework.context/src/main/java/org/springframework/ui/format/support/package-info.java

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
/**
* Support classes for the formatting package, providing
* common implementations as well as adapters.
*/
package org.springframework.ui.format;

2
org.springframework.context/src/main/java/org/springframework/validation/AbstractBindingResult.java

@ -27,6 +27,7 @@ import java.util.Map; @@ -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 @@ -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;
}

47
org.springframework.context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 @@ -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 @@ -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<Object> 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 @@ -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 <code>null</code>
*/
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 @@ -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<Object> 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

6
org.springframework.context/src/main/java/org/springframework/validation/BindingResult.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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 <code>null</code> 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
* <code>null</code> if looking for an editor for all properties of the given type
* @param valueType the type of the property (can be <code>null</code> if a property
* is given but should be specified in any case for consistency checking)
* @return the registered editor, or <code>null</code> if none
*/
PropertyEditor findEditor(String field, Class valueType);

40
org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java

@ -35,6 +35,9 @@ import org.springframework.beans.SimpleTypeConverter; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 <code>null</code>), 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

1
org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java

@ -19,6 +19,7 @@ import org.springframework.core.convert.TypeDescriptor; @@ -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 {

144
org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java

@ -17,6 +17,7 @@ @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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() {

35
org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java

@ -25,6 +25,7 @@ import java.util.HashMap; @@ -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 @@ -58,6 +59,28 @@ public class GenericConversionService implements ConversionService, ConverterReg
private final Map<Class, Map<Class, Object>> sourceTypeConverters = new HashMap<Class, Map<Class, Object>>();
/**
* Registers the converters in the set provided.
* JavaBean-friendly alternative to calling {@link #add(Converter)}.
* @see #add(Converter)
*/
public void setConverters(Set<Converter> 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<ConverterFactory> 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 @@ -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 @@ -444,4 +457,4 @@ public class GenericConversionService implements ConversionService, ConverterReg
}
}
}
}

101
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java

@ -16,11 +16,11 @@ @@ -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; @@ -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; @@ -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; @@ -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; @@ -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 @@ -165,19 +163,20 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
new ConcurrentHashMap<Class<?>, 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).
* <p>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 @@ -188,7 +187,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
* Set if context path and request URI should be URL-decoded. Both are returned <i>undecoded</i> by the Servlet API, in
* contrast to the servlet path. <p>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 @@ -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 @@ -217,7 +214,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
/**
* Set the MethodNameResolver to use for resolving default handler methods (carrying an empty
* <code>@RequestMapping</code> annotation). <p>Will only kick in when the handler method cannot be resolved uniquely
* <code>@RequestMapping</code> annotation).
* <p>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 @@ -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. <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
* Specify the strategy to store session attributes with.
* <p>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 @@ -243,10 +242,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
/**
* Cache content produced by <code>@SessionAttributes</code> annotated handlers for the given number of seconds.
* Default is 0, preventing caching completely. <p>In contrast to the "cacheSeconds" property which will apply to all
* Default is 0, preventing caching completely.
* <p>In contrast to the "cacheSeconds" property which will apply to all
* general handlers (but not to <code>@SessionAttributes</code> annotated handlers), this setting will apply to
* <code>@SessionAttributes</code> annotated handlers only.
*
* @see #setCacheSeconds
* @see org.springframework.web.bind.annotation.SessionAttributes
*/
@ -255,15 +254,17 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen @@ -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. <p>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. <p>The session mutex is guaranteed to be the same object during the entire lifetime of the
* session, available under the key defined by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a safe
* reference to synchronize on for locking on the current session. <p>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.
* <p>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.
* <p>The session mutex is guaranteed to be the same object during the entire lifetime of the
* session, available under the key defined by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant.
* It serves as a safe reference to synchronize on for locking on the current session.
* <p>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 @@ -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). <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
* names).
* <p>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 @@ -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 @@ -372,12 +374,12 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Template method for creating a new ServletRequestDataBinder instance. <p>The default implementation creates a
* standard ServletRequestDataBinder. This can be overridden for custom ServletRequestDataBinder subclasses.
*
* Template method for creating a new ServletRequestDataBinder instance.
* <p>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 <code>null</code> if the binder is just used to convert a plain
* parameter value)
* @param target the target object to bind onto (or <code>null</code>
* 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 @@ -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 @@ -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 @@ -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 @@ -780,16 +790,18 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
Class<?> returnValueType = returnValue.getClass();
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
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 @@ -799,6 +811,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
static class RequestMappingInfo {
String[] paths = new String[0];

49
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java

@ -5,7 +5,7 @@ @@ -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 @@ @@ -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; @@ -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; @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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() {

35
org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java

@ -75,8 +75,8 @@ import org.springframework.web.multipart.MultipartRequest; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -455,12 +454,14 @@ public class HandlerMethodInvoker {
"Cannot extract @RequestBody parameter (" + builder.toString() + "): no Content-Type found");
}
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
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 { @@ -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 { @@ -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 { @@ -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);
}

35
org.springframework.web/src/main/java/org/springframework/web/bind/support/ConfigurableWebBindingInitializer.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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 @@ -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 @@ -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 @@ -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);
}
}
}

Loading…
Cancel
Save