diff --git a/org.springframework.context/src/main/java/org/springframework/format/FormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/format/FormatterRegistry.java
index a31cfa17ebf..f5f15a41403 100644
--- a/org.springframework.context/src/main/java/org/springframework/format/FormatterRegistry.java
+++ b/org.springframework.context/src/main/java/org/springframework/format/FormatterRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2011 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.
@@ -30,7 +30,7 @@ import org.springframework.core.convert.converter.ConverterRegistry;
public interface FormatterRegistry extends ConverterRegistry {
/**
- * Adds a Formatter to format fields of a specific type.
+ * Adds a Formatter to format fields of the given type.
*
On print, if the Formatter's type T is declared and fieldType is not assignable to T,
* a coersion to T will be attempted before delegating to formatter to print a field value.
* On parse, if the parsed object returned by formatter is not assignable to the runtime field type,
@@ -40,6 +40,14 @@ public interface FormatterRegistry extends ConverterRegistry {
*/
void addFormatterForFieldType(Class> fieldType, Formatter> formatter);
+ /**
+ * Adds a Formatter to format fields of a specific type.
+ * The field type is implied by the parameterized Formatter instance.
+ * @param formatter the formatter to add
+ * @see #addFormatterForFieldType(Class, Formatter)
+ */
+ void addFormatter(Formatter> formatter);
+
/**
* Adds a Printer/Parser pair to format fields of a specific type.
* The formatter will delegate to the specified printer for printing
diff --git a/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionService.java b/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionService.java
index 5749c249c11..279cc5b811a 100644
--- a/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionService.java
+++ b/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionService.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2011 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.
@@ -68,12 +68,21 @@ public class FormattingConversionService extends GenericConversionService
addConverter(new ParserConverter(fieldType, formatter, this));
}
+ public void addFormatter(Formatter> formatter) {
+ final Class> fieldType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
+ if (fieldType == null) {
+ throw new IllegalArgumentException("Unable to extract parameterized field type argument from Formatter ["
+ + formatter.getClass().getName() + "]; does the formatter parameterize the generic type?");
+ }
+ addFormatterForFieldType(fieldType, formatter);
+ }
+
public void addFormatterForFieldType(Class> fieldType, Printer> printer, Parser> parser) {
addConverter(new PrinterConverter(fieldType, printer, this));
addConverter(new ParserConverter(fieldType, parser, this));
}
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({ "unchecked", "rawtypes" })
public void addFormatterForFieldAnnotation(final AnnotationFormatterFactory annotationFormatterFactory) {
final Class extends Annotation> annotationType = (Class extends Annotation>)
GenericTypeResolver.resolveTypeArgument(annotationFormatterFactory.getClass(), AnnotationFormatterFactory.class);
@@ -174,7 +183,7 @@ public class FormattingConversionService extends GenericConversionService
private TypeDescriptor printerObjectType;
- @SuppressWarnings("unchecked")
+ @SuppressWarnings("rawtypes")
private Printer printer;
private ConversionService conversionService;
diff --git a/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java b/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java
index 8c40588422f..713b39eec45 100644
--- a/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java
+++ b/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2011 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.
@@ -27,6 +27,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.format.AnnotationFormatterFactory;
+import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
@@ -38,10 +39,8 @@ import org.springframework.util.StringValueResolver;
/**
* A factory for a {@link FormattingConversionService} that installs default
- * formatters for common types such as numbers and datetimes.
- *
- * Subclasses may override {@link #installFormatters(FormatterRegistry)}
- * to register custom formatters.
+ * and custom converters and formatters for common types such as numbers
+ * and datetimes .
*
* @author Keith Donald
* @author Juergen Hoeller
@@ -55,21 +54,31 @@ public class FormattingConversionServiceFactoryBean
private Set> converters;
+ private Set> formatters;
+
private StringValueResolver embeddedValueResolver;
private FormattingConversionService conversionService;
-
/**
- * Configure the set of custom converter objects that should be added:
- * implementing {@link org.springframework.core.convert.converter.Converter},
- * {@link org.springframework.core.convert.converter.ConverterFactory},
- * or {@link org.springframework.core.convert.converter.GenericConverter}.
+ * Configure the set of custom converter objects that should be added.
+ * @param converters instances of
+ * {@link org.springframework.core.convert.converter.Converter},
+ * {@link org.springframework.core.convert.converter.ConverterFactory} or
+ * {@link org.springframework.core.convert.converter.GenericConverter}.
*/
public void setConverters(Set> converters) {
this.converters = converters;
}
+ /**
+ * Configure the set of custom formatter objects that should be added.
+ * @param formatters instances of {@link Formatter} or {@link AnnotationFormatterFactory}.
+ */
+ public void setFormatters(Set> formatters) {
+ this.formatters = formatters;
+ }
+
public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) {
this.embeddedValueResolver = embeddedValueResolver;
}
@@ -112,6 +121,18 @@ public class FormattingConversionServiceFactoryBean
else {
registry.addFormatterForFieldAnnotation(new NoJodaDateTimeFormatAnnotationFormatterFactory());
}
+ if (this.formatters != null) {
+ for (Object formatter : this.formatters) {
+ if (formatter instanceof Formatter>) {
+ this.conversionService.addFormatter((Formatter>) formatter);
+ } else if (formatter instanceof AnnotationFormatterFactory>) {
+ this.conversionService.addFormatterForFieldAnnotation((AnnotationFormatterFactory>) formatter);
+ } else {
+ throw new IllegalArgumentException(
+ "Custom formatters must be implementations of Formatter or AnnotationFormatterFactory");
+ }
+ }
+ }
}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
index c5ec33f6cb0..fb972fc94eb 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
@@ -16,9 +16,8 @@
package org.springframework.web.servlet.config;
-import org.w3c.dom.Element;
-
import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
@@ -39,6 +38,7 @@ import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConvert
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
import org.springframework.util.ClassUtils;
+import org.springframework.util.xml.DomUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
@@ -49,6 +49,7 @@ import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExc
import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
+import org.w3c.dom.Element;
/**
* {@link BeanDefinitionParser} that parses the {@code annotation-driven} element to configure a Spring MVC web
@@ -75,6 +76,7 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
* @author Keith Donald
* @author Juergen Hoeller
* @author Arjen Poutsma
+ * @author Rossen Stoyanchev
* @since 3.0
*/
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
@@ -107,14 +109,16 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
RuntimeBeanReference validator = getValidator(element, source, parserContext);
+ RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element, source, parserContext);
RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
bindingDef.setSource(source);
bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
bindingDef.getPropertyValues().add("conversionService", conversionService);
bindingDef.getPropertyValues().add("validator", validator);
+ bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);
- ManagedList messageConverters = getMessageConverters(source);
+ ManagedList> messageConverters = getMessageConverters(element, source, parserContext);
RootBeanDefinition annAdapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
annAdapterDef.setSource(source);
@@ -166,10 +170,10 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
return null;
}
-
private RuntimeBeanReference getConversionService(Element element, Object source, ParserContext parserContext) {
+ RuntimeBeanReference conversionServiceRef;
if (element.hasAttribute("conversion-service")) {
- return new RuntimeBeanReference(element.getAttribute("conversion-service"));
+ conversionServiceRef = new RuntimeBeanReference(element.getAttribute("conversion-service"));
}
else {
RootBeanDefinition conversionDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class);
@@ -177,8 +181,21 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
conversionDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String conversionName = parserContext.getReaderContext().registerWithGeneratedName(conversionDef);
parserContext.registerComponent(new BeanComponentDefinition(conversionDef, conversionName));
- return new RuntimeBeanReference(conversionName);
+ conversionServiceRef = new RuntimeBeanReference(conversionName);
}
+ Element formattersElement = DomUtils.getChildElementByTagName(element, "formatters");
+ if (formattersElement != null) {
+ ManagedList formatters = new ManagedList();
+ formatters.setSource(source);
+ for (Element formatter : DomUtils.getChildElementsByTagName(formattersElement, "bean")) {
+ BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(formatter);
+ beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(formatter, beanDef);
+ formatters.add(beanDef);
+ }
+ BeanDefinition beanDef = parserContext.getRegistry().getBeanDefinition(conversionServiceRef.getBeanName());
+ beanDef.getPropertyValues().add("formatters", formatters);
+ }
+ return conversionServiceRef;
}
private RuntimeBeanReference getValidator(Element element, Object source, ParserContext parserContext) {
@@ -198,29 +215,51 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
}
}
- private ManagedList getMessageConverters(Object source) {
- ManagedList messageConverters = new ManagedList();
- messageConverters.setSource(source);
- messageConverters.add(createConverterBeanDefinition(ByteArrayHttpMessageConverter.class, source));
+ private RuntimeBeanReference getMessageCodesResolver(Element element, Object source, ParserContext parserContext) {
+ if (element.hasAttribute("message-codes-resolver")) {
+ return new RuntimeBeanReference(element.getAttribute("message-codes-resolver"));
+ } else {
+ return null;
+ }
+ }
- RootBeanDefinition stringConverterDef = createConverterBeanDefinition(StringHttpMessageConverter.class, source);
- stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
- messageConverters.add(stringConverterDef);
+ private ManagedList> getMessageConverters(Element element, Object source, ParserContext parserContext) {
+ Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
+ if (convertersElement != null) {
+ ManagedList messageConverters = new ManagedList();
+ messageConverters.setSource(source);
+ for (Element converter : DomUtils.getChildElementsByTagName(convertersElement, "bean")) {
+ BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(converter);
+ beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(converter, beanDef);
+ messageConverters.add(beanDef);
+ }
+ return messageConverters;
+ } else {
+ ManagedList messageConverters = new ManagedList();
+ messageConverters.setSource(source);
+ messageConverters.add(createConverterBeanDefinition(ByteArrayHttpMessageConverter.class, source));
- messageConverters.add(createConverterBeanDefinition(ResourceHttpMessageConverter.class, source));
- messageConverters.add(createConverterBeanDefinition(SourceHttpMessageConverter.class, source));
- messageConverters.add(createConverterBeanDefinition(XmlAwareFormHttpMessageConverter.class, source));
- if (jaxb2Present) {
- messageConverters.add(createConverterBeanDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
+ RootBeanDefinition stringConverterDef = createConverterBeanDefinition(StringHttpMessageConverter.class,
+ source);
+ stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
+ messageConverters.add(stringConverterDef);
+
+ messageConverters.add(createConverterBeanDefinition(ResourceHttpMessageConverter.class, source));
+ messageConverters.add(createConverterBeanDefinition(SourceHttpMessageConverter.class, source));
+ messageConverters.add(createConverterBeanDefinition(XmlAwareFormHttpMessageConverter.class, source));
+ if (jaxb2Present) {
+ messageConverters
+ .add(createConverterBeanDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
+ }
+ if (jacksonPresent) {
+ messageConverters.add(createConverterBeanDefinition(MappingJacksonHttpMessageConverter.class, source));
+ }
+ if (romePresent) {
+ messageConverters.add(createConverterBeanDefinition(AtomFeedHttpMessageConverter.class, source));
+ messageConverters.add(createConverterBeanDefinition(RssChannelHttpMessageConverter.class, source));
+ }
+ return messageConverters;
}
- if (jacksonPresent) {
- messageConverters.add(createConverterBeanDefinition(MappingJacksonHttpMessageConverter.class, source));
- }
- if (romePresent) {
- messageConverters.add(createConverterBeanDefinition(AtomFeedHttpMessageConverter.class, source));
- messageConverters.add(createConverterBeanDefinition(RssChannelHttpMessageConverter.class, source));
- }
- return messageConverters;
}
private RootBeanDefinition createConverterBeanDefinition(Class extends HttpMessageConverter> converterClass,
diff --git a/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd b/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd
index 02c6e7ebad7..c65528db118 100644
--- a/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+++ b/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd
@@ -1,6 +1,7 @@
@@ -16,6 +17,47 @@
]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -161,11 +217,11 @@
-
-
+
-
+ ]]>
+
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java
new file mode 100644
index 00000000000..dfc9accb5d5
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2002-2011 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.web.servlet.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.text.ParseException;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.springframework.beans.DirectFieldAccessor;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.format.AnnotationFormatterFactory;
+import org.springframework.format.Formatter;
+import org.springframework.format.Parser;
+import org.springframework.format.Printer;
+import org.springframework.format.support.FormattingConversionService;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.ResourceHttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.validation.MessageCodesResolver;
+import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
+import org.springframework.web.bind.support.WebArgumentResolver;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.support.GenericWebApplicationContext;
+import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
+import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver;
+
+/**
+ * Test fixture for the configuration in mvc-config-annotation-driven.xml.
+ * @author Rossen Stoyanchev
+ */
+public class AnnotationDrivenBeanDefinitionParserTests {
+
+ private static GenericWebApplicationContext appContext;
+
+ @BeforeClass
+ public static void setup() {
+ appContext = new GenericWebApplicationContext();
+ XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
+ reader.loadBeanDefinitions(new ClassPathResource("mvc-config-annotation-driven.xml",
+ AnnotationDrivenBeanDefinitionParserTests.class));
+ appContext.refresh();
+ }
+
+ @Test
+ public void testMessageCodesResolver() {
+ AnnotationMethodHandlerAdapter adapter = appContext.getBean(AnnotationMethodHandlerAdapter.class);
+ assertNotNull(adapter);
+ Object initializer = new DirectFieldAccessor(adapter).getPropertyValue("webBindingInitializer");
+ assertNotNull(initializer);
+ MessageCodesResolver resolver = ((ConfigurableWebBindingInitializer) initializer).getMessageCodesResolver();
+ assertNotNull(resolver);
+ assertEquals(TestMessageCodesResolver.class, resolver.getClass());
+ }
+
+ @Test
+ public void testMessageConverters() {
+ verifyMessageConverters(appContext.getBean(AnnotationMethodHandlerAdapter.class));
+ verifyMessageConverters(appContext.getBean(AnnotationMethodHandlerExceptionResolver.class));
+ }
+
+ @Test
+ public void testFormatters() throws Exception {
+ FormattingConversionService conversionService = appContext.getBean(FormattingConversionService.class);
+ assertNotNull(conversionService);
+
+ TestBean testBean = conversionService.convert("5", TestBean.class);
+ assertEquals(TestBeanFormatter.class.getSimpleName() + " should have been used.", 5, testBean.getField());
+ assertEquals("5", conversionService.convert(testBean, String.class));
+
+ TypeDescriptor intTypeDescriptor = new TypeDescriptor(TestBean.class.getDeclaredField("anotherField"));
+ Object actual = conversionService.convert(">>5<<", TypeDescriptor.valueOf(String.class), intTypeDescriptor);
+ assertEquals(TestBeanAnnotationFormatterFactory.class.getSimpleName() + " should have been used", 5, actual);
+ actual = conversionService.convert(5, intTypeDescriptor, TypeDescriptor.valueOf(String.class));
+ assertEquals(">>5<<", actual);
+ }
+
+ private void verifyMessageConverters(Object bean) {
+ assertNotNull(bean);
+ Object converters = new DirectFieldAccessor(bean).getPropertyValue("messageConverters");
+ assertNotNull(converters);
+ assertTrue(converters instanceof HttpMessageConverter>[]);
+ assertEquals(2, ((HttpMessageConverter>[]) converters).length);
+ assertTrue(((HttpMessageConverter>[]) converters)[0] instanceof StringHttpMessageConverter);
+ assertTrue(((HttpMessageConverter>[]) converters)[1] instanceof ResourceHttpMessageConverter);
+ }
+
+ private static class TestMessageCodesResolver implements MessageCodesResolver {
+
+ public String[] resolveMessageCodes(String errorCode, String objectName) {
+ throw new IllegalStateException("Not expected to be invoked");
+ }
+
+ @SuppressWarnings("rawtypes")
+ public String[] resolveMessageCodes(String errorCode, String objectName, String field, Class fieldType) {
+ throw new IllegalStateException("Not expected to be invoked");
+ }
+
+ }
+
+ @SuppressWarnings("unused")
+ private static class TestWebArgumentResolver implements WebArgumentResolver {
+
+ public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
+ throw new IllegalStateException("Not expected to be invoked");
+ }
+
+ }
+
+ @SuppressWarnings("unused")
+ private static class AnotherTestWebArgumentResolver implements WebArgumentResolver {
+
+ public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
+ throw new IllegalStateException("Not expected to be invoked");
+ }
+
+ }
+
+ private static class TestBeanFormatter implements Formatter {
+
+ public String print(TestBean object, Locale locale) {
+ return String.valueOf(object.getField());
+ }
+
+ public TestBean parse(String text, Locale locale) throws ParseException {
+ TestBean object = new TestBean();
+ object.setField(Integer.parseInt(text));
+ return object;
+ }
+
+ }
+
+ private static class TestBeanAnnotationFormatterFactory implements AnnotationFormatterFactory {
+
+ private final Set> fieldTypes = new HashSet>(1);
+
+ @SuppressWarnings("unused")
+ public TestBeanAnnotationFormatterFactory() {
+ fieldTypes.add(Integer.class);
+ }
+
+ public Set> getFieldTypes() {
+ return fieldTypes;
+ }
+
+ public Printer> getPrinter(SpecialIntFormat annotation, Class> fieldType) {
+ return new Printer() {
+ public String print(Integer object, Locale locale) {
+ return ">>" + object.toString() + "<<";
+ }
+ };
+ }
+
+ public Parser> getParser(SpecialIntFormat annotation, Class> fieldType) {
+ return new Parser() {
+ public Integer parse(String text, Locale locale) throws ParseException {
+ if (!text.startsWith(">>") || !text.endsWith("<<") || (text.length() < 5)) {
+ throw new ParseException(text + " is not in the expected format '>>intValue<<'", 0);
+ }
+ return Integer.parseInt(text.substring(2,3));
+ }
+ };
+ }
+
+ }
+
+ private static class TestBean {
+
+ private int field;
+
+ @SpecialIntFormat
+ private int anotherField;
+
+ public int getField() {
+ return field;
+ }
+
+ public void setField(int field) {
+ this.field = field;
+ }
+
+ @SuppressWarnings("unused")
+ public int getAnotherField() {
+ return anotherField;
+ }
+
+ @SuppressWarnings("unused")
+ public void setAnotherField(int anotherField) {
+ this.anotherField = anotherField;
+ }
+
+ }
+
+ @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface SpecialIntFormat {
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-annotation-driven.xml b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-annotation-driven.xml
new file mode 100644
index 00000000000..fc7e450b24c
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-annotation-driven.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+