SPR-7912 Introduce FormatterRegistrar interface and FormattingConversionServiceFactoryBean enhancements.

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@3927 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
Rossen Stoyanchev 2011-01-26 22:20:49 +00:00
parent 228a10ccdc
commit 27df774e38
4 changed files with 140 additions and 49 deletions

View File

@ -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);
}

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.format.datetime.joda; package org.springframework.format.datetime.joda;
import java.util.Calendar; import java.util.Calendar;
@ -27,7 +26,7 @@ import org.joda.time.ReadableInstant;
import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat; import org.joda.time.format.ISODateTimeFormat;
import org.springframework.format.FormatterRegistrar;
import org.springframework.format.FormatterRegistry; import org.springframework.format.FormatterRegistry;
import org.springframework.format.Parser; import org.springframework.format.Parser;
import org.springframework.format.Printer; import org.springframework.format.Printer;
@ -37,14 +36,14 @@ import org.springframework.format.Printer;
* *
* @author Keith Donald * @author Keith Donald
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 3.0 * @since 3.1
* @see #setDateStyle * @see #setDateStyle
* @see #setTimeStyle * @see #setTimeStyle
* @see #setDateTimeStyle * @see #setDateTimeStyle
* @see #setUseIsoFormat * @see #setUseIsoFormat
* @see #installJodaTimeFormatting * @see #installJodaTimeFormatting
*/ */
public class JodaTimeFormattingConfigurer { public class JodaTimeFormatterRegistrar implements FormatterRegistrar {
private String dateStyle; private String dateStyle;
@ -54,7 +53,6 @@ public class JodaTimeFormattingConfigurer {
private boolean useIsoFormat; private boolean useIsoFormat;
/** /**
* Set the default format style of Joda {@link LocalDate} objects. * Set the default format style of Joda {@link LocalDate} objects.
* Default is {@link DateTimeFormat#shortDate()}. * Default is {@link DateTimeFormat#shortDate()}.
@ -89,33 +87,28 @@ public class JodaTimeFormattingConfigurer {
this.useIsoFormat = useIsoFormat; this.useIsoFormat = useIsoFormat;
} }
public void registerFormatters(FormatterRegistry registry) {
/** JodaTimeConverters.registerConverters(registry);
* Install Joda Time formatters given the current configuration of this {@link JodaTimeFormattingConfigurer}.
*/
public void installJodaTimeFormatting(FormatterRegistry formatterRegistry) {
JodaTimeConverters.registerConverters(formatterRegistry);
DateTimeFormatter jodaDateFormatter = getJodaDateFormatter(); DateTimeFormatter jodaDateFormatter = getJodaDateFormatter();
formatterRegistry.addFormatterForFieldType(LocalDate.class, registry.addFormatterForFieldType(LocalDate.class, new ReadablePartialPrinter(jodaDateFormatter),
new ReadablePartialPrinter(jodaDateFormatter), new DateTimeParser(jodaDateFormatter)); new DateTimeParser(jodaDateFormatter));
DateTimeFormatter jodaTimeFormatter = getJodaTimeFormatter(); DateTimeFormatter jodaTimeFormatter = getJodaTimeFormatter();
formatterRegistry.addFormatterForFieldType(LocalTime.class, registry.addFormatterForFieldType(LocalTime.class, new ReadablePartialPrinter(jodaTimeFormatter),
new ReadablePartialPrinter(jodaTimeFormatter), new DateTimeParser(jodaTimeFormatter)); new DateTimeParser(jodaTimeFormatter));
DateTimeFormatter jodaDateTimeFormatter = getJodaDateTimeFormatter(); DateTimeFormatter jodaDateTimeFormatter = getJodaDateTimeFormatter();
Parser<DateTime> dateTimeParser = new DateTimeParser(jodaDateTimeFormatter); Parser<DateTime> dateTimeParser = new DateTimeParser(jodaDateTimeFormatter);
formatterRegistry.addFormatterForFieldType(LocalDateTime.class, registry.addFormatterForFieldType(LocalDateTime.class, new ReadablePartialPrinter(jodaDateTimeFormatter),
new ReadablePartialPrinter(jodaDateTimeFormatter), dateTimeParser); dateTimeParser);
Printer<ReadableInstant> readableInstantPrinter = new ReadableInstantPrinter(jodaDateTimeFormatter); Printer<ReadableInstant> 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 // internal helpers
private DateTimeFormatter getJodaDateFormatter() { private DateTimeFormatter getJodaDateFormatter() {
@ -125,8 +118,7 @@ public class JodaTimeFormattingConfigurer {
if (this.dateStyle != null) { if (this.dateStyle != null) {
return DateTimeFormat.forStyle(this.dateStyle + "-"); return DateTimeFormat.forStyle(this.dateStyle + "-");
} } else {
else {
return DateTimeFormat.shortDate(); return DateTimeFormat.shortDate();
} }
} }
@ -137,8 +129,7 @@ public class JodaTimeFormattingConfigurer {
} }
if (this.timeStyle != null) { if (this.timeStyle != null) {
return DateTimeFormat.forStyle("-" + this.timeStyle); return DateTimeFormat.forStyle("-" + this.timeStyle);
} } else {
else {
return DateTimeFormat.shortTime(); return DateTimeFormat.shortTime();
} }
} }
@ -149,8 +140,7 @@ public class JodaTimeFormattingConfigurer {
} }
if (this.dateTimeStyle != null) { if (this.dateTimeStyle != null) {
return DateTimeFormat.forStyle(this.dateTimeStyle); return DateTimeFormat.forStyle(this.dateTimeStyle);
} } else {
else {
return DateTimeFormat.shortDateTime(); return DateTimeFormat.shortDateTime();
} }
} }

View File

@ -28,22 +28,34 @@ import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.format.AnnotationFormatterFactory; import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.Formatter; import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistrar;
import org.springframework.format.FormatterRegistry; import org.springframework.format.FormatterRegistry;
import org.springframework.format.Parser; import org.springframework.format.Parser;
import org.springframework.format.Printer; import org.springframework.format.Printer;
import org.springframework.format.annotation.DateTimeFormat; 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.format.number.NumberFormatAnnotationFormatterFactory;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.StringValueResolver; import org.springframework.util.StringValueResolver;
/** /**
* A factory for a {@link FormattingConversionService} that installs default * <p>A factory for a {@link FormattingConversionService} that installs default
* and custom converters and formatters for common types such as numbers * converters and formatters for common types such as numbers and datetimes.
* and datetimes . *
* <p>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)}.
*
* <p>A good example for registering converters and formatters in code is
* <code>JodaTimeFormatterRegistrar</code>, 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 Keith Donald
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 3.0 * @since 3.0
*/ */
public class FormattingConversionServiceFactoryBean public class FormattingConversionServiceFactoryBean
@ -56,15 +68,19 @@ public class FormattingConversionServiceFactoryBean
private Set<?> formatters; private Set<?> formatters;
private Set<FormatterRegistrar> formatterRegistrars;
private StringValueResolver embeddedValueResolver; private StringValueResolver embeddedValueResolver;
private FormattingConversionService conversionService; private FormattingConversionService conversionService;
private boolean registerDefaultFormatters = true;
/** /**
* Configure the set of custom converter objects that should be added. * 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.Converter},
* {@link org.springframework.core.convert.converter.ConverterFactory} or * {@link org.springframework.core.convert.converter.ConverterFactory},
* {@link org.springframework.core.convert.converter.GenericConverter}. * {@link org.springframework.core.convert.converter.GenericConverter}.
*/ */
public void setConverters(Set<?> converters) { public void setConverters(Set<?> converters) {
@ -73,22 +89,53 @@ public class FormattingConversionServiceFactoryBean
/** /**
* Configure the set of custom formatter objects that should be added. * 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) { public void setFormatters(Set<?> formatters) {
this.formatters = formatters; this.formatters = formatters;
} }
/**
* <p>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)}.
* <p>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.
* <p>FormatterRegistrars can also be used to register Formatters
* indexed under a specific field type different from its own &lt;T&gt;,
* 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<FormatterRegistrar> formatterRegistrars) {
this.formatterRegistrars = formatterRegistrars;
}
public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) { public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) {
this.embeddedValueResolver = 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() { public void afterPropertiesSet() {
this.conversionService = new FormattingConversionService(); this.conversionService = new FormattingConversionService();
this.conversionService.setEmbeddedValueResolver(this.embeddedValueResolver); this.conversionService.setEmbeddedValueResolver(this.embeddedValueResolver);
ConversionServiceFactory.addDefaultConverters(this.conversionService); ConversionServiceFactory.addDefaultConverters(this.conversionService);
ConversionServiceFactory.registerConverters(this.converters, this.conversionService); ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
installFormatters(this.conversionService); addDefaultFormatters();
registerFormatters();
} }
@ -110,17 +157,30 @@ public class FormattingConversionServiceFactoryBean
// subclassing hooks // subclassing hooks
/** /**
* Install Formatters and Converters into the new FormattingConversionService using the FormatterRegistry SPI. * Subclasses may override this method to register formatters and/or converters.
* Subclasses may override to customize the set of formatters and/or converters that are installed. * 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) { protected void installFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory()); }
if (jodaTimePresent) {
new JodaTimeFormattingConfigurer().installJodaTimeFormatting(registry); // private helper methods
}
else { private void addDefaultFormatters() {
registry.addFormatterForFieldAnnotation(new NoJodaDateTimeFormatAnnotationFormatterFactory()); 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) { if (this.formatters != null) {
for (Object formatter : this.formatters) { for (Object formatter : this.formatters) {
if (formatter instanceof Formatter<?>) { 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 * Dummy AnnotationFormatterFactory that simply fails if @DateTimeFormat is being used
* without the JodaTime library being present. * without the JodaTime library being present.

View File

@ -16,6 +16,8 @@
package org.springframework.format.datetime.joda; package org.springframework.format.datetime.joda;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -28,10 +30,8 @@ import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime; import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime; import org.joda.time.LocalTime;
import org.junit.After; import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.MutablePropertyValues;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.core.convert.support.ConversionServiceFactory;
@ -54,8 +54,8 @@ public class JodaTimeFormattingTests {
public void setUp() { public void setUp() {
ConversionServiceFactory.addDefaultConverters(conversionService); ConversionServiceFactory.addDefaultConverters(conversionService);
JodaTimeFormattingConfigurer configurer = new JodaTimeFormattingConfigurer(); JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
configurer.installJodaTimeFormatting(conversionService); registrar.registerFormatters(conversionService);
JodaTimeBean bean = new JodaTimeBean(); JodaTimeBean bean = new JodaTimeBean();
bean.getChildren().add(new JodaTimeBean()); bean.getChildren().add(new JodaTimeBean());