diff --git a/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java b/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java index d2184e735fe..4fec9a9aa21 100644 --- a/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java +++ b/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 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. @@ -16,6 +16,7 @@ package org.springframework.format.annotation; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -28,19 +29,19 @@ import java.lang.annotation.Target; * Can be applied to {@code java.util.Date}, {@code java.util.Calendar}, {@code java.long.Long}, * Joda-Time value types; and as of Spring 4 and JDK 8, to JSR-310 java.time types too. * - *

For style-based formatting, set the {@link #style()} attribute to be the style pattern code. + *

For style-based formatting, set the {@link #style} attribute to be the style pattern code. * The first character of the code is the date style, and the second character is the time style. * Specify a character of 'S' for short style, 'M' for medium, 'L' for long, and 'F' for full. * A date or time may be omitted by specifying the style character '-'. * - *

For ISO-based formatting, set the {@link #iso()} attribute to be the desired {@link ISO} format, - * such as {@link ISO#DATE}. For custom formatting, set the {@link #pattern()} attribute to be the + *

For ISO-based formatting, set the {@link #iso} attribute to be the desired {@link ISO} format, + * such as {@link ISO#DATE}. For custom formatting, set the {@link #pattern} attribute to be the * DateTime pattern, such as {@code yyyy/MM/dd hh:mm:ss a}. * *

Each attribute is mutually exclusive, so only set one attribute per annotation instance - * (the one most convenient one for your formatting needs). + * (the most convenient one for your formatting needs). * When the pattern attribute is specified, it takes precedence over both the style and ISO attribute. - * When the iso attribute is specified, if takes precedence over the style attribute. + * When the iso attribute is specified, it takes precedence over the style attribute. * When no annotation attributes are specified, the default format applied is style-based * with a style code of 'SS' (short date, short time). * @@ -49,8 +50,9 @@ import java.lang.annotation.Target; * @since 3.0 * @see org.joda.time.format.DateTimeFormat */ -@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) +@Documented @Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) public @interface DateTimeFormat { /** diff --git a/spring-context/src/main/java/org/springframework/format/annotation/NumberFormat.java b/spring-context/src/main/java/org/springframework/format/annotation/NumberFormat.java index 0eea0622ac6..7418ec6b2f1 100644 --- a/spring-context/src/main/java/org/springframework/format/annotation/NumberFormat.java +++ b/spring-context/src/main/java/org/springframework/format/annotation/NumberFormat.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -16,6 +16,7 @@ package org.springframework.format.annotation; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -23,11 +24,12 @@ import java.lang.annotation.Target; /** * Declares that a field should be formatted as a number. - * Supports formatting by style or custom pattern string. + * + *

Supports formatting by style or custom pattern string. * Can be applied to any JDK {@code java.lang.Number} type. * - *

For style-based formatting, set the {@link #style()} attribute to be the desired {@link Style}. - * For custom formatting, set the {@link #pattern()} attribute to be the number pattern, such as {@code #, ###.##}. + *

For style-based formatting, set the {@link #style} attribute to be the desired {@link Style}. + * For custom formatting, set the {@link #pattern} attribute to be the number pattern, such as {@code #, ###.##}. * *

Each attribute is mutually exclusive, so only set one attribute per annotation instance * (the one most convenient one for your formatting needs). When the pattern attribute is specified, @@ -38,8 +40,9 @@ import java.lang.annotation.Target; * @since 3.0 * @see java.text.NumberFormat */ -@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) +@Documented @Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) public @interface NumberFormat { /** diff --git a/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java b/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java index a2930e3e7e9..cc1b36ec279 100644 --- a/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java +++ b/spring-context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 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. @@ -28,6 +28,7 @@ import java.util.Properties; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -48,6 +49,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.format.Formatter; import org.springframework.format.Printer; +import org.springframework.format.annotation.NumberFormat; import org.springframework.format.datetime.joda.DateTimeParser; import org.springframework.format.datetime.joda.JodaDateTimeFormatAnnotationFormatterFactory; import org.springframework.format.datetime.joda.ReadablePartialPrinter; @@ -58,6 +60,8 @@ import static org.junit.Assert.*; /** * @author Keith Donald * @author Juergen Hoeller + * @author Kazuki Shimizu + * @author Sam Brannen */ public class FormattingConversionServiceTests { @@ -101,6 +105,7 @@ public class FormattingConversionServiceTests { } @Test + @SuppressWarnings("resource") public void testFormatFieldForValueInjection() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); ac.registerBeanDefinition("valueBean", new RootBeanDefinition(ValueBean.class)); @@ -111,6 +116,7 @@ public class FormattingConversionServiceTests { } @Test + @SuppressWarnings("resource") public void testFormatFieldForValueInjectionUsingMetaAnnotations() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); RootBeanDefinition bd = new RootBeanDefinition(MetaValueBean.class); @@ -120,12 +126,15 @@ public class FormattingConversionServiceTests { ac.registerBeanDefinition("ppc", new RootBeanDefinition(PropertyPlaceholderConfigurer.class)); ac.refresh(); System.setProperty("myDate", "10-31-09"); + System.setProperty("myNumber", "99.99%"); try { MetaValueBean valueBean = ac.getBean(MetaValueBean.class); assertEquals(new LocalDate(2009, 10, 31), new LocalDate(valueBean.date)); + assertEquals(Double.valueOf(0.9999), valueBean.number); } finally { System.clearProperty("myDate"); + System.clearProperty("myNumber"); } } @@ -142,6 +151,7 @@ public class FormattingConversionServiceTests { } @Test + @SuppressWarnings("resource") public void testFormatFieldForAnnotationWithPlaceholders() throws Exception { GenericApplicationContext context = new GenericApplicationContext(); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); @@ -157,6 +167,7 @@ public class FormattingConversionServiceTests { } @Test + @SuppressWarnings("resource") public void testFormatFieldForAnnotationWithPlaceholdersAndFactoryBean() throws Exception { GenericApplicationContext context = new GenericApplicationContext(); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); @@ -336,13 +347,14 @@ public class FormattingConversionServiceTests { public Date date; } - public static class MetaValueBean { @MyDateAnn public Date date; - } + @MyNumberAnn + public Double number; + } @Value("${myDate}") @org.springframework.format.annotation.DateTimeFormat(pattern="MM-d-yy") @@ -350,6 +362,11 @@ public class FormattingConversionServiceTests { public static @interface MyDateAnn { } + @Value("${myNumber}") + @NumberFormat(style = NumberFormat.Style.PERCENT) + @Retention(RetentionPolicy.RUNTIME) + public static @interface MyNumberAnn { + } public static class Model { @@ -368,7 +385,6 @@ public class FormattingConversionServiceTests { } } - public static class ModelWithPlaceholders { @org.springframework.format.annotation.DateTimeFormat(style="${dateStyle}") @@ -386,13 +402,11 @@ public class FormattingConversionServiceTests { } } - @org.springframework.format.annotation.DateTimeFormat(pattern="${datePattern}") @Retention(RetentionPolicy.RUNTIME) public static @interface MyDatePattern { } - public static class NullReturningFormatter implements Formatter { @Override @@ -407,12 +421,10 @@ public class FormattingConversionServiceTests { } - @SuppressWarnings("serial") public static class MyDate extends Date { } - private static class ModelWithSubclassField { @org.springframework.format.annotation.DateTimeFormat(style = "S-") diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index a7de588e6c7..0967961bd50 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -29,14 +29,14 @@ import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; + import javax.servlet.RequestDispatcher; import javax.validation.constraints.NotNull; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; import org.hamcrest.Matchers; + +import org.joda.time.LocalDate; + import org.junit.Before; import org.junit.Test; @@ -52,6 +52,7 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.io.ClassPathResource; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; +import org.springframework.format.annotation.NumberFormat; import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; @@ -132,6 +133,12 @@ import org.springframework.web.servlet.view.velocity.VelocityConfigurer; import org.springframework.web.servlet.view.velocity.VelocityViewResolver; import org.springframework.web.util.UrlPathHelper; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; /** @@ -142,11 +149,13 @@ import static org.junit.Assert.*; * @author Jeremy Grelle * @author Brian Clozel * @author Sebastien Deleuze + * @author Kazuki Shimizu + * @author Sam Brannen */ public class MvcNamespaceTests { public static final String VIEWCONTROLLER_BEAN_NAME = "org.springframework.web.servlet.config.viewControllerHandlerMapping"; - + private GenericWebApplicationContext appContext; private TestController handler; @@ -165,7 +174,7 @@ public class MvcNamespaceTests { appContext.getServletContext().setAttribute(attributeName, appContext); handler = new TestController(); - Method method = TestController.class.getMethod("testBind", Date.class, TestBean.class, BindingResult.class); + Method method = TestController.class.getMethod("testBind", Date.class, Double.class, TestBean.class, BindingResult.class); handlerMethod = new InvocableHandlerMethod(handler, method); } @@ -211,6 +220,7 @@ public class MvcNamespaceTests { // default web binding initializer behavior test request = new MockHttpServletRequest("GET", "/"); request.addParameter("date", "2009-10-31"); + request.addParameter("percent", "99.99%"); MockHttpServletResponse response = new MockHttpServletResponse(); HandlerExecutionChain chain = mapping.getHandler(request); @@ -222,6 +232,8 @@ public class MvcNamespaceTests { adapter.handle(request, response, handlerMethod); assertTrue(handler.recordedValidationError); + assertEquals(LocalDate.parse("2009-10-31").toDate(), handler.date); + assertEquals(Double.valueOf(0.9999),handler.percent); CompositeUriComponentsContributor uriComponentsContributor = this.appContext.getBean( MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME, @@ -718,7 +730,7 @@ public class MvcNamespaceTests { assertEquals(TilesViewResolver.class, resolvers.get(2).getClass()); resolver = resolvers.get(3); - FreeMarkerViewResolver freeMarkerViewResolver = (FreeMarkerViewResolver) resolver; + assertThat(resolver, instanceOf(FreeMarkerViewResolver.class)); accessor = new DirectFieldAccessor(resolver); assertEquals("freemarker-", accessor.getPropertyValue("prefix")); assertEquals(".freemarker", accessor.getPropertyValue("suffix")); @@ -726,14 +738,14 @@ public class MvcNamespaceTests { assertEquals(1024, accessor.getPropertyValue("cacheLimit")); resolver = resolvers.get(4); - VelocityViewResolver velocityViewResolver = (VelocityViewResolver) resolver; + assertThat(resolver, instanceOf(VelocityViewResolver.class)); accessor = new DirectFieldAccessor(resolver); assertEquals("", accessor.getPropertyValue("prefix")); assertEquals(".vm", accessor.getPropertyValue("suffix")); assertEquals(0, accessor.getPropertyValue("cacheLimit")); resolver = resolvers.get(5); - GroovyMarkupViewResolver groovyMarkupViewResolver = (GroovyMarkupViewResolver) resolver; + assertThat(resolver, instanceOf(GroovyMarkupViewResolver.class)); accessor = new DirectFieldAccessor(resolver); assertEquals("", accessor.getPropertyValue("prefix")); assertEquals(".tpl", accessor.getPropertyValue("suffix")); @@ -742,7 +754,6 @@ public class MvcNamespaceTests { assertEquals(InternalResourceViewResolver.class, resolvers.get(6).getClass()); assertEquals(InternalResourceViewResolver.class, resolvers.get(7).getClass()); - TilesConfigurer tilesConfigurer = appContext.getBean(TilesConfigurer.class); assertNotNull(tilesConfigurer); String[] definitions = { @@ -841,6 +852,12 @@ public class MvcNamespaceTests { public @interface IsoDate { } + @NumberFormat(style = NumberFormat.Style.PERCENT) + @Target({ElementType.PARAMETER}) + @Retention(RetentionPolicy.RUNTIME) + public @interface PercentNumber { + } + @Validated(MyGroup.class) @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @@ -850,10 +867,14 @@ public class MvcNamespaceTests { @Controller public static class TestController { + private Date date; + private Double percent; private boolean recordedValidationError; @RequestMapping - public void testBind(@RequestParam @IsoDate Date date, @MyValid TestBean bean, BindingResult result) { + public void testBind(@RequestParam @IsoDate Date date, @RequestParam(required = false) @PercentNumber Double percent, @MyValid TestBean bean, BindingResult result) { + this.date = date; + this.percent = percent; this.recordedValidationError = (result.getErrorCount() == 1); } } @@ -965,5 +986,4 @@ public class MvcNamespaceTests { } } - }