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:
parent
9f9f2349cd
commit
fee838a65e
|
|
@ -36,9 +36,11 @@ import java.util.Set;
|
|||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.GenericCollectionTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
|
@ -326,6 +328,24 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
|||
return null;
|
||||
}
|
||||
|
||||
public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException {
|
||||
try {
|
||||
PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
|
||||
if (pd != null) {
|
||||
if (pd.getReadMethod() != null) {
|
||||
return new TypeDescriptor(new MethodParameter(pd.getReadMethod(), -1));
|
||||
}
|
||||
else if (pd.getWriteMethod() != null) {
|
||||
return new TypeDescriptor(new MethodParameter(pd.getWriteMethod(), 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidPropertyException ex) {
|
||||
// Consider as not determinable.
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isReadableProperty(String propertyName) {
|
||||
try {
|
||||
PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
|
|
@ -86,6 +87,14 @@ public class DirectFieldAccessor extends AbstractPropertyAccessor {
|
|||
return null;
|
||||
}
|
||||
|
||||
public TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException {
|
||||
Field field = this.fieldMap.get(propertyName);
|
||||
if (field != null) {
|
||||
return new TypeDescriptor(field);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPropertyValue(String propertyName) throws BeansException {
|
||||
Field field = this.fieldMap.get(propertyName);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -18,6 +18,8 @@ package org.springframework.beans;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
|
||||
/**
|
||||
* Common interface for classes that can access named properties
|
||||
* (such as bean properties of an object or fields in an object)
|
||||
|
|
@ -86,6 +88,17 @@ public interface PropertyAccessor {
|
|||
*/
|
||||
Class getPropertyType(String propertyName) throws BeansException;
|
||||
|
||||
/**
|
||||
* Return a type descriptor for the specified property.
|
||||
* @param propertyName the property to check
|
||||
* (may be a nested path and/or an indexed/mapped property)
|
||||
* @return the property type for the particular property,
|
||||
* or <code>null</code> if not determinable
|
||||
* @throws InvalidPropertyException if there is no such property or
|
||||
* if the property isn't readable
|
||||
*/
|
||||
TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException;
|
||||
|
||||
/**
|
||||
* Get the current value of the specified property.
|
||||
* @param propertyName the name of the property to get the value of
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import java.beans.PropertyDescriptor;
|
|||
import java.beans.PropertyEditor;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
|
@ -203,8 +204,18 @@ class TypeConverterDelegate {
|
|||
convertedValue = convertToTypedMap((Map) convertedValue, propertyName, methodParam);
|
||||
}
|
||||
else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
|
||||
String strValue = ((String) convertedValue).trim();
|
||||
if (requiredType.isEnum() && "".equals(strValue)) {
|
||||
try {
|
||||
Constructor strCtor = requiredType.getConstructor(String.class);
|
||||
return (T) BeanUtils.instantiateClass(strCtor, convertedValue);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
// proceed with field lookup
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
|
||||
}
|
||||
}
|
||||
String trimmedValue = ((String) convertedValue).trim();
|
||||
if (requiredType.isEnum() && "".equals(trimmedValue)) {
|
||||
// It's an empty enum identifier: reset the enum value to null.
|
||||
return null;
|
||||
}
|
||||
|
|
@ -212,7 +223,7 @@ class TypeConverterDelegate {
|
|||
// with values defined as static fields. Resulting value still needs
|
||||
// to be checked, hence we don't return it right away.
|
||||
try {
|
||||
Field enumField = requiredType.getField(strValue);
|
||||
Field enumField = requiredType.getField(trimmedValue);
|
||||
convertedValue = enumField.get(null);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.beans.factory.config;
|
||||
|
||||
import java.beans.PropertyEditor;
|
||||
import java.security.AccessControlContext;
|
||||
|
||||
import org.springframework.beans.PropertyEditorRegistrar;
|
||||
import org.springframework.beans.PropertyEditorRegistry;
|
||||
|
|
@ -249,6 +250,12 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single
|
|||
*/
|
||||
Scope getRegisteredScope(String scopeName);
|
||||
|
||||
/**
|
||||
* Provides a security access control context relevant to this factory.
|
||||
* @return the applicable AccessControlContext (never <code>null</code>)
|
||||
*/
|
||||
AccessControlContext getAccessControlContext();
|
||||
|
||||
/**
|
||||
* Copy all relevant configuration from the given other factory.
|
||||
* <p>Should include all standard configuration settings as well as
|
||||
|
|
|
|||
|
|
@ -150,6 +150,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
/** Map from scope identifier String to corresponding Scope */
|
||||
private final Map<String, Scope> scopes = new HashMap<String, Scope>();
|
||||
|
||||
/** Security context used when running with a SecurityManager */
|
||||
private SecurityContextProvider securityContextProvider;
|
||||
|
||||
/** Map from bean name to merged RootBeanDefinition */
|
||||
private final Map<String, RootBeanDefinition> mergedBeanDefinitions =
|
||||
new ConcurrentHashMap<String, RootBeanDefinition>();
|
||||
|
|
@ -161,9 +164,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
|
||||
new NamedThreadLocal<Object>("Prototype beans currently in creation");
|
||||
|
||||
/** security context used when running with a Security Manager */
|
||||
private volatile SecurityContextProvider securityProvider = new SimpleSecurityContextProvider();
|
||||
|
||||
/**
|
||||
* Create a new AbstractBeanFactory.
|
||||
*/
|
||||
|
|
@ -761,6 +761,26 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
return this.scopes.get(scopeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the security context provider for this bean factory. If a security manager
|
||||
* is set, interaction with the user code will be executed using the privileged
|
||||
* of the provided security context.
|
||||
*/
|
||||
public void setSecurityContextProvider(SecurityContextProvider securityProvider) {
|
||||
this.securityContextProvider = securityProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate the creation of the access control context to the
|
||||
* {@link #setSecurityContextProvider SecurityContextProvider}.
|
||||
*/
|
||||
@Override
|
||||
public AccessControlContext getAccessControlContext() {
|
||||
return (this.securityContextProvider != null ?
|
||||
this.securityContextProvider.getAccessControlContext() :
|
||||
AccessController.getContext());
|
||||
}
|
||||
|
||||
public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) {
|
||||
Assert.notNull(otherFactory, "BeanFactory must not be null");
|
||||
setBeanClassLoader(otherFactory.getBeanClassLoader());
|
||||
|
|
@ -776,6 +796,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
this.hasDestructionAwareBeanPostProcessors = this.hasDestructionAwareBeanPostProcessors ||
|
||||
otherAbstractFactory.hasDestructionAwareBeanPostProcessors;
|
||||
this.scopes.putAll(otherAbstractFactory.scopes);
|
||||
this.securityContextProvider = otherAbstractFactory.securityContextProvider;
|
||||
}
|
||||
else {
|
||||
setTypeConverter(otherFactory.getTypeConverter());
|
||||
|
|
@ -1437,36 +1458,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Delegate the creation of the security context to {@link #getSecurityContextProvider()}.
|
||||
*/
|
||||
@Override
|
||||
protected AccessControlContext getAccessControlContext() {
|
||||
SecurityContextProvider provider = getSecurityContextProvider();
|
||||
return (provider != null ? provider.getAccessControlContext(): AccessController.getContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the security context provider for this bean factory.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public SecurityContextProvider getSecurityContextProvider() {
|
||||
return securityProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the security context provider for this bean factory. If a security manager
|
||||
* is set, interaction with the user code will be executed using the privileged
|
||||
* of the provided security context.
|
||||
*
|
||||
* @param securityProvider
|
||||
*/
|
||||
public void setSecurityContextProvider(SecurityContextProvider securityProvider) {
|
||||
this.securityProvider = securityProvider;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Abstract methods to be implemented by subclasses
|
||||
|
|
@ -1526,4 +1517,5 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
*/
|
||||
protected abstract Object createBean(String beanName, RootBeanDefinition mbd, Object[] args)
|
||||
throws BeanCreationException;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,10 +212,10 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
|||
* Returns the security context for this bean factory. If a security manager
|
||||
* is set, interaction with the user code will be executed using the privileged
|
||||
* of the security context returned by this method.
|
||||
*
|
||||
* @return
|
||||
* @see AccessController#getContext()
|
||||
*/
|
||||
protected AccessControlContext getAccessControlContext() {
|
||||
return AccessController.getContext();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,13 +22,14 @@ import java.security.AccessControlContext;
|
|||
* Provider of the security context of the code running inside the bean factory.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface SecurityContextProvider {
|
||||
|
||||
/**
|
||||
* Provides a security access control context relevant to a bean factory.
|
||||
*
|
||||
* @return bean factory security control context
|
||||
*/
|
||||
AccessControlContext getAccessControlContext();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,18 +20,19 @@ import java.security.AccessControlContext;
|
|||
import java.security.AccessController;
|
||||
|
||||
/**
|
||||
* Simple #SecurityContextProvider implementation.
|
||||
* Simple {@link SecurityContextProvider} implementation.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @since 3.0
|
||||
*/
|
||||
public class SimpleSecurityContextProvider implements SecurityContextProvider {
|
||||
|
||||
private final AccessControlContext acc;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new <code>SimpleSecurityContextProvider</code> instance.
|
||||
*
|
||||
* The security context will be retrieved on each call from the current
|
||||
* Construct a new <code>SimpleSecurityContextProvider</code> instance.
|
||||
* <p>The security context will be retrieved on each call from the current
|
||||
* thread.
|
||||
*/
|
||||
public SimpleSecurityContextProvider() {
|
||||
|
|
@ -39,20 +40,19 @@ public class SimpleSecurityContextProvider implements SecurityContextProvider {
|
|||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <code>SimpleSecurityContextProvider</code> instance.
|
||||
*
|
||||
* If the given control context is null, the security context will be
|
||||
* Construct a new <code>SimpleSecurityContextProvider</code> instance.
|
||||
* <p>If the given control context is null, the security context will be
|
||||
* retrieved on each call from the current thread.
|
||||
*
|
||||
* @param acc access control context (can be <code>null</code>)
|
||||
* @see AccessController#getContext()
|
||||
* @param acc
|
||||
* access control context (can be null)
|
||||
*/
|
||||
public SimpleSecurityContextProvider(AccessControlContext acc) {
|
||||
this.acc = acc;
|
||||
}
|
||||
|
||||
|
||||
public AccessControlContext getAccessControlContext() {
|
||||
return (acc == null ? AccessController.getContext() : acc);
|
||||
return (this.acc != null ? acc : AccessController.getContext());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,13 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life
|
|||
*/
|
||||
String CONFIG_LOCATION_DELIMITERS = ",; \t\n";
|
||||
|
||||
/**
|
||||
* Name of the ConversionService bean in the factory.
|
||||
* If none is supplied, default conversion rules apply.
|
||||
* @see org.springframework.core.convert.ConversionService
|
||||
*/
|
||||
String CONVERSION_SERVICE_BEAN_NAME = "conversionService";
|
||||
|
||||
/**
|
||||
* Name of the LoadTimeWeaver bean in the factory. If such a bean is supplied,
|
||||
* the context will use a temporary ClassLoader for type matching, in order
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ import org.springframework.context.weaving.LoadTimeWeaverAwareProcessor;
|
|||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
|
@ -367,6 +368,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
|||
// Register bean processors that intercept bean creation.
|
||||
registerBeanPostProcessors(beanFactory);
|
||||
|
||||
// Initialize conversion service for this context.
|
||||
initConversionService();
|
||||
|
||||
// Initialize message source for this context.
|
||||
initMessageSource();
|
||||
|
||||
|
|
@ -605,6 +609,16 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the BeanFactory's ConversionService.
|
||||
*/
|
||||
protected void initConversionService() {
|
||||
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
|
||||
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME)) {
|
||||
beanFactory.setConversionService(beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the MessageSource.
|
||||
* Use parent's if none defined in this context.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -22,9 +22,6 @@ import java.security.PrivilegedAction;
|
|||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.AbstractBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
|
@ -43,6 +40,7 @@ import org.springframework.context.ResourceLoaderAware;
|
|||
* underlying bean factory. Applications do not use this directly.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Costin Leau
|
||||
* @since 10.10.2003
|
||||
* @see org.springframework.context.ResourceLoaderAware
|
||||
* @see org.springframework.context.MessageSourceAware
|
||||
|
|
@ -52,13 +50,13 @@ import org.springframework.context.ResourceLoaderAware;
|
|||
*/
|
||||
class ApplicationContextAwareProcessor implements BeanPostProcessor {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
private final ConfigurableApplicationContext applicationContext;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new ApplicationContextAwareProcessor for the given context.
|
||||
*/
|
||||
public ApplicationContextAwareProcessor(ApplicationContext applicationContext) {
|
||||
public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
|
|
@ -66,16 +64,13 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor {
|
|||
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
|
||||
AccessControlContext acc = null;
|
||||
|
||||
if (System.getSecurityManager() != null) {
|
||||
if (applicationContext instanceof ConfigurableApplicationContext) {
|
||||
ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
|
||||
if (factory instanceof AbstractBeanFactory) {
|
||||
acc = ((AbstractBeanFactory) factory).getSecurityContextProvider().getAccessControlContext();
|
||||
if (System.getSecurityManager() != null &&
|
||||
(bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
|
||||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
|
||||
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
|
||||
if (acc != null) {
|
||||
AccessController.doPrivileged(new PrivilegedAction<Object>() {
|
||||
public Object run() {
|
||||
doProcess(bean);
|
||||
|
|
@ -83,7 +78,6 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor {
|
|||
}
|
||||
}, acc);
|
||||
}
|
||||
}
|
||||
else {
|
||||
doProcess(bean);
|
||||
}
|
||||
|
|
@ -109,4 +103,5 @@ class ApplicationContextAwareProcessor implements BeanPostProcessor {
|
|||
public Object postProcessAfterInitialization(Object bean, String name) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -55,7 +55,7 @@ public abstract class UiApplicationContextUtils {
|
|||
*/
|
||||
public static ThemeSource initThemeSource(ApplicationContext context) {
|
||||
if (context.containsLocalBean(THEME_SOURCE_BEAN_NAME)) {
|
||||
ThemeSource themeSource = (ThemeSource) context.getBean(THEME_SOURCE_BEAN_NAME, ThemeSource.class);
|
||||
ThemeSource themeSource = context.getBean(THEME_SOURCE_BEAN_NAME, ThemeSource.class);
|
||||
// Make ThemeSource aware of parent ThemeSource.
|
||||
if (context.getParent() instanceof ThemeSource && themeSource instanceof HierarchicalThemeSource) {
|
||||
HierarchicalThemeSource hts = (HierarchicalThemeSource) themeSource;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2004-2009 the original author or authors.
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -13,13 +13,18 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ui.format;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* A factory that creates {@link Formatter formatters} to format property values on properties annotated with a particular format {@link Annotation}.
|
||||
* For example, a <code>CurrencyAnnotationFormatterFactory</code> might create a <code>Formatter</code> that formats a <code>BigDecimal</code> value set on a property annotated with <code>@CurrencyFormat</code>.
|
||||
* A factory that creates {@link Formatter formatters} to format property values on properties
|
||||
* annotated with a particular format {@link Annotation}.
|
||||
*
|
||||
* <p>For example, a <code>CurrencyAnnotationFormatterFactory</code> might create a <code>Formatter</code>
|
||||
* that formats a <code>BigDecimal</code> value set on a property annotated with <code>@CurrencyFormat</code>.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @param <A> The type of Annotation this factory uses to create Formatter instances
|
||||
|
|
@ -34,4 +39,5 @@ public interface AnnotationFormatterFactory<A extends Annotation, T> {
|
|||
* @return the Formatter to use to format values of properties annotated with the annotation.
|
||||
*/
|
||||
Formatter<T> getFormatter(A annotation);
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2004-2009 the original author or authors.
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ui.format;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
|
|
@ -23,6 +24,7 @@ import java.lang.annotation.Target;
|
|||
|
||||
/**
|
||||
* A type that can be formatted as a String for display in a user interface.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
|
|
@ -32,7 +34,8 @@ import java.lang.annotation.Target;
|
|||
public @interface Formatted {
|
||||
|
||||
/**
|
||||
* The Formatter that handles the formatting.
|
||||
* The Formatter that handles the formatting for the annotated element.
|
||||
*/
|
||||
Class<?> value();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2004-2009 the original author or authors.
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ui.format;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
|
@ -20,6 +21,7 @@ import java.util.Locale;
|
|||
|
||||
/**
|
||||
* Formats objects of type T for display.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @param <T> the type of object this formatter can format
|
||||
|
|
@ -42,4 +44,5 @@ public interface Formatter<T> {
|
|||
* @throws ParseException when a parse exception occurs
|
||||
*/
|
||||
T parse(String formatted, Locale locale) throws ParseException;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2004-2009 the original author or authors.
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ui.format;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
|
@ -21,6 +22,7 @@ import org.springframework.core.convert.TypeDescriptor;
|
|||
|
||||
/**
|
||||
* A shared registry of Formatters.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
|
|
@ -42,21 +44,19 @@ public interface FormatterRegistry {
|
|||
* On parse, the decorator first delegates to the formatter to parse a <T>, then coerses the parsed value to type.
|
||||
* @param type the object type
|
||||
* @param targetFormatter the target formatter
|
||||
* @param <T> the type of object the target formatter formats
|
||||
*/
|
||||
<T> void add(Class<?> type, Formatter<T> targetFormatter);
|
||||
void add(Class<?> type, Formatter<?> targetFormatter);
|
||||
|
||||
/**
|
||||
* Adds a AnnotationFormatterFactory that returns the Formatter for properties annotated with a specific annotation.
|
||||
* @param factory the annotation formatter factory
|
||||
*/
|
||||
<A extends Annotation, T> void add(AnnotationFormatterFactory<A, T> factory);
|
||||
void add(AnnotationFormatterFactory<?, ?> factory);
|
||||
|
||||
/**
|
||||
* Get the Formatter for the type descriptor.
|
||||
* @return the Formatter, or <code>null</code> if no suitable one is registered
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
Formatter getFormatter(TypeDescriptor type);
|
||||
Formatter<Object> getFormatter(TypeDescriptor type);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2004-2009 the original author or authors.
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ui.format.date;
|
||||
|
||||
import java.text.DateFormat;
|
||||
|
|
@ -21,37 +22,60 @@ import java.text.SimpleDateFormat;
|
|||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.ui.format.Formatter;
|
||||
|
||||
/**
|
||||
* A formatter for {@link java.util.Date} types.
|
||||
* Allows the configuration of an explicit date pattern and locale.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see SimpleDateFormat
|
||||
*/
|
||||
public final class DateFormatter implements Formatter<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 int style = DateFormat.DEFAULT;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the pattern to use to format date values.
|
||||
* If not specified, the default pattern 'yyyy-MM-dd' is used.
|
||||
* @param pattern the date formatting pattern
|
||||
* Create a new default DateFormatter.
|
||||
*/
|
||||
public DateFormatter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new DateFormatter for the given date pattern.
|
||||
*/
|
||||
public DateFormatter(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the pattern to use to format date values.
|
||||
* <p>If not specified, DateFormat's default style will be used.
|
||||
*/
|
||||
public void setPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the style to use to format date values.
|
||||
* <p>If not specified, DateFormat's default style will be used.
|
||||
* @see DateFormat#DEFAULT
|
||||
* @see DateFormat#SHORT
|
||||
* @see DateFormat#MEDIUM
|
||||
* @see DateFormat#LONG
|
||||
* @see DateFormat#FULL
|
||||
*/
|
||||
public void setStyle(int style) {
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
|
||||
public String format(Date date, Locale locale) {
|
||||
if (date == null) {
|
||||
return "";
|
||||
|
|
@ -66,23 +90,17 @@ public final class DateFormatter implements Formatter<Date> {
|
|||
return getDateFormat(locale).parse(formatted);
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
private DateFormat getDateFormat(Locale locale) {
|
||||
DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT, locale);
|
||||
format.setLenient(false);
|
||||
if (format instanceof SimpleDateFormat) {
|
||||
String pattern = determinePattern(this.pattern);
|
||||
((SimpleDateFormat) format).applyPattern(pattern);
|
||||
} else {
|
||||
logger.warn("Unable to apply format pattern '" + pattern
|
||||
+ "'; Returned DateFormat is not a SimpleDateFormat");
|
||||
protected DateFormat getDateFormat(Locale locale) {
|
||||
DateFormat dateFormat;
|
||||
if (this.pattern != null) {
|
||||
dateFormat = new SimpleDateFormat(this.pattern, locale);
|
||||
}
|
||||
return format;
|
||||
else {
|
||||
dateFormat = DateFormat.getDateInstance(this.style, locale);
|
||||
}
|
||||
|
||||
private String determinePattern(String pattern) {
|
||||
return pattern != null ? pattern : DEFAULT_PATTERN;
|
||||
dateFormat.setLenient(false);
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2004-2009 the original author or authors.
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -13,62 +13,74 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ui.format;
|
||||
|
||||
package org.springframework.ui.format.support;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.ui.format.FormatterRegistry;
|
||||
import org.springframework.ui.format.Formatter;
|
||||
import org.springframework.ui.format.AnnotationFormatterFactory;
|
||||
import org.springframework.ui.format.Formatted;
|
||||
|
||||
/**
|
||||
* A generic implementation of {@link FormatterRegistry} suitable for use in most environments.
|
||||
* A generic implementation of {@link org.springframework.ui.format.FormatterRegistry} suitable for use in most environments.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see #setConversionService(ConversionService)
|
||||
* @see #add(Formatter)
|
||||
* @see #add(Class, Formatter)
|
||||
* @see #add(AnnotationFormatterFactory)
|
||||
* @see #add(org.springframework.ui.format.Formatter)
|
||||
* @see #add(Class, org.springframework.ui.format.Formatter)
|
||||
* @see #add(org.springframework.ui.format.AnnotationFormatterFactory)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class GenericFormatterRegistry implements FormatterRegistry {
|
||||
public class GenericFormatterRegistry implements FormatterRegistry, BeanFactoryAware, Cloneable {
|
||||
|
||||
private Map<Class, Formatter> typeFormatters = new ConcurrentHashMap<Class, Formatter>();
|
||||
private final Map<Class, Formatter> typeFormatters = new ConcurrentHashMap<Class, Formatter>();
|
||||
|
||||
private Map<Class, AnnotationFormatterFactory> annotationFormatters = new HashMap<Class, AnnotationFormatterFactory>();
|
||||
private final Map<Class, AnnotationFormatterFactory> annotationFormatters =
|
||||
new ConcurrentHashMap<Class, AnnotationFormatterFactory>();
|
||||
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
|
||||
private boolean shared = true;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the type conversion service that will be used to coerse objects to the types required for formatting.
|
||||
* Defaults to a {@link DefaultConversionService}.
|
||||
* @param conversionService the conversion service
|
||||
* @see #add(Class, Formatter)
|
||||
* Registers the formatters in the set provided.
|
||||
* JavaBean-friendly alternative to calling {@link #add(Formatter)}.
|
||||
* @see #add(Formatter)
|
||||
*/
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
public void setFormatters(Set<Formatter<?>> formatters) {
|
||||
for (Formatter<?> formatter : formatters) {
|
||||
add(formatter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the formatters in the map provided by type.
|
||||
* JavaBean-friendly alternative to calling {@link #add(Class, Formatter)}.
|
||||
* @param formatters the formatters map
|
||||
* @see #add(Class, Formatter)
|
||||
*/
|
||||
public void setFormatters(Map<Class<?>, Formatter<?>> formatters) {
|
||||
public void setFormatterMap(Map<Class<?>, Formatter<?>> formatters) {
|
||||
for (Map.Entry<Class<?>, Formatter<?>> entry : formatters.entrySet()) {
|
||||
add(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
|
@ -85,36 +97,103 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the type conversion service that will be used to coerce objects to the
|
||||
* types required for formatting. Defaults to a {@link DefaultConversionService}.
|
||||
* @see #add(Class, Formatter)
|
||||
*/
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
Assert.notNull(conversionService, "ConversionService must not be null");
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type conversion service which this FormatterRegistry delegates to.
|
||||
*/
|
||||
public ConversionService getConversionService() {
|
||||
return this.conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the context's default ConversionService if none specified locally.
|
||||
*/
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
if (this.conversionService == null &&
|
||||
beanFactory.containsBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)) {
|
||||
this.conversionService = beanFactory.getBean(
|
||||
ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, ConversionService.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// cloning support
|
||||
|
||||
/**
|
||||
* Specify whether this FormatterRegistry is shared, in which case newly
|
||||
* registered Formatters will be visible to other callers as well.
|
||||
* <p>A new GenericFormatterRegistry is considered as shared by default,
|
||||
* whereas a cloned GenericFormatterRegistry will be non-shared by default.
|
||||
* @see #clone()
|
||||
*/
|
||||
public void setShared(boolean shared) {
|
||||
this.shared = shared;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this FormatterRegistry is shared, in which case newly
|
||||
* registered Formatters will be visible to other callers as well.
|
||||
*/
|
||||
public boolean isShared() {
|
||||
return this.shared;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an independent clone of this FormatterRegistry.
|
||||
* @see #setShared
|
||||
*/
|
||||
@Override
|
||||
public GenericFormatterRegistry clone() {
|
||||
GenericFormatterRegistry clone = new GenericFormatterRegistry();
|
||||
clone.typeFormatters.putAll(this.typeFormatters);
|
||||
clone.annotationFormatters.putAll(this.annotationFormatters);
|
||||
clone.conversionService = this.conversionService;
|
||||
clone.shared = false;
|
||||
return clone;
|
||||
}
|
||||
|
||||
|
||||
// implementing FormatterRegistry
|
||||
|
||||
public <T> void add(Formatter<T> formatter) {
|
||||
typeFormatters.put(getFormattedObjectType(formatter.getClass()), formatter);
|
||||
this.typeFormatters.put(getFormattedObjectType(formatter.getClass()), formatter);
|
||||
}
|
||||
|
||||
public <T> void add(Class<?> type, Formatter<T> formatter) {
|
||||
public void add(Class<?> type, Formatter<?> formatter) {
|
||||
Class<?> formattedObjectType = getFormattedObjectType(formatter.getClass());
|
||||
if (!conversionService.canConvert(formattedObjectType, type)) {
|
||||
if (!this.conversionService.canConvert(formattedObjectType, type)) {
|
||||
throw new IllegalArgumentException("Unable to register formatter " + formatter + " for type [" + type.getName() + "]; not able to convert from [" + formattedObjectType.getName() + "] to parse");
|
||||
}
|
||||
if (!conversionService.canConvert(type, formattedObjectType)) {
|
||||
if (!this.conversionService.canConvert(type, formattedObjectType)) {
|
||||
throw new IllegalArgumentException("Unable to register formatter " + formatter + " for type [" + type.getName() + "]; not able to convert to [" + formattedObjectType.getName() + "] to format");
|
||||
}
|
||||
typeFormatters.put(type, formatter);
|
||||
this.typeFormatters.put(type, formatter);
|
||||
}
|
||||
|
||||
public <A extends Annotation, T> void add(AnnotationFormatterFactory<A, T> factory) {
|
||||
annotationFormatters.put(getAnnotationType(factory.getClass()), factory);
|
||||
public void add(AnnotationFormatterFactory<?, ?> factory) {
|
||||
this.annotationFormatters.put(getAnnotationType(factory.getClass()), factory);
|
||||
}
|
||||
|
||||
public Formatter<?> getFormatter(TypeDescriptor type) {
|
||||
@SuppressWarnings("unchecked")
|
||||
public Formatter<Object> getFormatter(TypeDescriptor type) {
|
||||
Assert.notNull(type, "The TypeDescriptor is required");
|
||||
Formatter formatter = getAnnotationFormatter(type);
|
||||
Formatter<Object> formatter = getAnnotationFormatter(type);
|
||||
if (formatter == null) {
|
||||
formatter = getTypeFormatter(type.getType());
|
||||
}
|
||||
return formatter;
|
||||
}
|
||||
|
||||
|
||||
// internal helpers
|
||||
|
||||
private Class getFormattedObjectType(Class formatterClass) {
|
||||
|
|
@ -175,7 +254,8 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
|||
+ factoryClass.getName() + "]; does the factory parameterize the <A> generic type?");
|
||||
}
|
||||
|
||||
private Formatter<?> getAnnotationFormatter(TypeDescriptor type) {
|
||||
@SuppressWarnings("unchecked")
|
||||
private Formatter getAnnotationFormatter(TypeDescriptor type) {
|
||||
Annotation[] annotations = type.getAnnotations();
|
||||
for (Annotation a : annotations) {
|
||||
AnnotationFormatterFactory factory = annotationFormatters.get(a.annotationType());
|
||||
|
|
@ -186,17 +266,19 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
|||
return null;
|
||||
}
|
||||
|
||||
private Formatter<?> getTypeFormatter(Class<?> type) {
|
||||
private Formatter getTypeFormatter(Class<?> type) {
|
||||
Assert.notNull(type, "The Class of the object to format is required");
|
||||
Formatter formatter = findFormatter(type);
|
||||
if (formatter != null) {
|
||||
Class<?> formattedObjectType = getFormattedObjectType(formatter.getClass());
|
||||
if (type.isAssignableFrom(formattedObjectType)) {
|
||||
return formatter;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return new ConvertingFormatter(type, formattedObjectType, formatter);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return getDefaultFormatter(type);
|
||||
}
|
||||
}
|
||||
|
|
@ -236,11 +318,13 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
|||
throw new IllegalStateException(
|
||||
"Formatter referenced by @Formatted annotation does not have public constructor", e);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class ConvertingFormatter implements Formatter {
|
||||
|
||||
private Class<?> type;
|
||||
|
|
@ -255,6 +339,7 @@ public class GenericFormatterRegistry implements FormatterRegistry {
|
|||
this.targetFormatter = targetFormatter;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public String format(Object object, Locale locale) {
|
||||
object = conversionService.convert(object, formattedObjectType);
|
||||
return targetFormatter.format(object, locale);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Support classes for the formatting package, providing
|
||||
* common implementations as well as adapters.
|
||||
*/
|
||||
package org.springframework.ui.format;
|
||||
|
||||
|
|
@ -27,6 +27,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.PropertyEditorRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -65,6 +66,7 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
|
|||
* @see DefaultMessageCodesResolver
|
||||
*/
|
||||
public void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) {
|
||||
Assert.notNull(messageCodesResolver, "MessageCodesResolver must not be null");
|
||||
this.messageCodesResolver = messageCodesResolver;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -22,6 +22,13 @@ import org.springframework.beans.BeanUtils;
|
|||
import org.springframework.beans.ConfigurablePropertyAccessor;
|
||||
import org.springframework.beans.PropertyAccessorUtils;
|
||||
import org.springframework.beans.PropertyEditorRegistry;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.ui.format.Formatter;
|
||||
import org.springframework.ui.format.FormatterRegistry;
|
||||
import org.springframework.ui.format.support.FormattingConversionServiceAdapter;
|
||||
import org.springframework.ui.format.support.FormattingPropertyEditorAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link BindingResult} implementations that work with
|
||||
|
|
@ -37,6 +44,9 @@ import org.springframework.beans.PropertyEditorRegistry;
|
|||
*/
|
||||
public abstract class AbstractPropertyBindingResult extends AbstractBindingResult {
|
||||
|
||||
private FormatterRegistry formatterRegistry;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new AbstractPropertyBindingResult instance.
|
||||
* @param objectName the name of the target object
|
||||
|
|
@ -47,6 +57,12 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
|
|||
}
|
||||
|
||||
|
||||
public void initFormatterLookup(FormatterRegistry formatterRegistry) {
|
||||
Assert.notNull(formatterRegistry, "FormatterRegistry must not be null");
|
||||
this.formatterRegistry = formatterRegistry;
|
||||
getPropertyAccessor().setConversionService(new FormattingConversionServiceAdapter(formatterRegistry));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying PropertyAccessor.
|
||||
* @see #getPropertyAccessor()
|
||||
|
|
@ -89,7 +105,13 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
|
|||
*/
|
||||
@Override
|
||||
protected Object formatFieldValue(String field, Object value) {
|
||||
PropertyEditor customEditor = getCustomEditor(field);
|
||||
String fixedField = fixedField(field);
|
||||
TypeDescriptor td = getPropertyAccessor().getPropertyTypeDescriptor(fixedField);
|
||||
Formatter<Object> formatter = (this.formatterRegistry != null ? this.formatterRegistry.getFormatter(td) : null);
|
||||
if (formatter != null) {
|
||||
return formatter.format(value, LocaleContextHolder.getLocale());
|
||||
}
|
||||
PropertyEditor customEditor = getCustomEditor(fixedField);
|
||||
if (customEditor != null) {
|
||||
customEditor.setValue(value);
|
||||
String textValue = customEditor.getAsText();
|
||||
|
|
@ -104,11 +126,10 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
|
|||
|
||||
/**
|
||||
* Retrieve the custom PropertyEditor for the given field, if any.
|
||||
* @param field the field name
|
||||
* @param fixedField the fully qualified field name
|
||||
* @return the custom PropertyEditor, or <code>null</code>
|
||||
*/
|
||||
protected PropertyEditor getCustomEditor(String field) {
|
||||
String fixedField = fixedField(field);
|
||||
protected PropertyEditor getCustomEditor(String fixedField) {
|
||||
Class targetType = getPropertyAccessor().getPropertyType(fixedField);
|
||||
PropertyEditor editor = getPropertyAccessor().findCustomEditor(targetType, fixedField);
|
||||
if (editor == null) {
|
||||
|
|
@ -117,6 +138,22 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
|
|||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -88,10 +88,10 @@ public interface BindingResult extends Errors {
|
|||
|
||||
/**
|
||||
* Find a custom property editor for the given type and property.
|
||||
* @param valueType the type of the property (can be <code>null</code> if a property
|
||||
* is given but should be specified in any case for consistency checking)
|
||||
* @param field the path of the property (name or nested path), or
|
||||
* <code>null</code> if looking for an editor for all properties of the given type
|
||||
* @param valueType the type of the property (can be <code>null</code> if a property
|
||||
* is given but should be specified in any case for consistency checking)
|
||||
* @return the registered editor, or <code>null</code> if none
|
||||
*/
|
||||
PropertyEditor findEditor(String field, Class valueType);
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ import org.springframework.beans.SimpleTypeConverter;
|
|||
import org.springframework.beans.TypeConverter;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.ui.format.FormatterRegistry;
|
||||
import org.springframework.ui.format.support.GenericFormatterRegistry;
|
||||
import org.springframework.ui.format.support.FormattingConversionServiceAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
|
|
@ -132,6 +135,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
|||
|
||||
private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
|
||||
|
||||
private FormatterRegistry formatterRegistry;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new DataBinder instance, with default object name.
|
||||
|
|
@ -178,6 +183,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
|||
Assert.isNull(this.bindingResult,
|
||||
"DataBinder is already initialized - call initBeanPropertyAccess before any other configuration methods");
|
||||
this.bindingResult = new BeanPropertyBindingResult(getTarget(), getObjectName());
|
||||
if (this.formatterRegistry != null) {
|
||||
this.bindingResult.initFormatterLookup(this.formatterRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -189,6 +197,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
|||
Assert.isNull(this.bindingResult,
|
||||
"DataBinder is already initialized - call initDirectFieldAccess before any other configuration methods");
|
||||
this.bindingResult = new DirectFieldBindingResult(getTarget(), getObjectName());
|
||||
if (this.formatterRegistry != null) {
|
||||
this.bindingResult.initFormatterLookup(this.formatterRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -215,6 +226,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
|||
protected SimpleTypeConverter getSimpleTypeConverter() {
|
||||
if (this.typeConverter == null) {
|
||||
this.typeConverter = new SimpleTypeConverter();
|
||||
if (this.formatterRegistry != null) {
|
||||
this.typeConverter.setConversionService(new FormattingConversionServiceAdapter(this.formatterRegistry));
|
||||
}
|
||||
}
|
||||
return this.typeConverter;
|
||||
}
|
||||
|
|
@ -418,6 +432,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
|||
* @see DefaultBindingErrorProcessor
|
||||
*/
|
||||
public void setBindingErrorProcessor(BindingErrorProcessor bindingErrorProcessor) {
|
||||
Assert.notNull(bindingErrorProcessor, "BindingErrorProcessor must not be null");
|
||||
this.bindingErrorProcessor = bindingErrorProcessor;
|
||||
}
|
||||
|
||||
|
|
@ -428,6 +443,31 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
|||
return this.bindingErrorProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the FormatterRegistry to use for obtaining Formatters in preference
|
||||
* to JavaBeans PropertyEditors.
|
||||
*/
|
||||
public void setFormatterRegistry(FormatterRegistry formatterRegistry) {
|
||||
this.formatterRegistry = formatterRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the FormatterRegistry to use for obtaining Formatters in preference
|
||||
* to JavaBeans PropertyEditors.
|
||||
* @return the FormatterRegistry (never <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
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import org.springframework.core.convert.TypeDescriptor;
|
|||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.ui.format.number.CurrencyFormatter;
|
||||
import org.springframework.ui.format.number.IntegerFormatter;
|
||||
import org.springframework.ui.format.support.GenericFormatterRegistry;
|
||||
|
||||
public class GenericFormatterRegistryTests {
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.validation;
|
||||
|
||||
import java.beans.PropertyEditorSupport;
|
||||
import java.beans.PropertyEditor;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
|
|
@ -27,7 +28,6 @@ import java.util.Set;
|
|||
import java.util.TreeSet;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.springframework.beans.BeanWithObjectProperty;
|
||||
import org.springframework.beans.DerivedTestBean;
|
||||
|
|
@ -42,6 +42,8 @@ import org.springframework.beans.propertyeditors.CustomCollectionEditor;
|
|||
import org.springframework.beans.propertyeditors.CustomNumberEditor;
|
||||
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||
import org.springframework.context.support.StaticMessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.ui.format.number.DecimalFormatter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -289,7 +291,35 @@ public class DataBinderTests extends TestCase {
|
|||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.addPropertyValue("object", "1");
|
||||
binder.bind(pvs);
|
||||
Assert.assertEquals(new Integer(1), tb.getObject());
|
||||
assertEquals(new Integer(1), tb.getObject());
|
||||
}
|
||||
|
||||
public void testBindingWithFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.getFormatterRegistry().add(Float.class, new DecimalFormatter());
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.addPropertyValue("myFloat", "1,2");
|
||||
|
||||
LocaleContextHolder.setLocale(Locale.GERMAN);
|
||||
try {
|
||||
binder.bind(pvs);
|
||||
assertEquals(new Float(1.2), tb.getMyFloat());
|
||||
assertEquals("1,2", binder.getBindingResult().getFieldValue("myFloat"));
|
||||
|
||||
PropertyEditor editor = binder.getBindingResult().findEditor("myFloat", Float.class);
|
||||
assertNotNull(editor);
|
||||
editor.setValue(new Float(1.4));
|
||||
assertEquals("1,4", editor.getAsText());
|
||||
|
||||
editor = binder.getBindingResult().findEditor("myFloat", null);
|
||||
assertNotNull(editor);
|
||||
editor.setAsText("1,6");
|
||||
assertEquals(new Float(1.6), editor.getValue());
|
||||
}
|
||||
finally {
|
||||
LocaleContextHolder.resetLocaleContext();
|
||||
}
|
||||
}
|
||||
|
||||
public void testBindingWithAllowedFields() throws Exception {
|
||||
|
|
@ -673,36 +703,36 @@ public class DataBinderTests extends TestCase {
|
|||
|
||||
assertEquals(2, errors.getGlobalErrorCount());
|
||||
assertEquals("NAME_TOUCHY_MISMATCH", errors.getGlobalError().getCode());
|
||||
assertEquals("NAME_TOUCHY_MISMATCH", ((ObjectError) errors.getGlobalErrors().get(0)).getCode());
|
||||
assertEquals("NAME_TOUCHY_MISMATCH.tb", ((ObjectError) errors.getGlobalErrors().get(0)).getCodes()[0]);
|
||||
assertEquals("NAME_TOUCHY_MISMATCH", ((ObjectError) errors.getGlobalErrors().get(0)).getCodes()[1]);
|
||||
assertEquals("tb", ((ObjectError) errors.getGlobalErrors().get(0)).getObjectName());
|
||||
assertEquals("GENERAL_ERROR", ((ObjectError) errors.getGlobalErrors().get(1)).getCode());
|
||||
assertEquals("GENERAL_ERROR.tb", ((ObjectError) errors.getGlobalErrors().get(1)).getCodes()[0]);
|
||||
assertEquals("GENERAL_ERROR", ((ObjectError) errors.getGlobalErrors().get(1)).getCodes()[1]);
|
||||
assertEquals("msg", ((ObjectError) errors.getGlobalErrors().get(1)).getDefaultMessage());
|
||||
assertEquals("arg", ((ObjectError) errors.getGlobalErrors().get(1)).getArguments()[0]);
|
||||
assertEquals("NAME_TOUCHY_MISMATCH", (errors.getGlobalErrors().get(0)).getCode());
|
||||
assertEquals("NAME_TOUCHY_MISMATCH.tb", (errors.getGlobalErrors().get(0)).getCodes()[0]);
|
||||
assertEquals("NAME_TOUCHY_MISMATCH", (errors.getGlobalErrors().get(0)).getCodes()[1]);
|
||||
assertEquals("tb", (errors.getGlobalErrors().get(0)).getObjectName());
|
||||
assertEquals("GENERAL_ERROR", (errors.getGlobalErrors().get(1)).getCode());
|
||||
assertEquals("GENERAL_ERROR.tb", (errors.getGlobalErrors().get(1)).getCodes()[0]);
|
||||
assertEquals("GENERAL_ERROR", (errors.getGlobalErrors().get(1)).getCodes()[1]);
|
||||
assertEquals("msg", (errors.getGlobalErrors().get(1)).getDefaultMessage());
|
||||
assertEquals("arg", (errors.getGlobalErrors().get(1)).getArguments()[0]);
|
||||
|
||||
assertTrue(errors.hasFieldErrors());
|
||||
assertEquals(4, errors.getFieldErrorCount());
|
||||
assertEquals("TOO_YOUNG", errors.getFieldError().getCode());
|
||||
assertEquals("TOO_YOUNG", ((FieldError) errors.getFieldErrors().get(0)).getCode());
|
||||
assertEquals("age", ((FieldError) errors.getFieldErrors().get(0)).getField());
|
||||
assertEquals("AGE_NOT_ODD", ((FieldError) errors.getFieldErrors().get(1)).getCode());
|
||||
assertEquals("age", ((FieldError) errors.getFieldErrors().get(1)).getField());
|
||||
assertEquals("NOT_ROD", ((FieldError) errors.getFieldErrors().get(2)).getCode());
|
||||
assertEquals("name", ((FieldError) errors.getFieldErrors().get(2)).getField());
|
||||
assertEquals("TOO_YOUNG", ((FieldError) errors.getFieldErrors().get(3)).getCode());
|
||||
assertEquals("spouse.age", ((FieldError) errors.getFieldErrors().get(3)).getField());
|
||||
assertEquals("TOO_YOUNG", (errors.getFieldErrors().get(0)).getCode());
|
||||
assertEquals("age", (errors.getFieldErrors().get(0)).getField());
|
||||
assertEquals("AGE_NOT_ODD", (errors.getFieldErrors().get(1)).getCode());
|
||||
assertEquals("age", (errors.getFieldErrors().get(1)).getField());
|
||||
assertEquals("NOT_ROD", (errors.getFieldErrors().get(2)).getCode());
|
||||
assertEquals("name", (errors.getFieldErrors().get(2)).getField());
|
||||
assertEquals("TOO_YOUNG", (errors.getFieldErrors().get(3)).getCode());
|
||||
assertEquals("spouse.age", (errors.getFieldErrors().get(3)).getField());
|
||||
|
||||
assertTrue(errors.hasFieldErrors("age"));
|
||||
assertEquals(2, errors.getFieldErrorCount("age"));
|
||||
assertEquals("TOO_YOUNG", errors.getFieldError("age").getCode());
|
||||
assertEquals("TOO_YOUNG", ((FieldError) errors.getFieldErrors("age").get(0)).getCode());
|
||||
assertEquals("tb", ((FieldError) errors.getFieldErrors("age").get(0)).getObjectName());
|
||||
assertEquals("age", ((FieldError) errors.getFieldErrors("age").get(0)).getField());
|
||||
assertEquals(new Integer(0), ((FieldError) errors.getFieldErrors("age").get(0)).getRejectedValue());
|
||||
assertEquals("AGE_NOT_ODD", ((FieldError) errors.getFieldErrors("age").get(1)).getCode());
|
||||
assertEquals("TOO_YOUNG", (errors.getFieldErrors("age").get(0)).getCode());
|
||||
assertEquals("tb", (errors.getFieldErrors("age").get(0)).getObjectName());
|
||||
assertEquals("age", (errors.getFieldErrors("age").get(0)).getField());
|
||||
assertEquals(new Integer(0), (errors.getFieldErrors("age").get(0)).getRejectedValue());
|
||||
assertEquals("AGE_NOT_ODD", (errors.getFieldErrors("age").get(1)).getCode());
|
||||
|
||||
assertTrue(errors.hasFieldErrors("name"));
|
||||
assertEquals(1, errors.getFieldErrorCount("name"));
|
||||
|
|
@ -711,14 +741,14 @@ public class DataBinderTests extends TestCase {
|
|||
assertEquals("NOT_ROD.name", errors.getFieldError("name").getCodes()[1]);
|
||||
assertEquals("NOT_ROD.java.lang.String", errors.getFieldError("name").getCodes()[2]);
|
||||
assertEquals("NOT_ROD", errors.getFieldError("name").getCodes()[3]);
|
||||
assertEquals("name", ((FieldError) errors.getFieldErrors("name").get(0)).getField());
|
||||
assertEquals(null, ((FieldError) errors.getFieldErrors("name").get(0)).getRejectedValue());
|
||||
assertEquals("name", (errors.getFieldErrors("name").get(0)).getField());
|
||||
assertEquals(null, (errors.getFieldErrors("name").get(0)).getRejectedValue());
|
||||
|
||||
assertTrue(errors.hasFieldErrors("spouse.age"));
|
||||
assertEquals(1, errors.getFieldErrorCount("spouse.age"));
|
||||
assertEquals("TOO_YOUNG", errors.getFieldError("spouse.age").getCode());
|
||||
assertEquals("tb", ((FieldError) errors.getFieldErrors("spouse.age").get(0)).getObjectName());
|
||||
assertEquals(new Integer(0), ((FieldError) errors.getFieldErrors("spouse.age").get(0)).getRejectedValue());
|
||||
assertEquals("tb", (errors.getFieldErrors("spouse.age").get(0)).getObjectName());
|
||||
assertEquals(new Integer(0), (errors.getFieldErrors("spouse.age").get(0)).getRejectedValue());
|
||||
}
|
||||
|
||||
public void testValidatorWithErrorsAndCodesPrefix() {
|
||||
|
|
@ -744,36 +774,36 @@ public class DataBinderTests extends TestCase {
|
|||
|
||||
assertEquals(2, errors.getGlobalErrorCount());
|
||||
assertEquals("validation.NAME_TOUCHY_MISMATCH", errors.getGlobalError().getCode());
|
||||
assertEquals("validation.NAME_TOUCHY_MISMATCH", ((ObjectError) errors.getGlobalErrors().get(0)).getCode());
|
||||
assertEquals("validation.NAME_TOUCHY_MISMATCH.tb", ((ObjectError) errors.getGlobalErrors().get(0)).getCodes()[0]);
|
||||
assertEquals("validation.NAME_TOUCHY_MISMATCH", ((ObjectError) errors.getGlobalErrors().get(0)).getCodes()[1]);
|
||||
assertEquals("tb", ((ObjectError) errors.getGlobalErrors().get(0)).getObjectName());
|
||||
assertEquals("validation.GENERAL_ERROR", ((ObjectError) errors.getGlobalErrors().get(1)).getCode());
|
||||
assertEquals("validation.GENERAL_ERROR.tb", ((ObjectError) errors.getGlobalErrors().get(1)).getCodes()[0]);
|
||||
assertEquals("validation.GENERAL_ERROR", ((ObjectError) errors.getGlobalErrors().get(1)).getCodes()[1]);
|
||||
assertEquals("msg", ((ObjectError) errors.getGlobalErrors().get(1)).getDefaultMessage());
|
||||
assertEquals("arg", ((ObjectError) errors.getGlobalErrors().get(1)).getArguments()[0]);
|
||||
assertEquals("validation.NAME_TOUCHY_MISMATCH", (errors.getGlobalErrors().get(0)).getCode());
|
||||
assertEquals("validation.NAME_TOUCHY_MISMATCH.tb", (errors.getGlobalErrors().get(0)).getCodes()[0]);
|
||||
assertEquals("validation.NAME_TOUCHY_MISMATCH", (errors.getGlobalErrors().get(0)).getCodes()[1]);
|
||||
assertEquals("tb", (errors.getGlobalErrors().get(0)).getObjectName());
|
||||
assertEquals("validation.GENERAL_ERROR", (errors.getGlobalErrors().get(1)).getCode());
|
||||
assertEquals("validation.GENERAL_ERROR.tb", (errors.getGlobalErrors().get(1)).getCodes()[0]);
|
||||
assertEquals("validation.GENERAL_ERROR", (errors.getGlobalErrors().get(1)).getCodes()[1]);
|
||||
assertEquals("msg", (errors.getGlobalErrors().get(1)).getDefaultMessage());
|
||||
assertEquals("arg", (errors.getGlobalErrors().get(1)).getArguments()[0]);
|
||||
|
||||
assertTrue(errors.hasFieldErrors());
|
||||
assertEquals(4, errors.getFieldErrorCount());
|
||||
assertEquals("validation.TOO_YOUNG", errors.getFieldError().getCode());
|
||||
assertEquals("validation.TOO_YOUNG", ((FieldError) errors.getFieldErrors().get(0)).getCode());
|
||||
assertEquals("age", ((FieldError) errors.getFieldErrors().get(0)).getField());
|
||||
assertEquals("validation.AGE_NOT_ODD", ((FieldError) errors.getFieldErrors().get(1)).getCode());
|
||||
assertEquals("age", ((FieldError) errors.getFieldErrors().get(1)).getField());
|
||||
assertEquals("validation.NOT_ROD", ((FieldError) errors.getFieldErrors().get(2)).getCode());
|
||||
assertEquals("name", ((FieldError) errors.getFieldErrors().get(2)).getField());
|
||||
assertEquals("validation.TOO_YOUNG", ((FieldError) errors.getFieldErrors().get(3)).getCode());
|
||||
assertEquals("spouse.age", ((FieldError) errors.getFieldErrors().get(3)).getField());
|
||||
assertEquals("validation.TOO_YOUNG", (errors.getFieldErrors().get(0)).getCode());
|
||||
assertEquals("age", (errors.getFieldErrors().get(0)).getField());
|
||||
assertEquals("validation.AGE_NOT_ODD", (errors.getFieldErrors().get(1)).getCode());
|
||||
assertEquals("age", (errors.getFieldErrors().get(1)).getField());
|
||||
assertEquals("validation.NOT_ROD", (errors.getFieldErrors().get(2)).getCode());
|
||||
assertEquals("name", (errors.getFieldErrors().get(2)).getField());
|
||||
assertEquals("validation.TOO_YOUNG", (errors.getFieldErrors().get(3)).getCode());
|
||||
assertEquals("spouse.age", (errors.getFieldErrors().get(3)).getField());
|
||||
|
||||
assertTrue(errors.hasFieldErrors("age"));
|
||||
assertEquals(2, errors.getFieldErrorCount("age"));
|
||||
assertEquals("validation.TOO_YOUNG", errors.getFieldError("age").getCode());
|
||||
assertEquals("validation.TOO_YOUNG", ((FieldError) errors.getFieldErrors("age").get(0)).getCode());
|
||||
assertEquals("tb", ((FieldError) errors.getFieldErrors("age").get(0)).getObjectName());
|
||||
assertEquals("age", ((FieldError) errors.getFieldErrors("age").get(0)).getField());
|
||||
assertEquals(new Integer(0), ((FieldError) errors.getFieldErrors("age").get(0)).getRejectedValue());
|
||||
assertEquals("validation.AGE_NOT_ODD", ((FieldError) errors.getFieldErrors("age").get(1)).getCode());
|
||||
assertEquals("validation.TOO_YOUNG", (errors.getFieldErrors("age").get(0)).getCode());
|
||||
assertEquals("tb", (errors.getFieldErrors("age").get(0)).getObjectName());
|
||||
assertEquals("age", (errors.getFieldErrors("age").get(0)).getField());
|
||||
assertEquals(new Integer(0), (errors.getFieldErrors("age").get(0)).getRejectedValue());
|
||||
assertEquals("validation.AGE_NOT_ODD", (errors.getFieldErrors("age").get(1)).getCode());
|
||||
|
||||
assertTrue(errors.hasFieldErrors("name"));
|
||||
assertEquals(1, errors.getFieldErrorCount("name"));
|
||||
|
|
@ -782,14 +812,14 @@ public class DataBinderTests extends TestCase {
|
|||
assertEquals("validation.NOT_ROD.name", errors.getFieldError("name").getCodes()[1]);
|
||||
assertEquals("validation.NOT_ROD.java.lang.String", errors.getFieldError("name").getCodes()[2]);
|
||||
assertEquals("validation.NOT_ROD", errors.getFieldError("name").getCodes()[3]);
|
||||
assertEquals("name", ((FieldError) errors.getFieldErrors("name").get(0)).getField());
|
||||
assertEquals(null, ((FieldError) errors.getFieldErrors("name").get(0)).getRejectedValue());
|
||||
assertEquals("name", (errors.getFieldErrors("name").get(0)).getField());
|
||||
assertEquals(null, (errors.getFieldErrors("name").get(0)).getRejectedValue());
|
||||
|
||||
assertTrue(errors.hasFieldErrors("spouse.age"));
|
||||
assertEquals(1, errors.getFieldErrorCount("spouse.age"));
|
||||
assertEquals("validation.TOO_YOUNG", errors.getFieldError("spouse.age").getCode());
|
||||
assertEquals("tb", ((FieldError) errors.getFieldErrors("spouse.age").get(0)).getObjectName());
|
||||
assertEquals(new Integer(0), ((FieldError) errors.getFieldErrors("spouse.age").get(0)).getRejectedValue());
|
||||
assertEquals("tb", (errors.getFieldErrors("spouse.age").get(0)).getObjectName());
|
||||
assertEquals(new Integer(0), (errors.getFieldErrors("spouse.age").get(0)).getRejectedValue());
|
||||
}
|
||||
|
||||
public void testValidatorWithNestedObjectNull() {
|
||||
|
|
@ -806,8 +836,8 @@ public class DataBinderTests extends TestCase {
|
|||
assertTrue(errors.hasFieldErrors("spouse"));
|
||||
assertEquals(1, errors.getFieldErrorCount("spouse"));
|
||||
assertEquals("SPOUSE_NOT_AVAILABLE", errors.getFieldError("spouse").getCode());
|
||||
assertEquals("tb", ((FieldError) errors.getFieldErrors("spouse").get(0)).getObjectName());
|
||||
assertEquals(null, ((FieldError) errors.getFieldErrors("spouse").get(0)).getRejectedValue());
|
||||
assertEquals("tb", (errors.getFieldErrors("spouse").get(0)).getObjectName());
|
||||
assertEquals(null, (errors.getFieldErrors("spouse").get(0)).getRejectedValue());
|
||||
}
|
||||
|
||||
public void testNestedValidatorWithoutNestedPath() {
|
||||
|
|
@ -820,7 +850,7 @@ public class DataBinderTests extends TestCase {
|
|||
assertTrue(errors.hasGlobalErrors());
|
||||
assertEquals(1, errors.getGlobalErrorCount());
|
||||
assertEquals("SPOUSE_NOT_AVAILABLE", errors.getGlobalError().getCode());
|
||||
assertEquals("tb", ((ObjectError) errors.getGlobalErrors().get(0)).getObjectName());
|
||||
assertEquals("tb", (errors.getGlobalErrors().get(0)).getObjectName());
|
||||
}
|
||||
|
||||
public void testBindingStringArrayToIntegerSet() {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import java.util.HashMap;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
|
|
@ -58,6 +59,28 @@ public class GenericConversionService implements ConversionService, ConverterReg
|
|||
private final Map<Class, Map<Class, Object>> sourceTypeConverters = new HashMap<Class, Map<Class, Object>>();
|
||||
|
||||
|
||||
/**
|
||||
* Registers the converters in the set provided.
|
||||
* JavaBean-friendly alternative to calling {@link #add(Converter)}.
|
||||
* @see #add(Converter)
|
||||
*/
|
||||
public void setConverters(Set<Converter> converters) {
|
||||
for (Converter converter : converters) {
|
||||
add(converter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the converters in the set provided.
|
||||
* JavaBean-friendly alternative to calling {@link #add(ConverterFactory)}.
|
||||
* @see #add(ConverterFactory)
|
||||
*/
|
||||
public void setConverterFactories(Set<ConverterFactory> converters) {
|
||||
for (ConverterFactory converterFactory : converters) {
|
||||
add(converterFactory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent of this conversion service. This is optional.
|
||||
*/
|
||||
|
|
@ -100,16 +123,6 @@ public class GenericConversionService implements ConversionService, ConverterReg
|
|||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
|
||||
package org.springframework.web.servlet.mvc.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -33,7 +33,6 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
|
|
@ -54,7 +53,6 @@ import org.springframework.core.annotation.AnnotationUtils;
|
|||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.BufferedImageHttpMessageConverter;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
|
@ -72,9 +70,9 @@ import org.springframework.util.ObjectUtils;
|
|||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.support.BindingAwareModelMap;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.HttpSessionRequiredException;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
|
|
@ -119,11 +117,11 @@ import org.springframework.web.util.WebUtils;
|
|||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Arjen Poutsma
|
||||
* @since 2.5
|
||||
* @see #setPathMatcher
|
||||
* @see #setMethodNameResolver
|
||||
* @see #setWebBindingInitializer
|
||||
* @see #setSessionAttributeStore
|
||||
* @since 2.5
|
||||
*/
|
||||
public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter {
|
||||
|
||||
|
|
@ -165,19 +163,20 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
new ConcurrentHashMap<Class<?>, ServletHandlerMethodResolver>();
|
||||
|
||||
private HttpMessageConverter<?>[] messageConverters =
|
||||
new HttpMessageConverter[]{new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
|
||||
new HttpMessageConverter[] {new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
|
||||
new FormHttpMessageConverter(), new SourceHttpMessageConverter()};
|
||||
|
||||
|
||||
public AnnotationMethodHandlerAdapter() {
|
||||
// no restriction of HTTP methods by default
|
||||
super(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set if URL lookup should always use the full path within the current servlet context. Else, the path within the
|
||||
* current servlet mapping is used if applicable (that is, in the case of a ".../*" servlet mapping in web.xml).
|
||||
* <p>Default is "false".
|
||||
*
|
||||
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
|
||||
*/
|
||||
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
|
||||
|
|
@ -188,7 +187,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
* Set if context path and request URI should be URL-decoded. Both are returned <i>undecoded</i> by the Servlet API, in
|
||||
* contrast to the servlet path. <p>Uses either the request encoding or the default encoding according to the Servlet
|
||||
* spec (ISO-8859-1).
|
||||
*
|
||||
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
|
||||
*/
|
||||
public void setUrlDecode(boolean urlDecode) {
|
||||
|
|
@ -207,7 +205,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
/**
|
||||
* Set the PathMatcher implementation to use for matching URL paths against registered URL patterns. Default is
|
||||
* AntPathMatcher.
|
||||
*
|
||||
* @see org.springframework.util.AntPathMatcher
|
||||
*/
|
||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||
|
|
@ -217,7 +214,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
|
||||
/**
|
||||
* Set the MethodNameResolver to use for resolving default handler methods (carrying an empty
|
||||
* <code>@RequestMapping</code> annotation). <p>Will only kick in when the handler method cannot be resolved uniquely
|
||||
* <code>@RequestMapping</code> annotation).
|
||||
* <p>Will only kick in when the handler method cannot be resolved uniquely
|
||||
* through the annotation metadata already.
|
||||
*/
|
||||
public void setMethodNameResolver(MethodNameResolver methodNameResolver) {
|
||||
|
|
@ -225,15 +223,16 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
}
|
||||
|
||||
/**
|
||||
* Specify a WebBindingInitializer which will apply pre-configured configuration to every DataBinder that this
|
||||
* controller uses.
|
||||
* Specify a WebBindingInitializer which will apply pre-configured configuration to every
|
||||
* DataBinder that this controller uses.
|
||||
*/
|
||||
public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
|
||||
this.webBindingInitializer = webBindingInitializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the strategy to store session attributes with. <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
|
||||
* Specify the strategy to store session attributes with.
|
||||
* <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
|
||||
* storing session attributes in the HttpSession, using the same attribute name as in the model.
|
||||
*/
|
||||
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
|
||||
|
|
@ -243,10 +242,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
|
||||
/**
|
||||
* Cache content produced by <code>@SessionAttributes</code> annotated handlers for the given number of seconds.
|
||||
* Default is 0, preventing caching completely. <p>In contrast to the "cacheSeconds" property which will apply to all
|
||||
* Default is 0, preventing caching completely.
|
||||
* <p>In contrast to the "cacheSeconds" property which will apply to all
|
||||
* general handlers (but not to <code>@SessionAttributes</code> annotated handlers), this setting will apply to
|
||||
* <code>@SessionAttributes</code> annotated handlers only.
|
||||
*
|
||||
* @see #setCacheSeconds
|
||||
* @see org.springframework.web.bind.annotation.SessionAttributes
|
||||
*/
|
||||
|
|
@ -255,15 +254,17 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
}
|
||||
|
||||
/**
|
||||
* Set if controller execution should be synchronized on the session, to serialize parallel invocations from the same
|
||||
* client. <p>More specifically, the execution of each handler method will get synchronized if this flag is "true". The
|
||||
* best available session mutex will be used for the synchronization; ideally, this will be a mutex exposed by
|
||||
* HttpSessionMutexListener. <p>The session mutex is guaranteed to be the same object during the entire lifetime of the
|
||||
* session, available under the key defined by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a safe
|
||||
* reference to synchronize on for locking on the current session. <p>In many cases, the HttpSession reference itself
|
||||
* is a safe mutex as well, since it will always be the same object reference for the same active logical session.
|
||||
* However, this is not guaranteed across different servlet containers; the only 100% safe way is a session mutex.
|
||||
*
|
||||
* Set if controller execution should be synchronized on the session, to serialize
|
||||
* parallel invocations from the same client.
|
||||
* <p>More specifically, the execution of each handler method will get synchronized if this
|
||||
* flag is "true". The best available session mutex will be used for the synchronization;
|
||||
* ideally, this will be a mutex exposed by HttpSessionMutexListener.
|
||||
* <p>The session mutex is guaranteed to be the same object during the entire lifetime of the
|
||||
* session, available under the key defined by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant.
|
||||
* It serves as a safe reference to synchronize on for locking on the current session.
|
||||
* <p>In many cases, the HttpSession reference itself a safe mutex as well, since it will
|
||||
* always be the same object reference for the same active logical session. However, this is
|
||||
* not guaranteed across different servlet containers; the only 100% safe way is a session mutex.
|
||||
* @see org.springframework.web.util.HttpSessionMutexListener
|
||||
* @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession)
|
||||
*/
|
||||
|
|
@ -273,7 +274,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
|
||||
/**
|
||||
* Set the ParameterNameDiscoverer to use for resolving method parameter names if needed (e.g. for default attribute
|
||||
* names). <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
|
||||
* names).
|
||||
* <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
|
||||
*/
|
||||
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
|
||||
this.parameterNameDiscoverer = parameterNameDiscoverer;
|
||||
|
|
@ -316,10 +318,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
* responses.
|
||||
*/
|
||||
public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
|
||||
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
|
||||
this.messageConverters = messageConverters;
|
||||
}
|
||||
|
||||
|
||||
public boolean supports(Object handler) {
|
||||
return getMethodResolver(handler).hasHandlerMethods();
|
||||
}
|
||||
|
|
@ -372,12 +374,12 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
}
|
||||
|
||||
/**
|
||||
* Template method for creating a new ServletRequestDataBinder instance. <p>The default implementation creates a
|
||||
* standard ServletRequestDataBinder. This can be overridden for custom ServletRequestDataBinder subclasses.
|
||||
*
|
||||
* Template method for creating a new ServletRequestDataBinder instance.
|
||||
* <p>The default implementation creates a standard ServletRequestDataBinder.
|
||||
* This can be overridden for custom ServletRequestDataBinder subclasses.
|
||||
* @param request current HTTP request
|
||||
* @param target the target object to bind onto (or <code>null</code> if the binder is just used to convert a plain
|
||||
* parameter value)
|
||||
* @param target the target object to bind onto (or <code>null</code>
|
||||
* if the binder is just used to convert a plain parameter value)
|
||||
* @param objectName the objectName of the target object
|
||||
* @return the ServletRequestDataBinder instance to use
|
||||
* @throws Exception in case of invalid state or arguments
|
||||
|
|
@ -390,7 +392,9 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
return new ServletRequestDataBinder(target, objectName);
|
||||
}
|
||||
|
||||
/** Build a HandlerMethodResolver for the given handler type. */
|
||||
/**
|
||||
* Build a HandlerMethodResolver for the given handler type.
|
||||
*/
|
||||
private ServletHandlerMethodResolver getMethodResolver(Object handler) {
|
||||
Class handlerClass = ClassUtils.getUserClass(handler);
|
||||
ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
|
||||
|
|
@ -401,7 +405,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
return resolver;
|
||||
}
|
||||
|
||||
/** Servlet-specific subclass of {@link HandlerMethodResolver}. */
|
||||
|
||||
/**
|
||||
* Servlet-specific subclass of {@link HandlerMethodResolver}.
|
||||
*/
|
||||
private class ServletHandlerMethodResolver extends HandlerMethodResolver {
|
||||
|
||||
private ServletHandlerMethodResolver(Class<?> handlerType) {
|
||||
|
|
@ -587,7 +594,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
}
|
||||
}
|
||||
|
||||
/** Servlet-specific subclass of {@link HandlerMethodInvoker}. */
|
||||
|
||||
/**
|
||||
* Servlet-specific subclass of {@link HandlerMethodInvoker}.
|
||||
*/
|
||||
private class ServletHandlerMethodInvoker extends HandlerMethodInvoker {
|
||||
|
||||
private boolean responseArgumentUsed = false;
|
||||
|
|
@ -780,6 +790,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
|
||||
Class<?> returnValueType = returnValue.getClass();
|
||||
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
|
||||
if (messageConverters != null) {
|
||||
for (HttpMessageConverter messageConverter : messageConverters) {
|
||||
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
|
||||
if (messageConverter.supports(returnValueType)) {
|
||||
|
|
@ -795,10 +806,12 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class RequestMappingInfo {
|
||||
|
||||
String[] paths = new String[0];
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* http://www.apache.org/licensesch/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
|
@ -16,14 +16,6 @@
|
|||
|
||||
package org.springframework.web.servlet.mvc.annotation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.io.Writer;
|
||||
|
|
@ -43,7 +35,6 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
|
|
@ -52,7 +43,9 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
||||
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
|
||||
import org.springframework.aop.support.DefaultPointcutAdvisor;
|
||||
|
|
@ -80,6 +73,8 @@ import org.springframework.stereotype.Controller;
|
|||
import org.springframework.ui.ExtendedModelMap;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.ui.format.support.GenericFormatterRegistry;
|
||||
import org.springframework.ui.format.date.DateFormatter;
|
||||
import org.springframework.util.SerializationTestUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
|
|
@ -95,6 +90,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
|||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
import org.springframework.web.bind.support.WebArgumentResolver;
|
||||
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
|
@ -405,7 +401,7 @@ public class ServletAnnotationControllerTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void commandProvidingFormController() throws Exception {
|
||||
public void commandProvidingFormControllerWithCustomEditor() throws Exception {
|
||||
@SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() {
|
||||
@Override
|
||||
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
|
||||
|
|
@ -431,6 +427,37 @@ public class ServletAnnotationControllerTests {
|
|||
assertEquals("myView-String:myDefaultName-typeMismatch-tb1-myOriginalValue", response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commandProvidingFormControllerWithFormatter() throws Exception {
|
||||
@SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() {
|
||||
@Override
|
||||
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
|
||||
GenericWebApplicationContext wac = new GenericWebApplicationContext();
|
||||
wac.registerBeanDefinition("controller",
|
||||
new RootBeanDefinition(MyCommandProvidingFormController.class));
|
||||
wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class));
|
||||
RootBeanDefinition registryDef = new RootBeanDefinition(GenericFormatterRegistry.class);
|
||||
registryDef.getPropertyValues().addPropertyValue("formatters", new DateFormatter("yyyy-MM-dd"));
|
||||
RootBeanDefinition initializerDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
|
||||
initializerDef.getPropertyValues().addPropertyValue("formatterRegistry", registryDef);
|
||||
RootBeanDefinition adapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
|
||||
adapterDef.getPropertyValues().addPropertyValue("webBindingInitializer", initializerDef);
|
||||
wac.registerBeanDefinition("handlerAdapter", adapterDef);
|
||||
wac.refresh();
|
||||
return wac;
|
||||
}
|
||||
};
|
||||
servlet.init(new MockServletConfig());
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do");
|
||||
request.addParameter("defaultName", "myDefaultName");
|
||||
request.addParameter("age", "value2");
|
||||
request.addParameter("date", "2007-10-02");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
servlet.service(request, response);
|
||||
assertEquals("myView-String:myDefaultName-typeMismatch-tb1-myOriginalValue", response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typedCommandProvidingFormController() throws Exception {
|
||||
@SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() {
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ import org.springframework.web.multipart.MultipartRequest;
|
|||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Arjen Poutsma
|
||||
* @see #invokeHandlerMethod
|
||||
* @since 2.5.2
|
||||
* @see #invokeHandlerMethod
|
||||
*/
|
||||
public class HandlerMethodInvoker {
|
||||
|
||||
|
|
@ -93,18 +93,17 @@ public class HandlerMethodInvoker {
|
|||
|
||||
private final WebArgumentResolver[] customArgumentResolvers;
|
||||
|
||||
private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus();
|
||||
|
||||
private final HttpMessageConverter[] messageConverters;
|
||||
|
||||
private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus();
|
||||
|
||||
|
||||
public HandlerMethodInvoker(HandlerMethodResolver methodResolver) {
|
||||
this(methodResolver, null);
|
||||
}
|
||||
|
||||
public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer) {
|
||||
this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null, new WebArgumentResolver[0],
|
||||
new HttpMessageConverter[0]);
|
||||
this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null, null, null);
|
||||
}
|
||||
|
||||
public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer,
|
||||
|
|
@ -379,7 +378,7 @@ public class HandlerMethodInvoker {
|
|||
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)
|
||||
throws Exception {
|
||||
|
||||
Class paramType = methodParam.getParameterType();
|
||||
Class<?> paramType = methodParam.getParameterType();
|
||||
if (paramName.length() == 0) {
|
||||
paramName = getRequiredParameterName(methodParam);
|
||||
}
|
||||
|
|
@ -411,7 +410,7 @@ public class HandlerMethodInvoker {
|
|||
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)
|
||||
throws Exception {
|
||||
|
||||
Class paramType = methodParam.getParameterType();
|
||||
Class<?> paramType = methodParam.getParameterType();
|
||||
if (headerName.length() == 0) {
|
||||
headerName = getRequiredParameterName(methodParam);
|
||||
}
|
||||
|
|
@ -455,7 +454,8 @@ public class HandlerMethodInvoker {
|
|||
"Cannot extract @RequestBody parameter (" + builder.toString() + "): no Content-Type found");
|
||||
}
|
||||
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
|
||||
for (HttpMessageConverter<?> messageConverter : messageConverters) {
|
||||
if (this.messageConverters != null) {
|
||||
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
|
||||
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
|
||||
if (messageConverter.supports(paramType)) {
|
||||
for (MediaType supportedMediaType : messageConverter.getSupportedMediaTypes()) {
|
||||
|
|
@ -465,6 +465,7 @@ public class HandlerMethodInvoker {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes);
|
||||
}
|
||||
|
||||
|
|
@ -480,7 +481,7 @@ public class HandlerMethodInvoker {
|
|||
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)
|
||||
throws Exception {
|
||||
|
||||
Class paramType = methodParam.getParameterType();
|
||||
Class<?> paramType = methodParam.getParameterType();
|
||||
if (cookieName.length() == 0) {
|
||||
cookieName = getRequiredParameterName(methodParam);
|
||||
}
|
||||
|
|
@ -512,7 +513,7 @@ public class HandlerMethodInvoker {
|
|||
private Object resolvePathVariable(String pathVarName, MethodParameter methodParam,
|
||||
NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception {
|
||||
|
||||
Class paramType = methodParam.getParameterType();
|
||||
Class<?> paramType = methodParam.getParameterType();
|
||||
if (pathVarName.length() == 0) {
|
||||
pathVarName = getRequiredParameterName(methodParam);
|
||||
}
|
||||
|
|
@ -565,8 +566,8 @@ public class HandlerMethodInvoker {
|
|||
if ("".equals(name)) {
|
||||
name = Conventions.getVariableNameForParameter(methodParam);
|
||||
}
|
||||
Class paramType = methodParam.getParameterType();
|
||||
Object bindObject = null;
|
||||
Class<?> paramType = methodParam.getParameterType();
|
||||
Object bindObject;
|
||||
if (implicitModel.containsKey(name)) {
|
||||
bindObject = implicitModel.get(name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.web.bind.support;
|
||||
|
||||
import org.springframework.beans.PropertyEditorRegistrar;
|
||||
import org.springframework.ui.format.FormatterRegistry;
|
||||
import org.springframework.validation.BindingErrorProcessor;
|
||||
import org.springframework.validation.MessageCodesResolver;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
|
|
@ -42,6 +43,8 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
|
|||
|
||||
private BindingErrorProcessor bindingErrorProcessor;
|
||||
|
||||
private FormatterRegistry formatterRegistry;
|
||||
|
||||
private PropertyEditorRegistrar[] propertyEditorRegistrars;
|
||||
|
||||
|
||||
|
|
@ -91,24 +94,35 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
|
|||
}
|
||||
|
||||
/**
|
||||
* Specify a single PropertyEditorRegistrar to be applied
|
||||
* to every DataBinder that this controller uses.
|
||||
* Specify a FormatterRegistry which will apply to every DataBinder.
|
||||
*/
|
||||
public final void setFormatterRegistry(FormatterRegistry formatterRegistry) {
|
||||
this.formatterRegistry = formatterRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a FormatterRegistry which will apply to every DataBinder.
|
||||
*/
|
||||
public final FormatterRegistry getFormatterRegistry() {
|
||||
return this.formatterRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a single PropertyEditorRegistrar to be applied to every DataBinder.
|
||||
*/
|
||||
public final void setPropertyEditorRegistrar(PropertyEditorRegistrar propertyEditorRegistrar) {
|
||||
this.propertyEditorRegistrars = new PropertyEditorRegistrar[] {propertyEditorRegistrar};
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify multiple PropertyEditorRegistrars to be applied
|
||||
* to every DataBinder that this controller uses.
|
||||
* Specify multiple PropertyEditorRegistrars to be applied to every DataBinder.
|
||||
*/
|
||||
public final void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
|
||||
this.propertyEditorRegistrars = propertyEditorRegistrars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the PropertyEditorRegistrars to be applied
|
||||
* to every DataBinder that this controller uses.
|
||||
* Return the PropertyEditorRegistrars to be applied to every DataBinder.
|
||||
*/
|
||||
public final PropertyEditorRegistrar[] getPropertyEditorRegistrars() {
|
||||
return this.propertyEditorRegistrars;
|
||||
|
|
@ -125,9 +139,12 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
|
|||
if (this.bindingErrorProcessor != null) {
|
||||
binder.setBindingErrorProcessor(this.bindingErrorProcessor);
|
||||
}
|
||||
if (this.formatterRegistry != null) {
|
||||
binder.setFormatterRegistry(this.formatterRegistry);
|
||||
}
|
||||
if (this.propertyEditorRegistrars != null) {
|
||||
for (int i = 0; i < this.propertyEditorRegistrars.length; i++) {
|
||||
this.propertyEditorRegistrars[i].registerCustomEditors(binder);
|
||||
for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
|
||||
propertyEditorRegistrar.registerCustomEditors(binder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue