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

This commit is contained in:
Juergen Hoeller 2009-08-24 13:30:42 +00:00
parent 9f9f2349cd
commit fee838a65e
33 changed files with 846 additions and 320 deletions

View File

@ -36,9 +36,11 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -326,6 +328,24 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
return null; 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) { public boolean isReadableProperty(String propertyName) {
try { try {
PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName); PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);

View File

@ -22,6 +22,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@ -86,6 +87,14 @@ public class DirectFieldAccessor extends AbstractPropertyAccessor {
return null; 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 @Override
public Object getPropertyValue(String propertyName) throws BeansException { public Object getPropertyValue(String propertyName) throws BeansException {
Field field = this.fieldMap.get(propertyName); Field field = this.fieldMap.get(propertyName);

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,6 +18,8 @@ package org.springframework.beans;
import java.util.Map; import java.util.Map;
import org.springframework.core.convert.TypeDescriptor;
/** /**
* Common interface for classes that can access named properties * Common interface for classes that can access named properties
* (such as bean properties of an object or fields in an object) * (such as bean properties of an object or fields in an object)
@ -86,6 +88,17 @@ public interface PropertyAccessor {
*/ */
Class getPropertyType(String propertyName) throws BeansException; 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. * Get the current value of the specified property.
* @param propertyName the name of the property to get the value of * @param propertyName the name of the property to get the value of

View File

@ -20,6 +20,7 @@ import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor; import java.beans.PropertyEditor;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
@ -203,8 +204,18 @@ class TypeConverterDelegate {
convertedValue = convertToTypedMap((Map) convertedValue, propertyName, methodParam); convertedValue = convertToTypedMap((Map) convertedValue, propertyName, methodParam);
} }
else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) { else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
String strValue = ((String) convertedValue).trim(); try {
if (requiredType.isEnum() && "".equals(strValue)) { 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. // It's an empty enum identifier: reset the enum value to null.
return null; return null;
} }
@ -212,7 +223,7 @@ class TypeConverterDelegate {
// with values defined as static fields. Resulting value still needs // with values defined as static fields. Resulting value still needs
// to be checked, hence we don't return it right away. // to be checked, hence we don't return it right away.
try { try {
Field enumField = requiredType.getField(strValue); Field enumField = requiredType.getField(trimmedValue);
convertedValue = enumField.get(null); convertedValue = enumField.get(null);
} }
catch (Throwable ex) { catch (Throwable ex) {

View File

@ -17,6 +17,7 @@
package org.springframework.beans.factory.config; package org.springframework.beans.factory.config;
import java.beans.PropertyEditor; import java.beans.PropertyEditor;
import java.security.AccessControlContext;
import org.springframework.beans.PropertyEditorRegistrar; import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.PropertyEditorRegistry;
@ -249,6 +250,12 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single
*/ */
Scope getRegisteredScope(String scopeName); 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. * Copy all relevant configuration from the given other factory.
* <p>Should include all standard configuration settings as well as * <p>Should include all standard configuration settings as well as

View File

@ -150,6 +150,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
/** Map from scope identifier String to corresponding Scope */ /** Map from scope identifier String to corresponding Scope */
private final Map<String, Scope> scopes = new HashMap<String, 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 */ /** Map from bean name to merged RootBeanDefinition */
private final Map<String, RootBeanDefinition> mergedBeanDefinitions = private final Map<String, RootBeanDefinition> mergedBeanDefinitions =
new ConcurrentHashMap<String, RootBeanDefinition>(); new ConcurrentHashMap<String, RootBeanDefinition>();
@ -161,9 +164,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
private final ThreadLocal<Object> prototypesCurrentlyInCreation = private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<Object>("Prototype beans currently in creation"); 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. * Create a new AbstractBeanFactory.
*/ */
@ -761,6 +761,26 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
return this.scopes.get(scopeName); 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) { public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) {
Assert.notNull(otherFactory, "BeanFactory must not be null"); Assert.notNull(otherFactory, "BeanFactory must not be null");
setBeanClassLoader(otherFactory.getBeanClassLoader()); setBeanClassLoader(otherFactory.getBeanClassLoader());
@ -776,6 +796,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
this.hasDestructionAwareBeanPostProcessors = this.hasDestructionAwareBeanPostProcessors || this.hasDestructionAwareBeanPostProcessors = this.hasDestructionAwareBeanPostProcessors ||
otherAbstractFactory.hasDestructionAwareBeanPostProcessors; otherAbstractFactory.hasDestructionAwareBeanPostProcessors;
this.scopes.putAll(otherAbstractFactory.scopes); this.scopes.putAll(otherAbstractFactory.scopes);
this.securityContextProvider = otherAbstractFactory.securityContextProvider;
} }
else { else {
setTypeConverter(otherFactory.getTypeConverter()); setTypeConverter(otherFactory.getTypeConverter());
@ -1436,37 +1457,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
} }
} }
} }
/**
* {@inheritDoc}
*
* Delegate the creation of the security context to {@link #getSecurityContextProvider()}.
*/
@Override
protected AccessControlContext getAccessControlContext() {
SecurityContextProvider provider = getSecurityContextProvider();
return (provider != null ? provider.getAccessControlContext(): AccessController.getContext());
}
/**
* Return the security context provider for this bean factory.
*
* @return
*/
public SecurityContextProvider getSecurityContextProvider() {
return securityProvider;
}
/**
* Set the security context provider for this bean factory. If a security manager
* is set, interaction with the user code will be executed using the privileged
* of the provided security context.
*
* @param securityProvider
*/
public void setSecurityContextProvider(SecurityContextProvider securityProvider) {
this.securityProvider = securityProvider;
}
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// Abstract methods to be implemented by subclasses // Abstract methods to be implemented by subclasses
@ -1526,4 +1517,5 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
*/ */
protected abstract Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) protected abstract Object createBean(String beanName, RootBeanDefinition mbd, Object[] args)
throws BeanCreationException; throws BeanCreationException;
} }

View File

@ -212,10 +212,10 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
* Returns the security context for this bean factory. If a security manager * 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 * is set, interaction with the user code will be executed using the privileged
* of the security context returned by this method. * of the security context returned by this method.
* * @see AccessController#getContext()
* @return
*/ */
protected AccessControlContext getAccessControlContext() { protected AccessControlContext getAccessControlContext() {
return AccessController.getContext(); return AccessController.getContext();
} }
}
}

View File

@ -20,15 +20,16 @@ import java.security.AccessControlContext;
/** /**
* Provider of the security context of the code running inside the bean factory. * Provider of the security context of the code running inside the bean factory.
* *
* @author Costin Leau * @author Costin Leau
* @since 3.0
*/ */
public interface SecurityContextProvider { public interface SecurityContextProvider {
/** /**
* Provides a security access control context relevant to a bean factory. * Provides a security access control context relevant to a bean factory.
*
* @return bean factory security control context * @return bean factory security control context
*/ */
AccessControlContext getAccessControlContext(); AccessControlContext getAccessControlContext();
} }

View File

@ -20,18 +20,19 @@ import java.security.AccessControlContext;
import java.security.AccessController; import java.security.AccessController;
/** /**
* Simple #SecurityContextProvider implementation. * Simple {@link SecurityContextProvider} implementation.
* *
* @author Costin Leau * @author Costin Leau
* @since 3.0
*/ */
public class SimpleSecurityContextProvider implements SecurityContextProvider { public class SimpleSecurityContextProvider implements SecurityContextProvider {
private final AccessControlContext acc; private final AccessControlContext acc;
/** /**
* Constructs a new <code>SimpleSecurityContextProvider</code> instance. * Construct a new <code>SimpleSecurityContextProvider</code> instance.
* * <p>The security context will be retrieved on each call from the current
* The security context will be retrieved on each call from the current
* thread. * thread.
*/ */
public SimpleSecurityContextProvider() { public SimpleSecurityContextProvider() {
@ -39,20 +40,19 @@ public class SimpleSecurityContextProvider implements SecurityContextProvider {
} }
/** /**
* Constructs a new <code>SimpleSecurityContextProvider</code> instance. * Construct a new <code>SimpleSecurityContextProvider</code> instance.
* * <p>If the given control context is null, the security context will be
* If the given control context is null, the security context will be
* retrieved on each call from the current thread. * retrieved on each call from the current thread.
* * @param acc access control context (can be <code>null</code>)
* @see AccessController#getContext() * @see AccessController#getContext()
* @param acc
* access control context (can be null)
*/ */
public SimpleSecurityContextProvider(AccessControlContext acc) { public SimpleSecurityContextProvider(AccessControlContext acc) {
this.acc = acc; this.acc = acc;
} }
public AccessControlContext getAccessControlContext() { public AccessControlContext getAccessControlContext() {
return (acc == null ? AccessController.getContext() : acc); return (this.acc != null ? acc : AccessController.getContext());
} }
} }

View File

@ -44,6 +44,13 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life
*/ */
String CONFIG_LOCATION_DELIMITERS = ",; \t\n"; 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, * 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 * the context will use a temporary ClassLoader for type matching, in order

View File

@ -63,6 +63,7 @@ import org.springframework.context.weaving.LoadTimeWeaverAwareProcessor;
import org.springframework.core.OrderComparator; import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered; import org.springframework.core.PriorityOrdered;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
@ -367,6 +368,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Register bean processors that intercept bean creation. // Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory); registerBeanPostProcessors(beanFactory);
// Initialize conversion service for this context.
initConversionService();
// Initialize message source for this context. // Initialize message source for this context.
initMessageSource(); initMessageSource();
@ -605,6 +609,16 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
} }
} }
/**
* Initialize the BeanFactory's ConversionService.
*/
protected void initConversionService() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME)) {
beanFactory.setConversionService(beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
}
/** /**
* Initialize the MessageSource. * Initialize the MessageSource.
* Use parent's if none defined in this context. * Use parent's if none defined in this context.

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,9 +22,6 @@ import java.security.PrivilegedAction;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor; 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.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
@ -43,6 +40,7 @@ import org.springframework.context.ResourceLoaderAware;
* underlying bean factory. Applications do not use this directly. * underlying bean factory. Applications do not use this directly.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Costin Leau
* @since 10.10.2003 * @since 10.10.2003
* @see org.springframework.context.ResourceLoaderAware * @see org.springframework.context.ResourceLoaderAware
* @see org.springframework.context.MessageSourceAware * @see org.springframework.context.MessageSourceAware
@ -52,37 +50,33 @@ import org.springframework.context.ResourceLoaderAware;
*/ */
class ApplicationContextAwareProcessor implements BeanPostProcessor { class ApplicationContextAwareProcessor implements BeanPostProcessor {
private final ApplicationContext applicationContext; private final ConfigurableApplicationContext applicationContext;
/** /**
* Create a new ApplicationContextAwareProcessor for the given context. * Create a new ApplicationContextAwareProcessor for the given context.
*/ */
public ApplicationContextAwareProcessor(ApplicationContext applicationContext) { public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
} }
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException { public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
AccessControlContext acc = null; AccessControlContext acc = null;
if (System.getSecurityManager() != null) { if (System.getSecurityManager() != null &&
if (applicationContext instanceof ConfigurableApplicationContext) { (bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
if (factory instanceof AbstractBeanFactory) { acc = this.applicationContext.getBeanFactory().getAccessControlContext();
acc = ((AbstractBeanFactory) factory).getSecurityContextProvider().getAccessControlContext(); }
if (acc != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
doProcess(bean);
return null;
} }
} }, acc);
// 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);
}
} }
else { else {
doProcess(bean); doProcess(bean);
@ -109,4 +103,5 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String name) { public Object postProcessAfterInitialization(Object bean, String name) {
return bean; return bean;
} }
}
}

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -55,7 +55,7 @@ public abstract class UiApplicationContextUtils {
*/ */
public static ThemeSource initThemeSource(ApplicationContext context) { public static ThemeSource initThemeSource(ApplicationContext context) {
if (context.containsLocalBean(THEME_SOURCE_BEAN_NAME)) { 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. // Make ThemeSource aware of parent ThemeSource.
if (context.getParent() instanceof ThemeSource && themeSource instanceof HierarchicalThemeSource) { if (context.getParent() instanceof ThemeSource && themeSource instanceof HierarchicalThemeSource) {
HierarchicalThemeSource hts = (HierarchicalThemeSource) themeSource; HierarchicalThemeSource hts = (HierarchicalThemeSource) themeSource;

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.ui.format; package org.springframework.ui.format;
import java.lang.annotation.Annotation; 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}. * A factory that creates {@link Formatter formatters} to format property values on properties
* 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>. * 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 * @author Keith Donald
* @since 3.0 * @since 3.0
* @param <A> The type of Annotation this factory uses to create Formatter instances * @param <A> The type of Annotation this factory uses to create Formatter instances
@ -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. * @return the Formatter to use to format values of properties annotated with the annotation.
*/ */
Formatter<T> getFormatter(A annotation); Formatter<T> getFormatter(A annotation);
}
}

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.ui.format; package org.springframework.ui.format;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
@ -23,6 +24,7 @@ import java.lang.annotation.Target;
/** /**
* A type that can be formatted as a String for display in a user interface. * A type that can be formatted as a String for display in a user interface.
*
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */
@ -30,9 +32,10 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
public @interface Formatted { public @interface Formatted {
/** /**
* The Formatter that handles the formatting. * The Formatter that handles the formatting for the annotated element.
*/ */
Class<?> value(); Class<?> value();
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.ui.format; package org.springframework.ui.format;
import java.text.ParseException; import java.text.ParseException;
@ -20,6 +21,7 @@ import java.util.Locale;
/** /**
* Formats objects of type T for display. * Formats objects of type T for display.
*
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
* @param <T> the type of object this formatter can format * @param <T> the type of object this formatter can format
@ -42,4 +44,5 @@ public interface Formatter<T> {
* @throws ParseException when a parse exception occurs * @throws ParseException when a parse exception occurs
*/ */
T parse(String formatted, Locale locale) throws ParseException; T parse(String formatted, Locale locale) throws ParseException;
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.ui.format; package org.springframework.ui.format;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
@ -21,6 +22,7 @@ import org.springframework.core.convert.TypeDescriptor;
/** /**
* A shared registry of Formatters. * A shared registry of Formatters.
*
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */
@ -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. * 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 type the object type
* @param targetFormatter the target formatter * @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. * Adds a AnnotationFormatterFactory that returns the Formatter for properties annotated with a specific annotation.
* @param factory the annotation formatter factory * @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. * Get the Formatter for the type descriptor.
* @return the Formatter, or <code>null</code> if no suitable one is registered * @return the Formatter, or <code>null</code> if no suitable one is registered
*/ */
@SuppressWarnings("unchecked") Formatter<Object> getFormatter(TypeDescriptor type);
Formatter getFormatter(TypeDescriptor type);
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.ui.format.date; package org.springframework.ui.format.date;
import java.text.DateFormat; import java.text.DateFormat;
@ -21,37 +22,60 @@ import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ui.format.Formatter; import org.springframework.ui.format.Formatter;
/** /**
* A formatter for {@link java.util.Date} types. * A formatter for {@link java.util.Date} types.
* Allows the configuration of an explicit date pattern and locale. * Allows the configuration of an explicit date pattern and locale.
*
* @author Keith Donald * @author Keith Donald
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
* @see SimpleDateFormat * @see SimpleDateFormat
*/ */
public final class DateFormatter implements Formatter<Date> { public final class DateFormatter implements Formatter<Date> {
private static Log logger = LogFactory.getLog(DateFormatter.class);
/**
* The default date pattern.
*/
private static final String DEFAULT_PATTERN = "yyyy-MM-dd";
private String pattern; private String pattern;
private int style = DateFormat.DEFAULT;
/** /**
* Sets the pattern to use to format date values. * Create a new default DateFormatter.
* If not specified, the default pattern 'yyyy-MM-dd' is used. */
* @param pattern the date formatting pattern public DateFormatter() {
}
/**
* Create a new DateFormatter for the given date pattern.
*/
public DateFormatter(String pattern) {
this.pattern = pattern;
}
/**
* Set the pattern to use to format date values.
* <p>If not specified, DateFormat's default style will be used.
*/ */
public void setPattern(String pattern) { public void setPattern(String pattern) {
this.pattern = 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) { public String format(Date date, Locale locale) {
if (date == null) { if (date == null) {
return ""; return "";
@ -66,23 +90,17 @@ public final class DateFormatter implements Formatter<Date> {
return getDateFormat(locale).parse(formatted); return getDateFormat(locale).parse(formatted);
} }
// internal helpers
private DateFormat getDateFormat(Locale locale) { protected DateFormat getDateFormat(Locale locale) {
DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT, locale); DateFormat dateFormat;
format.setLenient(false); if (this.pattern != null) {
if (format instanceof SimpleDateFormat) { dateFormat = new SimpleDateFormat(this.pattern, locale);
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; else {
dateFormat = DateFormat.getDateInstance(this.style, locale);
}
dateFormat.setLenient(false);
return dateFormat;
} }
private String determinePattern(String pattern) { }
return pattern != null ? pattern : DEFAULT_PATTERN;
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,62 +13,74 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.ui.format;
package org.springframework.ui.format.support;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable; import java.lang.reflect.TypeVariable;
import java.text.ParseException; import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; 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.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.Assert; 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 Keith Donald
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
* @see #setConversionService(ConversionService) * @see #setConversionService(ConversionService)
* @see #add(Formatter) * @see #add(org.springframework.ui.format.Formatter)
* @see #add(Class, Formatter) * @see #add(Class, org.springframework.ui.format.Formatter)
* @see #add(AnnotationFormatterFactory) * @see #add(org.springframework.ui.format.AnnotationFormatterFactory)
*/ */
@SuppressWarnings("unchecked") public class GenericFormatterRegistry implements FormatterRegistry, BeanFactoryAware, Cloneable {
public class GenericFormatterRegistry implements FormatterRegistry {
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 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. * Registers the formatters in the set provided.
* Defaults to a {@link DefaultConversionService}. * JavaBean-friendly alternative to calling {@link #add(Formatter)}.
* @param conversionService the conversion service * @see #add(Formatter)
* @see #add(Class, Formatter)
*/ */
public void setConversionService(ConversionService conversionService) { public void setFormatters(Set<Formatter<?>> formatters) {
this.conversionService = conversionService; for (Formatter<?> formatter : formatters) {
add(formatter);
}
} }
/** /**
* Registers the formatters in the map provided by type. * Registers the formatters in the map provided by type.
* JavaBean-friendly alternative to calling {@link #add(Class, Formatter)}. * JavaBean-friendly alternative to calling {@link #add(Class, Formatter)}.
* @param formatters the formatters map
* @see #add(Class, Formatter) * @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()) { for (Map.Entry<Class<?>, Formatter<?>> entry : formatters.entrySet()) {
add(entry.getKey(), entry.getValue()); add(entry.getKey(), entry.getValue());
} }
@ -85,36 +97,103 @@ public class GenericFormatterRegistry implements FormatterRegistry {
} }
} }
/**
* Specify the type conversion service that will be used to coerce objects to the
* types required for formatting. Defaults to a {@link DefaultConversionService}.
* @see #add(Class, Formatter)
*/
public void setConversionService(ConversionService conversionService) {
Assert.notNull(conversionService, "ConversionService must not be null");
this.conversionService = conversionService;
}
/**
* Return the type conversion service which this FormatterRegistry delegates to.
*/
public ConversionService getConversionService() {
return this.conversionService;
}
/**
* Take the context's default ConversionService if none specified locally.
*/
public void setBeanFactory(BeanFactory beanFactory) {
if (this.conversionService == null &&
beanFactory.containsBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)) {
this.conversionService = beanFactory.getBean(
ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, ConversionService.class);
}
}
// cloning support
/**
* Specify whether this FormatterRegistry is shared, in which case newly
* registered Formatters will be visible to other callers as well.
* <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 // implementing FormatterRegistry
public <T> void add(Formatter<T> formatter) { 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()); 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"); 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"); 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) { public void add(AnnotationFormatterFactory<?, ?> factory) {
annotationFormatters.put(getAnnotationType(factory.getClass()), 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"); Assert.notNull(type, "The TypeDescriptor is required");
Formatter formatter = getAnnotationFormatter(type); Formatter<Object> formatter = getAnnotationFormatter(type);
if (formatter == null) { if (formatter == null) {
formatter = getTypeFormatter(type.getType()); formatter = getTypeFormatter(type.getType());
} }
return formatter; return formatter;
} }
// internal helpers // internal helpers
private Class getFormattedObjectType(Class formatterClass) { private Class getFormattedObjectType(Class formatterClass) {
@ -175,7 +254,8 @@ public class GenericFormatterRegistry implements FormatterRegistry {
+ factoryClass.getName() + "]; does the factory parameterize the <A> generic type?"); + 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(); Annotation[] annotations = type.getAnnotations();
for (Annotation a : annotations) { for (Annotation a : annotations) {
AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType()); AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType());
@ -186,17 +266,19 @@ public class GenericFormatterRegistry implements FormatterRegistry {
return null; return null;
} }
private Formatter<?> getTypeFormatter(Class<?> type) { private Formatter getTypeFormatter(Class<?> type) {
Assert.notNull(type, "The Class of the object to format is required"); Assert.notNull(type, "The Class of the object to format is required");
Formatter formatter = findFormatter(type); Formatter formatter = findFormatter(type);
if (formatter != null) { if (formatter != null) {
Class<?> formattedObjectType = getFormattedObjectType(formatter.getClass()); Class<?> formattedObjectType = getFormattedObjectType(formatter.getClass());
if (type.isAssignableFrom(formattedObjectType)) { if (type.isAssignableFrom(formattedObjectType)) {
return formatter; return formatter;
} else { }
else {
return new ConvertingFormatter(type, formattedObjectType, formatter); return new ConvertingFormatter(type, formattedObjectType, formatter);
} }
} else { }
else {
return getDefaultFormatter(type); return getDefaultFormatter(type);
} }
} }
@ -236,11 +318,13 @@ public class GenericFormatterRegistry implements FormatterRegistry {
throw new IllegalStateException( throw new IllegalStateException(
"Formatter referenced by @Formatted annotation does not have public constructor", e); "Formatter referenced by @Formatted annotation does not have public constructor", e);
} }
} else { }
else {
return null; return null;
} }
} }
private class ConvertingFormatter implements Formatter { private class ConvertingFormatter implements Formatter {
private Class<?> type; private Class<?> type;
@ -255,6 +339,7 @@ public class GenericFormatterRegistry implements FormatterRegistry {
this.targetFormatter = targetFormatter; this.targetFormatter = targetFormatter;
} }
@SuppressWarnings("unchecked")
public String format(Object object, Locale locale) { public String format(Object object, Locale locale) {
object = conversionService.convert(object, formattedObjectType); object = conversionService.convert(object, formattedObjectType);
return targetFormatter.format(object, locale); return targetFormatter.format(object, locale);
@ -268,4 +353,4 @@ public class GenericFormatterRegistry implements FormatterRegistry {
} }
} }

View File

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

View File

@ -27,6 +27,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -65,6 +66,7 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
* @see DefaultMessageCodesResolver * @see DefaultMessageCodesResolver
*/ */
public void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) { public void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) {
Assert.notNull(messageCodesResolver, "MessageCodesResolver must not be null");
this.messageCodesResolver = messageCodesResolver; this.messageCodesResolver = messageCodesResolver;
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,6 +22,13 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.ConfigurablePropertyAccessor; import org.springframework.beans.ConfigurablePropertyAccessor;
import org.springframework.beans.PropertyAccessorUtils; import org.springframework.beans.PropertyAccessorUtils;
import org.springframework.beans.PropertyEditorRegistry; 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 * Abstract base class for {@link BindingResult} implementations that work with
@ -37,6 +44,9 @@ import org.springframework.beans.PropertyEditorRegistry;
*/ */
public abstract class AbstractPropertyBindingResult extends AbstractBindingResult { public abstract class AbstractPropertyBindingResult extends AbstractBindingResult {
private FormatterRegistry formatterRegistry;
/** /**
* Create a new AbstractPropertyBindingResult instance. * Create a new AbstractPropertyBindingResult instance.
* @param objectName the name of the target object * @param objectName the name of the target object
@ -47,6 +57,12 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
} }
public void initFormatterLookup(FormatterRegistry formatterRegistry) {
Assert.notNull(formatterRegistry, "FormatterRegistry must not be null");
this.formatterRegistry = formatterRegistry;
getPropertyAccessor().setConversionService(new FormattingConversionServiceAdapter(formatterRegistry));
}
/** /**
* Returns the underlying PropertyAccessor. * Returns the underlying PropertyAccessor.
* @see #getPropertyAccessor() * @see #getPropertyAccessor()
@ -89,7 +105,13 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
*/ */
@Override @Override
protected Object formatFieldValue(String field, Object value) { 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) { if (customEditor != null) {
customEditor.setValue(value); customEditor.setValue(value);
String textValue = customEditor.getAsText(); String textValue = customEditor.getAsText();
@ -104,11 +126,10 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
/** /**
* Retrieve the custom PropertyEditor for the given field, if any. * 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> * @return the custom PropertyEditor, or <code>null</code>
*/ */
protected PropertyEditor getCustomEditor(String field) { protected PropertyEditor getCustomEditor(String fixedField) {
String fixedField = fixedField(field);
Class targetType = getPropertyAccessor().getPropertyType(fixedField); Class targetType = getPropertyAccessor().getPropertyType(fixedField);
PropertyEditor editor = getPropertyAccessor().findCustomEditor(targetType, fixedField); PropertyEditor editor = getPropertyAccessor().findCustomEditor(targetType, fixedField);
if (editor == null) { if (editor == null) {
@ -117,6 +138,22 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
return editor; 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 * Provide the PropertyAccessor to work with, according to the

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -88,10 +88,10 @@ public interface BindingResult extends Errors {
/** /**
* Find a custom property editor for the given type and property. * 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 * @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 * <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 * @return the registered editor, or <code>null</code> if none
*/ */
PropertyEditor findEditor(String field, Class valueType); PropertyEditor findEditor(String field, Class valueType);

View File

@ -35,6 +35,9 @@ import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.TypeConverter; import org.springframework.beans.TypeConverter;
import org.springframework.beans.TypeMismatchException; import org.springframework.beans.TypeMismatchException;
import org.springframework.core.MethodParameter; 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.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.PatternMatchUtils; import org.springframework.util.PatternMatchUtils;
@ -132,6 +135,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor(); private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
private FormatterRegistry formatterRegistry;
/** /**
* Create a new DataBinder instance, with default object name. * Create a new DataBinder instance, with default object name.
@ -178,6 +183,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
Assert.isNull(this.bindingResult, Assert.isNull(this.bindingResult,
"DataBinder is already initialized - call initBeanPropertyAccess before any other configuration methods"); "DataBinder is already initialized - call initBeanPropertyAccess before any other configuration methods");
this.bindingResult = new BeanPropertyBindingResult(getTarget(), getObjectName()); this.bindingResult = new BeanPropertyBindingResult(getTarget(), getObjectName());
if (this.formatterRegistry != null) {
this.bindingResult.initFormatterLookup(this.formatterRegistry);
}
} }
/** /**
@ -189,6 +197,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
Assert.isNull(this.bindingResult, Assert.isNull(this.bindingResult,
"DataBinder is already initialized - call initDirectFieldAccess before any other configuration methods"); "DataBinder is already initialized - call initDirectFieldAccess before any other configuration methods");
this.bindingResult = new DirectFieldBindingResult(getTarget(), getObjectName()); this.bindingResult = new DirectFieldBindingResult(getTarget(), getObjectName());
if (this.formatterRegistry != null) {
this.bindingResult.initFormatterLookup(this.formatterRegistry);
}
} }
/** /**
@ -215,6 +226,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
protected SimpleTypeConverter getSimpleTypeConverter() { protected SimpleTypeConverter getSimpleTypeConverter() {
if (this.typeConverter == null) { if (this.typeConverter == null) {
this.typeConverter = new SimpleTypeConverter(); this.typeConverter = new SimpleTypeConverter();
if (this.formatterRegistry != null) {
this.typeConverter.setConversionService(new FormattingConversionServiceAdapter(this.formatterRegistry));
}
} }
return this.typeConverter; return this.typeConverter;
} }
@ -418,6 +432,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
* @see DefaultBindingErrorProcessor * @see DefaultBindingErrorProcessor
*/ */
public void setBindingErrorProcessor(BindingErrorProcessor bindingErrorProcessor) { public void setBindingErrorProcessor(BindingErrorProcessor bindingErrorProcessor) {
Assert.notNull(bindingErrorProcessor, "BindingErrorProcessor must not be null");
this.bindingErrorProcessor = bindingErrorProcessor; this.bindingErrorProcessor = bindingErrorProcessor;
} }
@ -428,6 +443,31 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
return this.bindingErrorProcessor; 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 // Implementation of PropertyEditorRegistry/TypeConverter interface

View File

@ -19,6 +19,7 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.ui.format.number.CurrencyFormatter; import org.springframework.ui.format.number.CurrencyFormatter;
import org.springframework.ui.format.number.IntegerFormatter; import org.springframework.ui.format.number.IntegerFormatter;
import org.springframework.ui.format.support.GenericFormatterRegistry;
public class GenericFormatterRegistryTests { public class GenericFormatterRegistryTests {

View File

@ -17,6 +17,7 @@
package org.springframework.validation; package org.springframework.validation;
import java.beans.PropertyEditorSupport; import java.beans.PropertyEditorSupport;
import java.beans.PropertyEditor;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
@ -27,7 +28,6 @@ import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import junit.framework.TestCase; import junit.framework.TestCase;
import junit.framework.Assert;
import org.springframework.beans.BeanWithObjectProperty; import org.springframework.beans.BeanWithObjectProperty;
import org.springframework.beans.DerivedTestBean; import org.springframework.beans.DerivedTestBean;
@ -42,6 +42,8 @@ import org.springframework.beans.propertyeditors.CustomCollectionEditor;
import org.springframework.beans.propertyeditors.CustomNumberEditor; import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.context.support.StaticMessageSource; import org.springframework.context.support.StaticMessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.ui.format.number.DecimalFormatter;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -289,7 +291,35 @@ public class DataBinderTests extends TestCase {
MutablePropertyValues pvs = new MutablePropertyValues(); MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValue("object", "1"); pvs.addPropertyValue("object", "1");
binder.bind(pvs); 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 { public void testBindingWithAllowedFields() throws Exception {
@ -673,36 +703,36 @@ public class DataBinderTests extends TestCase {
assertEquals(2, errors.getGlobalErrorCount()); assertEquals(2, errors.getGlobalErrorCount());
assertEquals("NAME_TOUCHY_MISMATCH", errors.getGlobalError().getCode()); assertEquals("NAME_TOUCHY_MISMATCH", errors.getGlobalError().getCode());
assertEquals("NAME_TOUCHY_MISMATCH", ((ObjectError) errors.getGlobalErrors().get(0)).getCode()); assertEquals("NAME_TOUCHY_MISMATCH", (errors.getGlobalErrors().get(0)).getCode());
assertEquals("NAME_TOUCHY_MISMATCH.tb", ((ObjectError) errors.getGlobalErrors().get(0)).getCodes()[0]); assertEquals("NAME_TOUCHY_MISMATCH.tb", (errors.getGlobalErrors().get(0)).getCodes()[0]);
assertEquals("NAME_TOUCHY_MISMATCH", ((ObjectError) errors.getGlobalErrors().get(0)).getCodes()[1]); assertEquals("NAME_TOUCHY_MISMATCH", (errors.getGlobalErrors().get(0)).getCodes()[1]);
assertEquals("tb", ((ObjectError) errors.getGlobalErrors().get(0)).getObjectName()); assertEquals("tb", (errors.getGlobalErrors().get(0)).getObjectName());
assertEquals("GENERAL_ERROR", ((ObjectError) errors.getGlobalErrors().get(1)).getCode()); assertEquals("GENERAL_ERROR", (errors.getGlobalErrors().get(1)).getCode());
assertEquals("GENERAL_ERROR.tb", ((ObjectError) errors.getGlobalErrors().get(1)).getCodes()[0]); assertEquals("GENERAL_ERROR.tb", (errors.getGlobalErrors().get(1)).getCodes()[0]);
assertEquals("GENERAL_ERROR", ((ObjectError) errors.getGlobalErrors().get(1)).getCodes()[1]); assertEquals("GENERAL_ERROR", (errors.getGlobalErrors().get(1)).getCodes()[1]);
assertEquals("msg", ((ObjectError) errors.getGlobalErrors().get(1)).getDefaultMessage()); assertEquals("msg", (errors.getGlobalErrors().get(1)).getDefaultMessage());
assertEquals("arg", ((ObjectError) errors.getGlobalErrors().get(1)).getArguments()[0]); assertEquals("arg", (errors.getGlobalErrors().get(1)).getArguments()[0]);
assertTrue(errors.hasFieldErrors()); assertTrue(errors.hasFieldErrors());
assertEquals(4, errors.getFieldErrorCount()); assertEquals(4, errors.getFieldErrorCount());
assertEquals("TOO_YOUNG", errors.getFieldError().getCode()); assertEquals("TOO_YOUNG", errors.getFieldError().getCode());
assertEquals("TOO_YOUNG", ((FieldError) errors.getFieldErrors().get(0)).getCode()); assertEquals("TOO_YOUNG", (errors.getFieldErrors().get(0)).getCode());
assertEquals("age", ((FieldError) errors.getFieldErrors().get(0)).getField()); assertEquals("age", (errors.getFieldErrors().get(0)).getField());
assertEquals("AGE_NOT_ODD", ((FieldError) errors.getFieldErrors().get(1)).getCode()); assertEquals("AGE_NOT_ODD", (errors.getFieldErrors().get(1)).getCode());
assertEquals("age", ((FieldError) errors.getFieldErrors().get(1)).getField()); assertEquals("age", (errors.getFieldErrors().get(1)).getField());
assertEquals("NOT_ROD", ((FieldError) errors.getFieldErrors().get(2)).getCode()); assertEquals("NOT_ROD", (errors.getFieldErrors().get(2)).getCode());
assertEquals("name", ((FieldError) errors.getFieldErrors().get(2)).getField()); assertEquals("name", (errors.getFieldErrors().get(2)).getField());
assertEquals("TOO_YOUNG", ((FieldError) errors.getFieldErrors().get(3)).getCode()); assertEquals("TOO_YOUNG", (errors.getFieldErrors().get(3)).getCode());
assertEquals("spouse.age", ((FieldError) errors.getFieldErrors().get(3)).getField()); assertEquals("spouse.age", (errors.getFieldErrors().get(3)).getField());
assertTrue(errors.hasFieldErrors("age")); assertTrue(errors.hasFieldErrors("age"));
assertEquals(2, errors.getFieldErrorCount("age")); assertEquals(2, errors.getFieldErrorCount("age"));
assertEquals("TOO_YOUNG", errors.getFieldError("age").getCode()); assertEquals("TOO_YOUNG", errors.getFieldError("age").getCode());
assertEquals("TOO_YOUNG", ((FieldError) errors.getFieldErrors("age").get(0)).getCode()); assertEquals("TOO_YOUNG", (errors.getFieldErrors("age").get(0)).getCode());
assertEquals("tb", ((FieldError) errors.getFieldErrors("age").get(0)).getObjectName()); assertEquals("tb", (errors.getFieldErrors("age").get(0)).getObjectName());
assertEquals("age", ((FieldError) errors.getFieldErrors("age").get(0)).getField()); assertEquals("age", (errors.getFieldErrors("age").get(0)).getField());
assertEquals(new Integer(0), ((FieldError) errors.getFieldErrors("age").get(0)).getRejectedValue()); assertEquals(new Integer(0), (errors.getFieldErrors("age").get(0)).getRejectedValue());
assertEquals("AGE_NOT_ODD", ((FieldError) errors.getFieldErrors("age").get(1)).getCode()); assertEquals("AGE_NOT_ODD", (errors.getFieldErrors("age").get(1)).getCode());
assertTrue(errors.hasFieldErrors("name")); assertTrue(errors.hasFieldErrors("name"));
assertEquals(1, errors.getFieldErrorCount("name")); assertEquals(1, errors.getFieldErrorCount("name"));
@ -711,14 +741,14 @@ public class DataBinderTests extends TestCase {
assertEquals("NOT_ROD.name", errors.getFieldError("name").getCodes()[1]); assertEquals("NOT_ROD.name", errors.getFieldError("name").getCodes()[1]);
assertEquals("NOT_ROD.java.lang.String", errors.getFieldError("name").getCodes()[2]); assertEquals("NOT_ROD.java.lang.String", errors.getFieldError("name").getCodes()[2]);
assertEquals("NOT_ROD", errors.getFieldError("name").getCodes()[3]); assertEquals("NOT_ROD", errors.getFieldError("name").getCodes()[3]);
assertEquals("name", ((FieldError) errors.getFieldErrors("name").get(0)).getField()); assertEquals("name", (errors.getFieldErrors("name").get(0)).getField());
assertEquals(null, ((FieldError) errors.getFieldErrors("name").get(0)).getRejectedValue()); assertEquals(null, (errors.getFieldErrors("name").get(0)).getRejectedValue());
assertTrue(errors.hasFieldErrors("spouse.age")); assertTrue(errors.hasFieldErrors("spouse.age"));
assertEquals(1, errors.getFieldErrorCount("spouse.age")); assertEquals(1, errors.getFieldErrorCount("spouse.age"));
assertEquals("TOO_YOUNG", errors.getFieldError("spouse.age").getCode()); assertEquals("TOO_YOUNG", errors.getFieldError("spouse.age").getCode());
assertEquals("tb", ((FieldError) errors.getFieldErrors("spouse.age").get(0)).getObjectName()); assertEquals("tb", (errors.getFieldErrors("spouse.age").get(0)).getObjectName());
assertEquals(new Integer(0), ((FieldError) errors.getFieldErrors("spouse.age").get(0)).getRejectedValue()); assertEquals(new Integer(0), (errors.getFieldErrors("spouse.age").get(0)).getRejectedValue());
} }
public void testValidatorWithErrorsAndCodesPrefix() { public void testValidatorWithErrorsAndCodesPrefix() {
@ -744,36 +774,36 @@ public class DataBinderTests extends TestCase {
assertEquals(2, errors.getGlobalErrorCount()); assertEquals(2, errors.getGlobalErrorCount());
assertEquals("validation.NAME_TOUCHY_MISMATCH", errors.getGlobalError().getCode()); assertEquals("validation.NAME_TOUCHY_MISMATCH", errors.getGlobalError().getCode());
assertEquals("validation.NAME_TOUCHY_MISMATCH", ((ObjectError) errors.getGlobalErrors().get(0)).getCode()); assertEquals("validation.NAME_TOUCHY_MISMATCH", (errors.getGlobalErrors().get(0)).getCode());
assertEquals("validation.NAME_TOUCHY_MISMATCH.tb", ((ObjectError) errors.getGlobalErrors().get(0)).getCodes()[0]); assertEquals("validation.NAME_TOUCHY_MISMATCH.tb", (errors.getGlobalErrors().get(0)).getCodes()[0]);
assertEquals("validation.NAME_TOUCHY_MISMATCH", ((ObjectError) errors.getGlobalErrors().get(0)).getCodes()[1]); assertEquals("validation.NAME_TOUCHY_MISMATCH", (errors.getGlobalErrors().get(0)).getCodes()[1]);
assertEquals("tb", ((ObjectError) errors.getGlobalErrors().get(0)).getObjectName()); assertEquals("tb", (errors.getGlobalErrors().get(0)).getObjectName());
assertEquals("validation.GENERAL_ERROR", ((ObjectError) errors.getGlobalErrors().get(1)).getCode()); assertEquals("validation.GENERAL_ERROR", (errors.getGlobalErrors().get(1)).getCode());
assertEquals("validation.GENERAL_ERROR.tb", ((ObjectError) errors.getGlobalErrors().get(1)).getCodes()[0]); assertEquals("validation.GENERAL_ERROR.tb", (errors.getGlobalErrors().get(1)).getCodes()[0]);
assertEquals("validation.GENERAL_ERROR", ((ObjectError) errors.getGlobalErrors().get(1)).getCodes()[1]); assertEquals("validation.GENERAL_ERROR", (errors.getGlobalErrors().get(1)).getCodes()[1]);
assertEquals("msg", ((ObjectError) errors.getGlobalErrors().get(1)).getDefaultMessage()); assertEquals("msg", (errors.getGlobalErrors().get(1)).getDefaultMessage());
assertEquals("arg", ((ObjectError) errors.getGlobalErrors().get(1)).getArguments()[0]); assertEquals("arg", (errors.getGlobalErrors().get(1)).getArguments()[0]);
assertTrue(errors.hasFieldErrors()); assertTrue(errors.hasFieldErrors());
assertEquals(4, errors.getFieldErrorCount()); assertEquals(4, errors.getFieldErrorCount());
assertEquals("validation.TOO_YOUNG", errors.getFieldError().getCode()); assertEquals("validation.TOO_YOUNG", errors.getFieldError().getCode());
assertEquals("validation.TOO_YOUNG", ((FieldError) errors.getFieldErrors().get(0)).getCode()); assertEquals("validation.TOO_YOUNG", (errors.getFieldErrors().get(0)).getCode());
assertEquals("age", ((FieldError) errors.getFieldErrors().get(0)).getField()); assertEquals("age", (errors.getFieldErrors().get(0)).getField());
assertEquals("validation.AGE_NOT_ODD", ((FieldError) errors.getFieldErrors().get(1)).getCode()); assertEquals("validation.AGE_NOT_ODD", (errors.getFieldErrors().get(1)).getCode());
assertEquals("age", ((FieldError) errors.getFieldErrors().get(1)).getField()); assertEquals("age", (errors.getFieldErrors().get(1)).getField());
assertEquals("validation.NOT_ROD", ((FieldError) errors.getFieldErrors().get(2)).getCode()); assertEquals("validation.NOT_ROD", (errors.getFieldErrors().get(2)).getCode());
assertEquals("name", ((FieldError) errors.getFieldErrors().get(2)).getField()); assertEquals("name", (errors.getFieldErrors().get(2)).getField());
assertEquals("validation.TOO_YOUNG", ((FieldError) errors.getFieldErrors().get(3)).getCode()); assertEquals("validation.TOO_YOUNG", (errors.getFieldErrors().get(3)).getCode());
assertEquals("spouse.age", ((FieldError) errors.getFieldErrors().get(3)).getField()); assertEquals("spouse.age", (errors.getFieldErrors().get(3)).getField());
assertTrue(errors.hasFieldErrors("age")); assertTrue(errors.hasFieldErrors("age"));
assertEquals(2, errors.getFieldErrorCount("age")); assertEquals(2, errors.getFieldErrorCount("age"));
assertEquals("validation.TOO_YOUNG", errors.getFieldError("age").getCode()); assertEquals("validation.TOO_YOUNG", errors.getFieldError("age").getCode());
assertEquals("validation.TOO_YOUNG", ((FieldError) errors.getFieldErrors("age").get(0)).getCode()); assertEquals("validation.TOO_YOUNG", (errors.getFieldErrors("age").get(0)).getCode());
assertEquals("tb", ((FieldError) errors.getFieldErrors("age").get(0)).getObjectName()); assertEquals("tb", (errors.getFieldErrors("age").get(0)).getObjectName());
assertEquals("age", ((FieldError) errors.getFieldErrors("age").get(0)).getField()); assertEquals("age", (errors.getFieldErrors("age").get(0)).getField());
assertEquals(new Integer(0), ((FieldError) errors.getFieldErrors("age").get(0)).getRejectedValue()); assertEquals(new Integer(0), (errors.getFieldErrors("age").get(0)).getRejectedValue());
assertEquals("validation.AGE_NOT_ODD", ((FieldError) errors.getFieldErrors("age").get(1)).getCode()); assertEquals("validation.AGE_NOT_ODD", (errors.getFieldErrors("age").get(1)).getCode());
assertTrue(errors.hasFieldErrors("name")); assertTrue(errors.hasFieldErrors("name"));
assertEquals(1, errors.getFieldErrorCount("name")); assertEquals(1, errors.getFieldErrorCount("name"));
@ -782,14 +812,14 @@ public class DataBinderTests extends TestCase {
assertEquals("validation.NOT_ROD.name", errors.getFieldError("name").getCodes()[1]); assertEquals("validation.NOT_ROD.name", errors.getFieldError("name").getCodes()[1]);
assertEquals("validation.NOT_ROD.java.lang.String", errors.getFieldError("name").getCodes()[2]); assertEquals("validation.NOT_ROD.java.lang.String", errors.getFieldError("name").getCodes()[2]);
assertEquals("validation.NOT_ROD", errors.getFieldError("name").getCodes()[3]); assertEquals("validation.NOT_ROD", errors.getFieldError("name").getCodes()[3]);
assertEquals("name", ((FieldError) errors.getFieldErrors("name").get(0)).getField()); assertEquals("name", (errors.getFieldErrors("name").get(0)).getField());
assertEquals(null, ((FieldError) errors.getFieldErrors("name").get(0)).getRejectedValue()); assertEquals(null, (errors.getFieldErrors("name").get(0)).getRejectedValue());
assertTrue(errors.hasFieldErrors("spouse.age")); assertTrue(errors.hasFieldErrors("spouse.age"));
assertEquals(1, errors.getFieldErrorCount("spouse.age")); assertEquals(1, errors.getFieldErrorCount("spouse.age"));
assertEquals("validation.TOO_YOUNG", errors.getFieldError("spouse.age").getCode()); assertEquals("validation.TOO_YOUNG", errors.getFieldError("spouse.age").getCode());
assertEquals("tb", ((FieldError) errors.getFieldErrors("spouse.age").get(0)).getObjectName()); assertEquals("tb", (errors.getFieldErrors("spouse.age").get(0)).getObjectName());
assertEquals(new Integer(0), ((FieldError) errors.getFieldErrors("spouse.age").get(0)).getRejectedValue()); assertEquals(new Integer(0), (errors.getFieldErrors("spouse.age").get(0)).getRejectedValue());
} }
public void testValidatorWithNestedObjectNull() { public void testValidatorWithNestedObjectNull() {
@ -806,8 +836,8 @@ public class DataBinderTests extends TestCase {
assertTrue(errors.hasFieldErrors("spouse")); assertTrue(errors.hasFieldErrors("spouse"));
assertEquals(1, errors.getFieldErrorCount("spouse")); assertEquals(1, errors.getFieldErrorCount("spouse"));
assertEquals("SPOUSE_NOT_AVAILABLE", errors.getFieldError("spouse").getCode()); assertEquals("SPOUSE_NOT_AVAILABLE", errors.getFieldError("spouse").getCode());
assertEquals("tb", ((FieldError) errors.getFieldErrors("spouse").get(0)).getObjectName()); assertEquals("tb", (errors.getFieldErrors("spouse").get(0)).getObjectName());
assertEquals(null, ((FieldError) errors.getFieldErrors("spouse").get(0)).getRejectedValue()); assertEquals(null, (errors.getFieldErrors("spouse").get(0)).getRejectedValue());
} }
public void testNestedValidatorWithoutNestedPath() { public void testNestedValidatorWithoutNestedPath() {
@ -820,7 +850,7 @@ public class DataBinderTests extends TestCase {
assertTrue(errors.hasGlobalErrors()); assertTrue(errors.hasGlobalErrors());
assertEquals(1, errors.getGlobalErrorCount()); assertEquals(1, errors.getGlobalErrorCount());
assertEquals("SPOUSE_NOT_AVAILABLE", errors.getGlobalError().getCode()); 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() { public void testBindingStringArrayToIntegerSet() {

View File

@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.springframework.core.GenericTypeResolver; import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
@ -58,6 +59,28 @@ public class GenericConversionService implements ConversionService, ConverterReg
private final Map<Class, Map<Class, Object>> sourceTypeConverters = new HashMap<Class, Map<Class, Object>>(); 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. * Set the parent of this conversion service. This is optional.
*/ */
@ -100,16 +123,6 @@ public class GenericConversionService implements ConversionService, ConverterReg
sourceMap.remove(targetType); 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 // implementing ConversionService
@ -444,4 +457,4 @@ public class GenericConversionService implements ConversionService, ConverterReg
} }
} }
} }

View File

@ -16,11 +16,11 @@
package org.springframework.web.servlet.mvc.annotation; package org.springframework.web.servlet.mvc.annotation;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.security.Principal; import java.security.Principal;
import java.util.ArrayList; import java.util.ArrayList;
@ -33,7 +33,6 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
@ -54,7 +53,6 @@ import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage; import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.converter.BufferedImageHttpMessageConverter;
import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
@ -72,9 +70,9 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.validation.support.BindingAwareModelMap; import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.HttpSessionRequiredException; import org.springframework.web.HttpSessionRequiredException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
@ -119,11 +117,11 @@ import org.springframework.web.util.WebUtils;
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Arjen Poutsma * @author Arjen Poutsma
* @since 2.5
* @see #setPathMatcher * @see #setPathMatcher
* @see #setMethodNameResolver * @see #setMethodNameResolver
* @see #setWebBindingInitializer * @see #setWebBindingInitializer
* @see #setSessionAttributeStore * @see #setSessionAttributeStore
* @since 2.5
*/ */
public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter { public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter {
@ -165,19 +163,20 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
new ConcurrentHashMap<Class<?>, ServletHandlerMethodResolver>(); new ConcurrentHashMap<Class<?>, ServletHandlerMethodResolver>();
private HttpMessageConverter<?>[] messageConverters = private HttpMessageConverter<?>[] messageConverters =
new HttpMessageConverter[]{new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(), new HttpMessageConverter[] {new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
new FormHttpMessageConverter(), new SourceHttpMessageConverter()}; new FormHttpMessageConverter(), new SourceHttpMessageConverter()};
public AnnotationMethodHandlerAdapter() { public AnnotationMethodHandlerAdapter() {
// no restriction of HTTP methods by default // no restriction of HTTP methods by default
super(false); super(false);
} }
/** /**
* Set if URL lookup should always use the full path within the current servlet context. Else, the path within the * 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). * current servlet mapping is used if applicable (that is, in the case of a ".../*" servlet mapping in web.xml).
* <p>Default is "false". * <p>Default is "false".
*
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
*/ */
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) { public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
@ -188,7 +187,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
* Set if context path and request URI should be URL-decoded. Both are returned <i>undecoded</i> by the Servlet API, in * 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 * contrast to the servlet path. <p>Uses either the request encoding or the default encoding according to the Servlet
* spec (ISO-8859-1). * spec (ISO-8859-1).
*
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode * @see org.springframework.web.util.UrlPathHelper#setUrlDecode
*/ */
public void setUrlDecode(boolean urlDecode) { public void setUrlDecode(boolean urlDecode) {
@ -207,7 +205,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
/** /**
* Set the PathMatcher implementation to use for matching URL paths against registered URL patterns. Default is * Set the PathMatcher implementation to use for matching URL paths against registered URL patterns. Default is
* AntPathMatcher. * AntPathMatcher.
*
* @see org.springframework.util.AntPathMatcher * @see org.springframework.util.AntPathMatcher
*/ */
public void setPathMatcher(PathMatcher pathMatcher) { public void setPathMatcher(PathMatcher pathMatcher) {
@ -217,7 +214,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
/** /**
* Set the MethodNameResolver to use for resolving default handler methods (carrying an empty * 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. * through the annotation metadata already.
*/ */
public void setMethodNameResolver(MethodNameResolver methodNameResolver) { public void setMethodNameResolver(MethodNameResolver methodNameResolver) {
@ -225,15 +223,16 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
/** /**
* Specify a WebBindingInitializer which will apply pre-configured configuration to every DataBinder that this * Specify a WebBindingInitializer which will apply pre-configured configuration to every
* controller uses. * DataBinder that this controller uses.
*/ */
public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) { public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
this.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. * storing session attributes in the HttpSession, using the same attribute name as in the model.
*/ */
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) { public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
@ -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. * 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 * general handlers (but not to <code>@SessionAttributes</code> annotated handlers), this setting will apply to
* <code>@SessionAttributes</code> annotated handlers only. * <code>@SessionAttributes</code> annotated handlers only.
*
* @see #setCacheSeconds * @see #setCacheSeconds
* @see org.springframework.web.bind.annotation.SessionAttributes * @see org.springframework.web.bind.annotation.SessionAttributes
*/ */
@ -255,15 +254,17 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
/** /**
* Set if controller execution should be synchronized on the session, to serialize parallel invocations from the same * Set if controller execution should be synchronized on the session, to serialize
* client. <p>More specifically, the execution of each handler method will get synchronized if this flag is "true". The * parallel invocations from the same client.
* best available session mutex will be used for the synchronization; ideally, this will be a mutex exposed by * <p>More specifically, the execution of each handler method will get synchronized if this
* HttpSessionMutexListener. <p>The session mutex is guaranteed to be the same object during the entire lifetime of the * flag is "true". The best available session mutex will be used for the synchronization;
* session, available under the key defined by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a safe * ideally, this will be a mutex exposed by HttpSessionMutexListener.
* reference to synchronize on for locking on the current session. <p>In many cases, the HttpSession reference itself * <p>The session mutex is guaranteed to be the same object during the entire lifetime of the
* is a safe mutex as well, since it will always be the same object reference for the same active logical session. * session, available under the key defined by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant.
* However, this is not guaranteed across different servlet containers; the only 100% safe way is a session mutex. * 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.HttpSessionMutexListener
* @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession) * @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession)
*/ */
@ -273,7 +274,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
/** /**
* Set the ParameterNameDiscoverer to use for resolving method parameter names if needed (e.g. for default attribute * 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) { public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer; this.parameterNameDiscoverer = parameterNameDiscoverer;
@ -316,10 +318,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
* responses. * responses.
*/ */
public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) { public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
this.messageConverters = messageConverters; this.messageConverters = messageConverters;
} }
public boolean supports(Object handler) { public boolean supports(Object handler) {
return getMethodResolver(handler).hasHandlerMethods(); return getMethodResolver(handler).hasHandlerMethods();
} }
@ -372,12 +374,12 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
/** /**
* Template method for creating a new ServletRequestDataBinder instance. <p>The default implementation creates a * Template method for creating a new ServletRequestDataBinder instance.
* standard ServletRequestDataBinder. This can be overridden for custom ServletRequestDataBinder subclasses. * <p>The default implementation creates a standard ServletRequestDataBinder.
* * This can be overridden for custom ServletRequestDataBinder subclasses.
* @param request current HTTP request * @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 * @param target the target object to bind onto (or <code>null</code>
* parameter value) * if the binder is just used to convert a plain parameter value)
* @param objectName the objectName of the target object * @param objectName the objectName of the target object
* @return the ServletRequestDataBinder instance to use * @return the ServletRequestDataBinder instance to use
* @throws Exception in case of invalid state or arguments * @throws Exception in case of invalid state or arguments
@ -390,7 +392,9 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return new ServletRequestDataBinder(target, objectName); 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) { private ServletHandlerMethodResolver getMethodResolver(Object handler) {
Class handlerClass = ClassUtils.getUserClass(handler); Class handlerClass = ClassUtils.getUserClass(handler);
ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass); ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
@ -401,7 +405,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return resolver; return resolver;
} }
/** Servlet-specific subclass of {@link HandlerMethodResolver}. */
/**
* Servlet-specific subclass of {@link HandlerMethodResolver}.
*/
private class ServletHandlerMethodResolver extends HandlerMethodResolver { private class ServletHandlerMethodResolver extends HandlerMethodResolver {
private ServletHandlerMethodResolver(Class<?> handlerType) { private ServletHandlerMethodResolver(Class<?> handlerType) {
@ -587,7 +594,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
} }
/** Servlet-specific subclass of {@link HandlerMethodInvoker}. */
/**
* Servlet-specific subclass of {@link HandlerMethodInvoker}.
*/
private class ServletHandlerMethodInvoker extends HandlerMethodInvoker { private class ServletHandlerMethodInvoker extends HandlerMethodInvoker {
private boolean responseArgumentUsed = false; private boolean responseArgumentUsed = false;
@ -780,16 +790,18 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse()); HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
Class<?> returnValueType = returnValue.getClass(); Class<?> returnValueType = returnValue.getClass();
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>(); List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
for (HttpMessageConverter messageConverter : messageConverters) { if (messageConverters != null) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); for (HttpMessageConverter messageConverter : messageConverters) {
if (messageConverter.supports(returnValueType)) { allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
for (Object o : messageConverter.getSupportedMediaTypes()) { if (messageConverter.supports(returnValueType)) {
MediaType supportedMediaType = (MediaType) o; for (Object o : messageConverter.getSupportedMediaTypes()) {
for (MediaType acceptedMediaType : acceptedMediaTypes) { MediaType supportedMediaType = (MediaType) o;
if (supportedMediaType.includes(acceptedMediaType)) { for (MediaType acceptedMediaType : acceptedMediaTypes) {
messageConverter.write(returnValue, outputMessage); if (supportedMediaType.includes(acceptedMediaType)) {
responseArgumentUsed = true; messageConverter.write(returnValue, outputMessage);
return; responseArgumentUsed = true;
return;
}
} }
} }
} }
@ -799,6 +811,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
} }
} }
static class RequestMappingInfo { static class RequestMappingInfo {
String[] paths = new String[0]; String[] paths = new String[0];

View File

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -16,14 +16,6 @@
package org.springframework.web.servlet.mvc.annotation; 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.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.io.Writer; import java.io.Writer;
@ -43,7 +35,6 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.servlet.ServletConfig; import javax.servlet.ServletConfig;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@ -52,7 +43,9 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor; import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.DefaultPointcutAdvisor;
@ -80,6 +73,8 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.ExtendedModelMap; import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.ui.ModelMap; 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.SerializationTestUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
@ -95,6 +90,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus; 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.WebArgumentResolver;
import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
@ -405,7 +401,7 @@ public class ServletAnnotationControllerTests {
} }
@Test @Test
public void commandProvidingFormController() throws Exception { public void commandProvidingFormControllerWithCustomEditor() throws Exception {
@SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() { @SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() {
@Override @Override
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
@ -431,6 +427,37 @@ public class ServletAnnotationControllerTests {
assertEquals("myView-String:myDefaultName-typeMismatch-tb1-myOriginalValue", response.getContentAsString()); 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 @Test
public void typedCommandProvidingFormController() throws Exception { public void typedCommandProvidingFormController() throws Exception {
@SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() { @SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() {

View File

@ -75,8 +75,8 @@ import org.springframework.web.multipart.MultipartRequest;
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Arjen Poutsma * @author Arjen Poutsma
* @see #invokeHandlerMethod
* @since 2.5.2 * @since 2.5.2
* @see #invokeHandlerMethod
*/ */
public class HandlerMethodInvoker { public class HandlerMethodInvoker {
@ -93,18 +93,17 @@ public class HandlerMethodInvoker {
private final WebArgumentResolver[] customArgumentResolvers; private final WebArgumentResolver[] customArgumentResolvers;
private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus();
private final HttpMessageConverter[] messageConverters; private final HttpMessageConverter[] messageConverters;
private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus();
public HandlerMethodInvoker(HandlerMethodResolver methodResolver) { public HandlerMethodInvoker(HandlerMethodResolver methodResolver) {
this(methodResolver, null); this(methodResolver, null);
} }
public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer) { public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer) {
this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null, new WebArgumentResolver[0], this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null, null, null);
new HttpMessageConverter[0]);
} }
public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer, public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer,
@ -379,7 +378,7 @@ public class HandlerMethodInvoker {
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)
throws Exception { throws Exception {
Class paramType = methodParam.getParameterType(); Class<?> paramType = methodParam.getParameterType();
if (paramName.length() == 0) { if (paramName.length() == 0) {
paramName = getRequiredParameterName(methodParam); paramName = getRequiredParameterName(methodParam);
} }
@ -411,7 +410,7 @@ public class HandlerMethodInvoker {
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)
throws Exception { throws Exception {
Class paramType = methodParam.getParameterType(); Class<?> paramType = methodParam.getParameterType();
if (headerName.length() == 0) { if (headerName.length() == 0) {
headerName = getRequiredParameterName(methodParam); headerName = getRequiredParameterName(methodParam);
} }
@ -455,12 +454,14 @@ public class HandlerMethodInvoker {
"Cannot extract @RequestBody parameter (" + builder.toString() + "): no Content-Type found"); "Cannot extract @RequestBody parameter (" + builder.toString() + "): no Content-Type found");
} }
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>(); List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
for (HttpMessageConverter<?> messageConverter : messageConverters) { if (this.messageConverters != null) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter.supports(paramType)) { allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
for (MediaType supportedMediaType : messageConverter.getSupportedMediaTypes()) { if (messageConverter.supports(paramType)) {
if (supportedMediaType.includes(contentType)) { for (MediaType supportedMediaType : messageConverter.getSupportedMediaTypes()) {
return messageConverter.read(paramType, inputMessage); if (supportedMediaType.includes(contentType)) {
return messageConverter.read(paramType, inputMessage);
}
} }
} }
} }
@ -480,7 +481,7 @@ public class HandlerMethodInvoker {
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)
throws Exception { throws Exception {
Class paramType = methodParam.getParameterType(); Class<?> paramType = methodParam.getParameterType();
if (cookieName.length() == 0) { if (cookieName.length() == 0) {
cookieName = getRequiredParameterName(methodParam); cookieName = getRequiredParameterName(methodParam);
} }
@ -512,7 +513,7 @@ public class HandlerMethodInvoker {
private Object resolvePathVariable(String pathVarName, MethodParameter methodParam, private Object resolvePathVariable(String pathVarName, MethodParameter methodParam,
NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception { NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception {
Class paramType = methodParam.getParameterType(); Class<?> paramType = methodParam.getParameterType();
if (pathVarName.length() == 0) { if (pathVarName.length() == 0) {
pathVarName = getRequiredParameterName(methodParam); pathVarName = getRequiredParameterName(methodParam);
} }
@ -565,8 +566,8 @@ public class HandlerMethodInvoker {
if ("".equals(name)) { if ("".equals(name)) {
name = Conventions.getVariableNameForParameter(methodParam); name = Conventions.getVariableNameForParameter(methodParam);
} }
Class paramType = methodParam.getParameterType(); Class<?> paramType = methodParam.getParameterType();
Object bindObject = null; Object bindObject;
if (implicitModel.containsKey(name)) { if (implicitModel.containsKey(name)) {
bindObject = implicitModel.get(name); bindObject = implicitModel.get(name);
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.web.bind.support; package org.springframework.web.bind.support;
import org.springframework.beans.PropertyEditorRegistrar; import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.ui.format.FormatterRegistry;
import org.springframework.validation.BindingErrorProcessor; import org.springframework.validation.BindingErrorProcessor;
import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.MessageCodesResolver;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
@ -42,6 +43,8 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
private BindingErrorProcessor bindingErrorProcessor; private BindingErrorProcessor bindingErrorProcessor;
private FormatterRegistry formatterRegistry;
private PropertyEditorRegistrar[] propertyEditorRegistrars; private PropertyEditorRegistrar[] propertyEditorRegistrars;
@ -91,24 +94,35 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
} }
/** /**
* Specify a single PropertyEditorRegistrar to be applied * Specify a FormatterRegistry which will apply to every DataBinder.
* to every DataBinder that this controller uses. */
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) { public final void setPropertyEditorRegistrar(PropertyEditorRegistrar propertyEditorRegistrar) {
this.propertyEditorRegistrars = new PropertyEditorRegistrar[] {propertyEditorRegistrar}; this.propertyEditorRegistrars = new PropertyEditorRegistrar[] {propertyEditorRegistrar};
} }
/** /**
* Specify multiple PropertyEditorRegistrars to be applied * Specify multiple PropertyEditorRegistrars to be applied to every DataBinder.
* to every DataBinder that this controller uses.
*/ */
public final void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) { public final void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
this.propertyEditorRegistrars = propertyEditorRegistrars; this.propertyEditorRegistrars = propertyEditorRegistrars;
} }
/** /**
* Return the PropertyEditorRegistrars to be applied * Return the PropertyEditorRegistrars to be applied to every DataBinder.
* to every DataBinder that this controller uses.
*/ */
public final PropertyEditorRegistrar[] getPropertyEditorRegistrars() { public final PropertyEditorRegistrar[] getPropertyEditorRegistrars() {
return this.propertyEditorRegistrars; return this.propertyEditorRegistrars;
@ -125,9 +139,12 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
if (this.bindingErrorProcessor != null) { if (this.bindingErrorProcessor != null) {
binder.setBindingErrorProcessor(this.bindingErrorProcessor); binder.setBindingErrorProcessor(this.bindingErrorProcessor);
} }
if (this.formatterRegistry != null) {
binder.setFormatterRegistry(this.formatterRegistry);
}
if (this.propertyEditorRegistrars != null) { if (this.propertyEditorRegistrars != null) {
for (int i = 0; i < this.propertyEditorRegistrars.length; i++) { for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
this.propertyEditorRegistrars[i].registerCustomEditors(binder); propertyEditorRegistrar.registerCustomEditors(binder);
} }
} }
} }