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

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

View File

@@ -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

View File

@@ -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.

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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;

View File

@@ -1,25 +1,30 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.format;
import java.lang.annotation.Annotation;
/**
* A factory that creates {@link Formatter formatters} to format property values on properties annotated with a particular format {@link Annotation}.
* For example, a <code>CurrencyAnnotationFormatterFactory</code> might create a <code>Formatter</code> that formats a <code>BigDecimal</code> value set on a property annotated with <code>@CurrencyFormat</code>.
* A factory that creates {@link Formatter formatters} to format property values on properties
* annotated with a particular format {@link Annotation}.
*
* <p>For example, a <code>CurrencyAnnotationFormatterFactory</code> might create a <code>Formatter</code>
* that formats a <code>BigDecimal</code> value set on a property annotated with <code>@CurrencyFormat</code>.
*
* @author Keith Donald
* @since 3.0
* @param <A> The type of Annotation this factory uses to create Formatter instances
@@ -34,4 +39,5 @@ public interface AnnotationFormatterFactory<A extends Annotation, T> {
* @return the Formatter to use to format values of properties annotated with the annotation.
*/
Formatter<T> getFormatter(A annotation);
}
}

View File

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

View File

@@ -1,18 +1,19 @@
/*
* Copyright 2004-2009 the original author or authors.
*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.format;
import java.text.ParseException;
@@ -20,6 +21,7 @@ import java.util.Locale;
/**
* Formats objects of type T for display.
*
* @author Keith Donald
* @since 3.0
* @param <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;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2004-2009 the original author or authors.
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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 &lt;T&gt;, then coerses the parsed value to type.
* @param type the object type
* @param targetFormatter the target formatter
* @param <T> the type of object the target formatter formats
*/
<T> void add(Class<?> type, Formatter<T> targetFormatter);
void add(Class<?> type, Formatter<?> targetFormatter);
/**
* Adds a AnnotationFormatterFactory that returns the Formatter for properties annotated with a specific annotation.
* @param factory the annotation formatter factory
*/
<A extends Annotation, T> void add(AnnotationFormatterFactory<A, T> factory);
void add(AnnotationFormatterFactory<?, ?> factory);
/**
* Get the Formatter for the type descriptor.
* @return the Formatter, or <code>null</code> if no suitable one is registered
*/
@SuppressWarnings("unchecked")
Formatter getFormatter(TypeDescriptor type);
Formatter<Object> getFormatter(TypeDescriptor type);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2004-2009 the original author or authors.
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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);
}
dateFormat.setLenient(false);
return dateFormat;
}
private String determinePattern(String pattern) {
return pattern != null ? pattern : DEFAULT_PATTERN;
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.format.support;
import java.text.ParseException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.ui.format.Formatter;
import org.springframework.ui.format.FormatterRegistry;
import org.springframework.ui.format.support.GenericFormatterRegistry;
import org.springframework.util.Assert;
/**
* Adapter that exposes a {@link ConversionService} reference for a given
* {@link org.springframework.ui.format.FormatterRegistry}, retrieving the current
* Locale from {@link org.springframework.context.i18n.LocaleContextHolder}.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class FormattingConversionServiceAdapter implements ConversionService {
private final FormatterRegistry formatterRegistry;
private final ConversionService targetConversionService;
/**
* Create a new FormattingConversionServiceAdapter for the given FormatterRegistry.
* @param formatterRegistry the FormatterRegistry to wrap
*/
public FormattingConversionServiceAdapter(FormatterRegistry formatterRegistry) {
Assert.notNull(formatterRegistry, "FormatterRegistry must not be null");
this.formatterRegistry = formatterRegistry;
if (formatterRegistry instanceof GenericFormatterRegistry) {
this.targetConversionService = ((GenericFormatterRegistry) formatterRegistry).getConversionService();
}
else {
this.targetConversionService = new DefaultConversionService();
}
}
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
return canConvert(sourceType, TypeDescriptor.valueOf(targetType));
}
public boolean canConvert(Class<?> sourceType, TypeDescriptor targetType) {
return (this.formatterRegistry.getFormatter(targetType) != null ||
this.targetConversionService.canConvert(sourceType, targetType));
}
@SuppressWarnings("unchecked")
public <T> T convert(Object source, Class<T> targetType) {
return (T) convert(source, TypeDescriptor.valueOf(targetType));
}
public Object convert(Object source, TypeDescriptor targetType) {
if (source instanceof String) {
Formatter formatter = this.formatterRegistry.getFormatter(targetType);
if (formatter != null) {
try {
return formatter.parse((String) source, LocaleContextHolder.getLocale());
}
catch (ParseException ex) {
throw new ConversionFailedException(source, String.class, targetType.getType(), ex);
}
}
}
return this.targetConversionService.convert(source, targetType);
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.format.support;
import java.beans.PropertyEditorSupport;
import java.text.ParseException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.ui.format.Formatter;
import org.springframework.util.Assert;
/**
* Adapter that exposes a {@link java.beans.PropertyEditor} for any given
* {@link org.springframework.ui.format.Formatter}, retrieving the current
* Locale from {@link org.springframework.context.i18n.LocaleContextHolder}.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class FormattingPropertyEditorAdapter extends PropertyEditorSupport {
private final Formatter<Object> formatter;
/**
* Create a new FormattingPropertyEditorAdapter for the given Formatter.
* @param formatter the Formatter to wrap
*/
public FormattingPropertyEditorAdapter(Formatter<Object> formatter) {
Assert.notNull(formatter, "Formatter must not be null");
this.formatter = formatter;
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
try {
setValue(this.formatter.parse(text, LocaleContextHolder.getLocale()));
}
catch (ParseException ex) {
throw new IllegalArgumentException("Failed to parse formatted value", ex);
}
}
@Override
public String getAsText() {
return this.formatter.format(getValue(), LocaleContextHolder.getLocale());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2004-2009 the original author or authors.
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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);
@@ -268,4 +353,4 @@ public class GenericFormatterRegistry implements FormatterRegistry {
}
}
}

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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);

View File

@@ -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