From 149348c90781bde5d5ba77328b2f66c621122288 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 26 Jan 2011 22:20:49 +0000 Subject: [PATCH] SPR-7912 Introduce FormatterRegistrar interface and FormattingConversionServiceFactoryBean enhancements. --- .../format/FormatterRegistrar.java | 36 +++++++ ...r.java => JodaTimeFormatterRegistrar.java} | 44 +++----- ...ormattingConversionServiceFactoryBean.java | 101 ++++++++++++++---- .../joda/JodaTimeFormattingTests.java | 8 +- 4 files changed, 140 insertions(+), 49 deletions(-) create mode 100644 org.springframework.context/src/main/java/org/springframework/format/FormatterRegistrar.java rename org.springframework.context/src/main/java/org/springframework/format/datetime/joda/{JodaTimeFormattingConfigurer.java => JodaTimeFormatterRegistrar.java} (77%) diff --git a/org.springframework.context/src/main/java/org/springframework/format/FormatterRegistrar.java b/org.springframework.context/src/main/java/org/springframework/format/FormatterRegistrar.java new file mode 100644 index 0000000000..16b2208b1a --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/format/FormatterRegistrar.java @@ -0,0 +1,36 @@ +/* + * 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.format; + +import org.springframework.core.convert.converter.Converter; + +/** + * Registers {@link Converter Converters} and {@link Formatter Formatters} with + * a FormattingConversionService through the {@link FormatterRegistry} SPI. + * + * @author Keith Donald + * @since 3.1 + */ +public interface FormatterRegistrar { + + /** + * Register Formatters and Converters with a FormattingConversionService + * through a FormatterRegistry SPI. + * @param registry the FormatterRegistry instance to use. + */ + void registerFormatters(FormatterRegistry registry); + +} diff --git a/org.springframework.context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormattingConfigurer.java b/org.springframework.context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormatterRegistrar.java similarity index 77% rename from org.springframework.context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormattingConfigurer.java rename to org.springframework.context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormatterRegistrar.java index 45bb862f4e..af19ed8102 100644 --- a/org.springframework.context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormattingConfigurer.java +++ b/org.springframework.context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormatterRegistrar.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. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.format.datetime.joda; import java.util.Calendar; @@ -27,7 +26,7 @@ import org.joda.time.ReadableInstant; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; - +import org.springframework.format.FormatterRegistrar; import org.springframework.format.FormatterRegistry; import org.springframework.format.Parser; import org.springframework.format.Printer; @@ -37,14 +36,14 @@ import org.springframework.format.Printer; * * @author Keith Donald * @author Juergen Hoeller - * @since 3.0 + * @since 3.1 * @see #setDateStyle * @see #setTimeStyle * @see #setDateTimeStyle * @see #setUseIsoFormat * @see #installJodaTimeFormatting */ -public class JodaTimeFormattingConfigurer { +public class JodaTimeFormatterRegistrar implements FormatterRegistrar { private String dateStyle; @@ -54,7 +53,6 @@ public class JodaTimeFormattingConfigurer { private boolean useIsoFormat; - /** * Set the default format style of Joda {@link LocalDate} objects. * Default is {@link DateTimeFormat#shortDate()}. @@ -89,33 +87,28 @@ public class JodaTimeFormattingConfigurer { this.useIsoFormat = useIsoFormat; } - - /** - * Install Joda Time formatters given the current configuration of this {@link JodaTimeFormattingConfigurer}. - */ - public void installJodaTimeFormatting(FormatterRegistry formatterRegistry) { - JodaTimeConverters.registerConverters(formatterRegistry); + public void registerFormatters(FormatterRegistry registry) { + JodaTimeConverters.registerConverters(registry); DateTimeFormatter jodaDateFormatter = getJodaDateFormatter(); - formatterRegistry.addFormatterForFieldType(LocalDate.class, - new ReadablePartialPrinter(jodaDateFormatter), new DateTimeParser(jodaDateFormatter)); + registry.addFormatterForFieldType(LocalDate.class, new ReadablePartialPrinter(jodaDateFormatter), + new DateTimeParser(jodaDateFormatter)); DateTimeFormatter jodaTimeFormatter = getJodaTimeFormatter(); - formatterRegistry.addFormatterForFieldType(LocalTime.class, - new ReadablePartialPrinter(jodaTimeFormatter), new DateTimeParser(jodaTimeFormatter)); + registry.addFormatterForFieldType(LocalTime.class, new ReadablePartialPrinter(jodaTimeFormatter), + new DateTimeParser(jodaTimeFormatter)); DateTimeFormatter jodaDateTimeFormatter = getJodaDateTimeFormatter(); Parser dateTimeParser = new DateTimeParser(jodaDateTimeFormatter); - formatterRegistry.addFormatterForFieldType(LocalDateTime.class, - new ReadablePartialPrinter(jodaDateTimeFormatter), dateTimeParser); + registry.addFormatterForFieldType(LocalDateTime.class, new ReadablePartialPrinter(jodaDateTimeFormatter), + dateTimeParser); Printer readableInstantPrinter = new ReadableInstantPrinter(jodaDateTimeFormatter); - formatterRegistry.addFormatterForFieldType(ReadableInstant.class, readableInstantPrinter, dateTimeParser); + registry.addFormatterForFieldType(ReadableInstant.class, readableInstantPrinter, dateTimeParser); - formatterRegistry.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory()); + registry.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory()); } - // internal helpers private DateTimeFormatter getJodaDateFormatter() { @@ -125,8 +118,7 @@ public class JodaTimeFormattingConfigurer { if (this.dateStyle != null) { return DateTimeFormat.forStyle(this.dateStyle + "-"); - } - else { + } else { return DateTimeFormat.shortDate(); } } @@ -137,8 +129,7 @@ public class JodaTimeFormattingConfigurer { } if (this.timeStyle != null) { return DateTimeFormat.forStyle("-" + this.timeStyle); - } - else { + } else { return DateTimeFormat.shortTime(); } } @@ -149,8 +140,7 @@ public class JodaTimeFormattingConfigurer { } if (this.dateTimeStyle != null) { return DateTimeFormat.forStyle(this.dateTimeStyle); - } - else { + } else { return DateTimeFormat.shortDateTime(); } } 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 713b39eec4..d1469f116a 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 @@ -28,22 +28,34 @@ 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.FormatterRegistrar; import org.springframework.format.FormatterRegistry; import org.springframework.format.Parser; import org.springframework.format.Printer; import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.format.datetime.joda.JodaTimeFormattingConfigurer; +import org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar; import org.springframework.format.number.NumberFormatAnnotationFormatterFactory; import org.springframework.util.ClassUtils; import org.springframework.util.StringValueResolver; /** - * A factory for a {@link FormattingConversionService} that installs default - * and custom converters and formatters for common types such as numbers - * and datetimes . - * + *

A factory for a {@link FormattingConversionService} that installs default + * converters and formatters for common types such as numbers and datetimes. + * + *

Converters and formatters can be registered declaratively through + * {@link #setConverters(Set)} and {@link #setFormatters(Set)}. Another option + * is to register converters and formatters in code by implementing the + * {@link FormatterRegistrar} interface. You can then configure provide the set + * of registrars to use through {@link #setFormatterRegistrars(Set)}. + * + *

A good example for registering converters and formatters in code is + * JodaTimeFormatterRegistrar, which registers a number of + * date-related formatters and converters. For a more detailed list of cases + * see {@link #setFormatterRegistrars(Set)} + * * @author Keith Donald * @author Juergen Hoeller + * @author Rossen Stoyanchev * @since 3.0 */ public class FormattingConversionServiceFactoryBean @@ -56,15 +68,19 @@ public class FormattingConversionServiceFactoryBean private Set formatters; + private Set formatterRegistrars; + private StringValueResolver embeddedValueResolver; private FormattingConversionService conversionService; + private boolean registerDefaultFormatters = true; + /** * Configure the set of custom converter objects that should be added. - * @param converters instances of + * @param converters instances of any of the following: * {@link org.springframework.core.convert.converter.Converter}, - * {@link org.springframework.core.convert.converter.ConverterFactory} or + * {@link org.springframework.core.convert.converter.ConverterFactory}, * {@link org.springframework.core.convert.converter.GenericConverter}. */ public void setConverters(Set converters) { @@ -73,22 +89,53 @@ public class FormattingConversionServiceFactoryBean /** * Configure the set of custom formatter objects that should be added. - * @param formatters instances of {@link Formatter} or {@link AnnotationFormatterFactory}. + * @param formatters instances of {@link Formatter} or + * {@link AnnotationFormatterFactory}. */ public void setFormatters(Set formatters) { this.formatters = formatters; } + /** + *

Configure the set of FormatterRegistrars to invoke to register + * Converters and Formatters in addition to those added declaratively + * via {@link #setConverters(Set)} and {@link #setFormatters(Set)}. + *

FormatterRegistrars are useful when registering multiple related + * converters and formatters for a formatting category, such as Date + * formatting. All types related needed to support the formatting + * category can be registered from one place. + *

FormatterRegistrars can also be used to register Formatters + * indexed under a specific field type different from its own <T>, + * or when registering a Formatter from a Printer/Parser pair. + * @see FormatterRegistry#addFormatterForFieldType(Class, Formatter) + * @see FormatterRegistry#addFormatterForFieldType(Class, Printer, Parser) + */ + public void setFormatterRegistrars(Set formatterRegistrars) { + this.formatterRegistrars = formatterRegistrars; + } + public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) { this.embeddedValueResolver = embeddedValueResolver; } + /** + * Indicates whether default formatters should be registered or not. By + * default built-in formatters are registered. This flag can be used to + * turn that off and rely on explicitly registered formatters only. + * @see #setFormatters(Set) + * @see #setFormatterRegistrars(Set) + */ + public void setRegisterDefaultFormatters(boolean registerDefaultFormatters) { + this.registerDefaultFormatters = registerDefaultFormatters; + } + public void afterPropertiesSet() { this.conversionService = new FormattingConversionService(); this.conversionService.setEmbeddedValueResolver(this.embeddedValueResolver); ConversionServiceFactory.addDefaultConverters(this.conversionService); ConversionServiceFactory.registerConverters(this.converters, this.conversionService); - installFormatters(this.conversionService); + addDefaultFormatters(); + registerFormatters(); } @@ -110,17 +157,30 @@ public class FormattingConversionServiceFactoryBean // subclassing hooks /** - * Install Formatters and Converters into the new FormattingConversionService using the FormatterRegistry SPI. - * Subclasses may override to customize the set of formatters and/or converters that are installed. + * Subclasses may override this method to register formatters and/or converters. + * Starting with Spring 3.1 however the recommended way of doing that is to + * through FormatterRegistrars. + * @see #setFormatters(Set) + * @see #setFormatterRegistrars(Set) */ protected void installFormatters(FormatterRegistry registry) { - registry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory()); - if (jodaTimePresent) { - new JodaTimeFormattingConfigurer().installJodaTimeFormatting(registry); - } - else { - registry.addFormatterForFieldAnnotation(new NoJodaDateTimeFormatAnnotationFormatterFactory()); + } + + // private helper methods + + private void addDefaultFormatters() { + if (registerDefaultFormatters) { + this.conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory()); + if (jodaTimePresent) { + new JodaTimeFormatterRegistrar().registerFormatters(this.conversionService); + } else { + this.conversionService + .addFormatterForFieldAnnotation(new NoJodaDateTimeFormatAnnotationFormatterFactory()); + } } + } + + private void registerFormatters() { if (this.formatters != null) { for (Object formatter : this.formatters) { if (formatter instanceof Formatter) { @@ -133,9 +193,14 @@ public class FormattingConversionServiceFactoryBean } } } + if (this.formatterRegistrars != null) { + for (FormatterRegistrar registrar : this.formatterRegistrars) { + registrar.registerFormatters(this.conversionService); + } + } + installFormatters(this.conversionService); } - /** * Dummy AnnotationFormatterFactory that simply fails if @DateTimeFormat is being used * without the JodaTime library being present. diff --git a/org.springframework.context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java b/org.springframework.context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java index b27b299665..b2b8338d0a 100644 --- a/org.springframework.context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java +++ b/org.springframework.context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java @@ -16,6 +16,8 @@ package org.springframework.format.datetime.joda; +import static org.junit.Assert.assertEquals; + import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @@ -28,10 +30,8 @@ import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; import org.joda.time.LocalTime; import org.junit.After; -import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; - import org.springframework.beans.MutablePropertyValues; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.convert.support.ConversionServiceFactory; @@ -54,8 +54,8 @@ public class JodaTimeFormattingTests { public void setUp() { ConversionServiceFactory.addDefaultConverters(conversionService); - JodaTimeFormattingConfigurer configurer = new JodaTimeFormattingConfigurer(); - configurer.installJodaTimeFormatting(conversionService); + JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar(); + registrar.registerFormatters(conversionService); JodaTimeBean bean = new JodaTimeBean(); bean.getChildren().add(new JodaTimeBean());