diff --git a/org.springframework.context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java b/org.springframework.context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java index 76482250e10..49ebbe085a0 100644 --- a/org.springframework.context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java +++ b/org.springframework.context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java @@ -25,7 +25,7 @@ import java.lang.annotation.Target; * @author Keith Donald * @since 3.0 */ -@Target( { ElementType.METHOD, ElementType.FIELD }) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) public @interface DateTimeFormat { diff --git a/org.springframework.context/src/main/java/org/springframework/format/annotation/ISODateTimeFormat.java b/org.springframework.context/src/main/java/org/springframework/format/annotation/ISODateTimeFormat.java index 24d793b2a3c..c510e0cfa04 100644 --- a/org.springframework.context/src/main/java/org/springframework/format/annotation/ISODateTimeFormat.java +++ b/org.springframework.context/src/main/java/org/springframework/format/annotation/ISODateTimeFormat.java @@ -25,7 +25,7 @@ import java.lang.annotation.Target; * @author Keith Donald * @since 3.0 */ -@Target( { ElementType.METHOD, ElementType.FIELD }) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) public @interface ISODateTimeFormat { 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 new file mode 100644 index 00000000000..555ec84ec3a --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java @@ -0,0 +1,76 @@ +/* + * 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.format.support; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.convert.ConversionService; +import org.springframework.format.datetime.joda.JodaTimeFormattingConfigurer; +import org.springframework.util.ClassUtils; + +/** + * A factory for a FormattingConversionService that installs default formatters for common types such as numbers and datetimes. + * @author Keith Donald + * @since 3.0 + */ +public class FormattingConversionServiceFactoryBean implements FactoryBean, InitializingBean { + + private ConversionService parent; + + private FormattingConversionService conversionService; + + public void setParent(ConversionService parent) { + this.parent = parent; + } + + // implementing InitializingBean + + public void afterPropertiesSet() { + initConversionService(); + installJodaTimeFormattingIfPresent(); + } + + // implementing FactoryBean + + public ConversionService getObject() { + return this.conversionService; + } + + public Class getObjectType() { + return ConversionService.class; + } + + public boolean isSingleton() { + return true; + } + + // internal helpers + + private void initConversionService() { + if (this.parent != null) { + this.conversionService = new FormattingConversionService(this.parent); + } else { + this.conversionService = new FormattingConversionService(); + } + } + + private void installJodaTimeFormattingIfPresent() { + if (ClassUtils.isPresent("org.joda.time.DateTime", FormattingConversionService.class.getClassLoader())) { + new JodaTimeFormattingConfigurer().installJodaTimeFormatting(this.conversionService); + } + } + +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 8b51b047993..332386d858b 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -24,9 +24,12 @@ import java.util.Map; import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.MethodParameter; +import org.springframework.core.style.StylerUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import com.sun.xml.internal.rngom.ast.builder.Annotations; + /** * Context about a type to convert to. * @@ -277,7 +280,7 @@ public class TypeDescriptor { } return this.cachedFieldAnnotations; } else if (this.methodParameter != null) { - return this.methodParameter.getMethod().getAnnotations(); + return this.methodParameter.getParameterAnnotations(); } else { return new Annotation[0]; } @@ -411,7 +414,15 @@ public class TypeDescriptor { if (this == TypeDescriptor.NULL) { return "[TypeDescriptor.NULL]"; } else { - return "[TypeDescriptor type=" + getType().getName() + "]"; + StringBuilder builder = new StringBuilder(); + builder.append("[TypeDescriptor "); + Annotation[] anns = getAnnotations(); + for (Annotation ann : anns) { + builder.append("@" + ann.annotationType().getName()).append(' '); + } + builder.append(getType().getName()); + builder.append("]"); + return builder.toString(); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java index bc247a96e7e..724080d400b 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java @@ -26,6 +26,8 @@ import java.util.LinkedList; import java.util.Map; import java.util.Set; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.core.GenericTypeResolver; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; @@ -45,6 +47,8 @@ import org.springframework.util.ClassUtils; */ public class GenericConversionService implements ConversionService, ConverterRegistry { + private static final Log logger = LogFactory.getLog(GenericConversionService.class); + private static final GenericConverter NO_OP_CONVERTER = new GenericConverter() { public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return source; @@ -326,6 +330,9 @@ public class GenericConversionService implements ConversionService, ConverterReg } private GenericConverter findConverterForClassPair(TypeDescriptor sourceType, TypeDescriptor targetType) { + if (logger.isDebugEnabled()) { + logger.debug("Looking for Converter to convert from " + sourceType + " to " + targetType); + } Class sourceObjectType = sourceType.getObjectType(); if (sourceObjectType.isInterface()) { LinkedList> classQueue = new LinkedList>(); @@ -507,7 +514,14 @@ public class GenericConversionService implements ConversionService, ConverterReg public GenericConverter matchConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { for (MatchableConverter matchable : this.matchableConverters) { if (matchable.matches(sourceType, targetType)) { + if (logger.isDebugEnabled()) { + logger.debug("Converter Lookup [MATCHED] " + matchable); + } return matchable.getConverter(); + } else { + if (logger.isDebugEnabled()) { + logger.debug("Converter Lookup [DID NOT MATCH] " + matchable); + } } } return null; diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotatedControllersBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotatedControllersBeanDefinitionParser.java index 1429d07e9f0..afc3368658e 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotatedControllersBeanDefinitionParser.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotatedControllersBeanDefinitionParser.java @@ -23,6 +23,8 @@ import org.springframework.beans.factory.parsing.CompositeComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.format.support.FormattingConversionServiceFactoryBean; +import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter; import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping; import org.w3c.dom.Element; @@ -39,7 +41,7 @@ public class AnnotatedControllersBeanDefinitionParser implements BeanDefinitionP Object source = parserContext.extractSource(element); BeanDefinitionHolder handlerMappingHolder = registerDefaultAnnotationHandlerMapping(element, source, parserContext); BeanDefinitionHolder handlerAdapterHolder = registerAnnotationMethodHandlerAdapter(element, source, parserContext); - + CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source); parserContext.pushContainingComponent(compDefinition); parserContext.registerComponent(new BeanComponentDefinition(handlerMappingHolder)); @@ -50,21 +52,41 @@ public class AnnotatedControllersBeanDefinitionParser implements BeanDefinitionP } private BeanDefinitionHolder registerDefaultAnnotationHandlerMapping(Element element, Object source, ParserContext context) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DefaultAnnotationHandlerMapping.class); + BeanDefinitionBuilder builder = createBeanBuilder(DefaultAnnotationHandlerMapping.class, source); builder.addPropertyValue("order", 0); - builder.getRawBeanDefinition().setSource(source); return registerBeanDefinition(new BeanDefinitionHolder(builder.getBeanDefinition(), "defaultAnnotationHandlerMapping"), context); } private BeanDefinitionHolder registerAnnotationMethodHandlerAdapter(Element element, Object source, ParserContext context) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(AnnotationMethodHandlerAdapter.class); - builder.getRawBeanDefinition().setSource(source); + BeanDefinitionBuilder builder = createBeanBuilder(AnnotationMethodHandlerAdapter.class, source); + builder.addPropertyValue("webBindingInitializer", createWebBindingInitializer(element, source, context)); return registerBeanDefinition(new BeanDefinitionHolder(builder.getBeanDefinition(), "annotationMethodHandlerAdapter"), context); } + private BeanDefinition createWebBindingInitializer(Element element, Object source, ParserContext context) { + BeanDefinitionBuilder builder = createBeanBuilder(ConfigurableWebBindingInitializer.class, source); + if (context.getRegistry().containsBeanDefinition("conversionService")) { + builder.addPropertyReference("conversionService", "conversionService"); + } else { + builder.addPropertyValue("conversionService", createFormattingConversionService(element, source, context)); + } + return builder.getBeanDefinition(); + } + + private BeanDefinition createFormattingConversionService(Element element, Object source, ParserContext context) { + BeanDefinitionBuilder builder = createBeanBuilder(FormattingConversionServiceFactoryBean.class, source); + return builder.getBeanDefinition(); + } + private BeanDefinitionHolder registerBeanDefinition(BeanDefinitionHolder holder, ParserContext context) { context.getRegistry().registerBeanDefinition(holder.getBeanName(), holder.getBeanDefinition()); return holder; } + + private BeanDefinitionBuilder createBeanBuilder(Class clazz, Object source) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz); + builder.getRawBeanDefinition().setSource(source); + return builder; + } } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index c703743d30c..1fd6a869e5f 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -3,11 +3,22 @@ package org.springframework.web.servlet.config; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.util.Date; +import java.util.Locale; + import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.io.ClassPathResource; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.annotation.DateTimeFormat.Style; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletContext; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter; import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping; @@ -20,10 +31,12 @@ public class MvcNamespaceTests { public void setUp() { container = new GenericWebApplicationContext(); container.setServletContext(new MockServletContext()); + + LocaleContextHolder.setLocale(Locale.US); } @Test - public void testDefaultConfig() { + public void testDefaultConfig() throws Exception { XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(container); reader.loadBeanDefinitions(new ClassPathResource("mvc-config.xml", getClass())); assertEquals(2, container.getBeanDefinitionCount()); @@ -32,5 +45,22 @@ public class MvcNamespaceTests { assertEquals(0, mapping.getOrder()); AnnotationMethodHandlerAdapter adapter = container.getBean("annotationMethodHandlerAdapter", AnnotationMethodHandlerAdapter.class); assertNotNull(adapter); + + TestController handler = new TestController(); + + // default web binding initializer behavior test + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("date", "Oct 31, 2009"); + MockHttpServletResponse response = new MockHttpServletResponse(); + adapter.handle(request, response, handler); + } + + @Controller + public static class TestController { + + @RequestMapping + public void testBind(@RequestParam @DateTimeFormat(dateStyle=Style.MEDIUM) Date date) { + System.out.println(date); + } } } diff --git a/org.springframework.web.servlet/src/test/resources/log4j.xml b/org.springframework.web.servlet/src/test/resources/log4j.xml index 767b96d6206..785c094dde1 100644 --- a/org.springframework.web.servlet/src/test/resources/log4j.xml +++ b/org.springframework.web.servlet/src/test/resources/log4j.xml @@ -15,7 +15,7 @@ - +