From 955eaa87ae98972d9832db10e6d4fc93d471c538 Mon Sep 17 00:00:00 2001 From: Dmytro Nosan Date: Tue, 4 Jun 2019 18:08:02 +0300 Subject: [PATCH 1/2] Add Printer and Parser beans to conversion service Update `WebMvcAutoConfiguration` and `WebFluxAutoConfiguration` so that `Printer` and `Parser` beans are automatically registered with the conversion service. Prior to this commit only `GenericConverter`, `Converter` and `Formatter` beans were automatically registered. See gh-17064 --- .../reactive/WebFluxAutoConfiguration.java | 15 +++ .../web/servlet/WebMvcAutoConfiguration.java | 14 +++ .../WebFluxAutoConfigurationTests.java | 68 ++++++++++ .../servlet/WebMvcAutoConfigurationTests.java | 66 ++++++++++ .../boot/convert/ParserConverter.java | 93 ++++++++++++++ .../boot/convert/PrinterConverter.java | 85 +++++++++++++ .../boot/convert/ParserConverterTests.java | 116 ++++++++++++++++++ .../boot/convert/PrinterConverterTests.java | 93 ++++++++++++++ 8 files changed, 550 insertions(+) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ParserConverter.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PrinterConverter.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ParserConverterTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/PrinterConverterTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java index 920a62f16a6..9b7c7db5172 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java @@ -38,6 +38,8 @@ import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceCh import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.autoconfigure.web.format.WebConversionService; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.convert.ParserConverter; +import org.springframework.boot.convert.PrinterConverter; import org.springframework.boot.web.codec.CodecCustomizer; import org.springframework.boot.web.reactive.filter.OrderedHiddenHttpMethodFilter; import org.springframework.context.annotation.Bean; @@ -48,6 +50,8 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; +import org.springframework.format.Parser; +import org.springframework.format.Printer; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.util.ClassUtils; @@ -185,6 +189,17 @@ public class WebFluxAutoConfiguration { for (Formatter formatter : getBeansOfType(Formatter.class)) { registry.addFormatter(formatter); } + for (Printer printer : getBeansOfType(Printer.class)) { + if (!(printer instanceof Formatter)) { + registry.addConverter(new PrinterConverter(printer)); + + } + } + for (Parser parser : getBeansOfType(Parser.class)) { + if (!(parser instanceof Formatter)) { + registry.addConverter(new ParserConverter(parser)); + } + } } private Collection getBeansOfType(Class type) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index 07e0d7ecf6f..d659774fdd2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -55,6 +55,8 @@ import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy; import org.springframework.boot.autoconfigure.web.format.WebConversionService; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.convert.ParserConverter; +import org.springframework.boot.convert.PrinterConverter; import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter; import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter; import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter; @@ -74,6 +76,8 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; +import org.springframework.format.Parser; +import org.springframework.format.Printer; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.CacheControl; import org.springframework.http.MediaType; @@ -307,6 +311,16 @@ public class WebMvcAutoConfiguration { for (Formatter formatter : getBeansOfType(Formatter.class)) { registry.addFormatter(formatter); } + for (Printer printer : getBeansOfType(Printer.class)) { + if (!(printer instanceof Formatter)) { + registry.addConverter(new PrinterConverter(printer)); + } + } + for (Parser parser : getBeansOfType(Parser.class)) { + if (!(parser instanceof Formatter)) { + registry.addConverter(new ParserConverter(parser)); + } + } } private Collection getBeansOfType(Class type) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java index 63cff395959..dcbda43f7be 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.web.reactive; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -40,7 +41,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.core.convert.ConversionService; import org.springframework.core.io.ClassPathResource; +import org.springframework.format.Parser; +import org.springframework.format.Printer; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.CacheControl; import org.springframework.http.codec.ServerCodecConfigurer; @@ -373,6 +377,17 @@ class WebFluxAutoConfigurationTests { Assertions.setExtractBareNamePropertyMethods(true); } + @Test + void customPrinterAndParserShouldBeRegisteredAsConverters() { + this.contextRunner.withUserConfiguration(ParserConfiguration.class, PrinterConfiguration.class) + .run((context) -> { + Foo foo = new Foo("bar"); + ConversionService conversionService = context.getBean(ConversionService.class); + assertThat(conversionService.convert(foo, String.class)).isEqualTo("bar"); + assertThat(conversionService.convert("bar", Foo.class)).extracting(Foo::toString).isEqualTo("bar"); + }); + } + private Map getHandlerMap(ApplicationContext context) { HandlerMapping mapping = context.getBean("resourceHandlerMapping", HandlerMapping.class); if (mapping instanceof SimpleUrlHandlerMapping) { @@ -545,4 +560,57 @@ class WebFluxAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class PrinterConfiguration { + + @Bean + public Printer fooPrinter() { + return new FooPrinter(); + } + + private static class FooPrinter implements Printer { + + @Override + public String print(Foo foo, Locale locale) { + return foo.toString(); + } + + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParserConfiguration { + + @Bean + public Parser fooParser() { + return new FooParser(); + } + + private static class FooParser implements Parser { + + @Override + public Foo parse(String source, Locale locale) { + return new Foo(source); + } + + } + + } + + static class Foo { + + private final String name; + + Foo(String name) { + this.name = name; + } + + @Override + public String toString() { + return this.name; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java index ab7ee064f50..103ff0cbade 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java @@ -56,6 +56,8 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.format.Parser; +import org.springframework.format.Printer; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; @@ -773,6 +775,17 @@ class WebMvcAutoConfigurationTests { }); } + @Test + void customPrinterAndParserShouldBeRegisteredAsConverters() { + this.contextRunner.withUserConfiguration(ParserConfiguration.class, PrinterConfiguration.class) + .run((context) -> { + Foo foo = new Foo("bar"); + ConversionService conversionService = context.getBean(ConversionService.class); + assertThat(conversionService.convert(foo, String.class)).isEqualTo("bar"); + assertThat(conversionService.convert("bar", Foo.class)).extracting(Foo::toString).isEqualTo("bar"); + }); + } + private void assertCacheControl(AssertableWebApplicationContext context) { Map handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class)); assertThat(handlerMap).hasSize(2); @@ -1093,4 +1106,57 @@ class WebMvcAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class PrinterConfiguration { + + @Bean + public Printer fooPrinter() { + return new FooPrinter(); + } + + private static class FooPrinter implements Printer { + + @Override + public String print(Foo foo, Locale locale) { + return foo.toString(); + } + + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParserConfiguration { + + @Bean + public Parser fooParser() { + return new FooParser(); + } + + private static class FooParser implements Parser { + + @Override + public Foo parse(String source, Locale locale) { + return new Foo(source); + } + + } + + } + + static class Foo { + + private final String name; + + Foo(String name) { + this.name = name; + } + + @Override + public String toString() { + return this.name; + } + + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ParserConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ParserConverter.java new file mode 100644 index 00000000000..ec10de45ffb --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ParserConverter.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.boot.convert; + +import java.text.ParseException; +import java.util.Collections; +import java.util.Set; + +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.DecoratingProxy; +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.format.Parser; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * {@link Converter} to convert from a {@link String} to {@code } using the underlying + * {@link Parser}{@code }. + * + * @author Dmytro Nosan + * @since 2.2.0 + */ +public class ParserConverter implements GenericConverter { + + private final Class type; + + private final Parser parser; + + /** + * Creates a {@code Converter} to convert {@code String} to a {@code T} via parser. + * @param parser parses {@code String} to a {@code T} + */ + public ParserConverter(Parser parser) { + Assert.notNull(parser, "Parser must not be null"); + this.type = getType(parser); + this.parser = parser; + } + + @Override + public Set getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(String.class, this.type)); + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + String value = (String) source; + if (!StringUtils.hasText(value)) { + return null; + } + try { + return this.parser.parse(value, LocaleContextHolder.getLocale()); + } + catch (ParseException ex) { + throw new IllegalArgumentException("Value [" + value + "] can not be parsed", ex); + } + } + + @Override + public String toString() { + return String.class.getName() + " -> " + this.type.getName() + " : " + this.parser; + } + + private static Class getType(Parser parser) { + Class type = GenericTypeResolver.resolveTypeArgument(parser.getClass(), Parser.class); + if (type == null && parser instanceof DecoratingProxy) { + type = GenericTypeResolver.resolveTypeArgument(((DecoratingProxy) parser).getDecoratedClass(), + Parser.class); + } + if (type == null) { + throw new IllegalArgumentException("Unable to extract the parameterized type from Parser: '" + + parser.getClass().getName() + "'. Does the class parameterize the generic type?"); + } + return type; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PrinterConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PrinterConverter.java new file mode 100644 index 00000000000..2af9a1af72a --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PrinterConverter.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.boot.convert; + +import java.util.Collections; +import java.util.Set; + +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.DecoratingProxy; +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.format.Printer; +import org.springframework.util.Assert; + +/** + * {@link Converter} to convert {@code } to a {@link String} using the underlying + * {@link Printer}{@code }. + * + * @author Dmytro Nosan + * @since 2.2.0 + */ +public class PrinterConverter implements GenericConverter { + + private final Printer printer; + + private final Class type; + + /** + * Creates a {@code Converter} to convert {@code T} to a {@code String} via printer. + * @param printer prints {@code T} to a {@code String} + */ + public PrinterConverter(Printer printer) { + Assert.notNull(printer, "Printer must not be null"); + this.type = getType(printer); + this.printer = printer; + } + + @Override + public Set getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(this.type, String.class)); + } + + @Override + @SuppressWarnings("unchecked") + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + if (source == null) { + return ""; + } + return this.printer.print(source, LocaleContextHolder.getLocale()); + } + + public String toString() { + return this.type.getName() + " -> " + String.class.getName() + " : " + this.printer; + } + + private static Class getType(Printer printer) { + Class type = GenericTypeResolver.resolveTypeArgument(printer.getClass(), Printer.class); + if (type == null && printer instanceof DecoratingProxy) { + type = GenericTypeResolver.resolveTypeArgument(((DecoratingProxy) printer).getDecoratedClass(), + Printer.class); + } + if (type == null) { + throw new IllegalArgumentException("Unable to extract the parameterized type from Printer: '" + + printer.getClass().getName() + "'. Does the class parameterize the generic type?"); + } + return type; + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ParserConverterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ParserConverterTests.java new file mode 100644 index 00000000000..8614c76cabf --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ParserConverterTests.java @@ -0,0 +1,116 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.boot.convert; + +import java.text.ParseException; +import java.time.Duration; +import java.util.Locale; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.format.Parser; +import org.springframework.util.unit.DataSize; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link ParserConverter}. + * + * @author Dmytro Nosan + */ +class ParserConverterTests { + + private final DefaultConversionService conversionService = new DefaultConversionService(); + + @BeforeEach + void addParsers() { + this.conversionService.addConverter(new ParserConverter(new DurationParser())); + this.conversionService + .addConverter(new ParserConverter(((Parser) new ProxyFactory(new DataSizeParser()).getProxy()))); + + } + + @Test + void convertStringToDataSize() { + assertThat(convert("1KB", DataSize.class)).isEqualTo(DataSize.ofKilobytes(1)); + assertThat(convert("", DataSize.class)).isNull(); + assertThat(convert(null, DataSize.class)).isNull(); + } + + @Test + void convertStringToDuration() { + assertThat(convert("PT1S", Duration.class)).isEqualTo(Duration.ofSeconds(1)); + assertThat(convert(null, Duration.class)).isNull(); + assertThat(convert("", Duration.class)).isNull(); + } + + @Test + void shouldFailParserGenericCanNotBeResolved() { + assertThatIllegalArgumentException() + .isThrownBy(() -> this.conversionService.addConverter(new ParserConverter((source, locale) -> ""))) + .withMessageContaining("Unable to extract the parameterized type from Parser"); + } + + @Test + void shouldFailParserThrowsParserException() { + this.conversionService.addConverter(new ParserConverter(new ObjectParser())); + assertThatExceptionOfType(ConversionFailedException.class).isThrownBy(() -> convert("Text", Object.class)) + .withCauseInstanceOf(IllegalArgumentException.class) + .withMessageContaining("Value [Text] can not be parsed"); + + } + + private T convert(String source, Class type) { + return type.cast(this.conversionService.convert(source, TypeDescriptor.valueOf(String.class), + TypeDescriptor.valueOf(type))); + } + + private static class DataSizeParser implements Parser { + + @Override + public DataSize parse(String value, Locale locale) { + return DataSize.parse(value); + } + + } + + private static class DurationParser implements Parser { + + @Override + public Duration parse(String value, Locale locale) { + return Duration.parse(value); + } + + } + + private static class ObjectParser implements Parser { + + @Override + public Object parse(String source, Locale locale) throws ParseException { + throw new ParseException("", 0); + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/PrinterConverterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/PrinterConverterTests.java new file mode 100644 index 00000000000..5fb0920c01c --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/PrinterConverterTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.boot.convert; + +import java.time.Duration; +import java.util.Locale; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.format.Printer; +import org.springframework.util.unit.DataSize; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link PrinterConverter}. + * + * @author Dmytro Nosan + */ +class PrinterConverterTests { + + private final DefaultConversionService conversionService = new DefaultConversionService(); + + @BeforeEach + void addPrinters() { + this.conversionService.addConverter(new PrinterConverter(new DurationPrinter())); + this.conversionService + .addConverter(new PrinterConverter(((Printer) new ProxyFactory(new DataSizePrinter()).getProxy()))); + + } + + @Test + void convertDataSizeToString() { + assertThat(convert(DataSize.ofKilobytes(1), DataSize.class)).isEqualTo("1024B"); + assertThat(convert(null, DataSize.class)).isEmpty(); + } + + @Test + void convertDurationToString() { + assertThat(convert(Duration.ofSeconds(1), Duration.class)).isEqualTo("PT1S"); + assertThat(convert(null, Duration.class)).isEmpty(); + } + + @Test + void shouldFailPrinterGenericCanNotBeResolved() { + assertThatIllegalArgumentException() + .isThrownBy(() -> this.conversionService.addConverter(new PrinterConverter((source, locale) -> ""))) + .withMessageContaining("Unable to extract the parameterized type from Printer"); + } + + private String convert(T source, Class type) { + return (String) this.conversionService.convert(source, TypeDescriptor.valueOf(type), + TypeDescriptor.valueOf(String.class)); + } + + private static class DataSizePrinter implements Printer { + + @Override + public String print(DataSize dataSize, Locale locale) { + return dataSize.toString(); + } + + } + + private static class DurationPrinter implements Printer { + + @Override + public String print(Duration duration, Locale locale) { + return duration.toString(); + } + + } + +} From 9db20313a125c8e80aace486508ab1e21566c526 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 11 Jun 2019 09:57:39 -0700 Subject: [PATCH 2/2] Polish "Add Printer and Parser beans to conversion service" Extract common registration code and make use of the Spring Framework registration methods. See gh-17064 --- .../reactive/WebFluxAutoConfiguration.java | 35 +--- .../web/servlet/WebMvcAutoConfiguration.java | 33 +--- .../WebFluxAutoConfigurationTests.java | 56 ++++--- .../servlet/WebMvcAutoConfigurationTests.java | 56 ++++--- .../convert/ApplicationConversionService.java | 43 ++++- .../boot/convert/ParserConverter.java | 93 ----------- .../boot/convert/PrinterConverter.java | 85 ---------- .../ApplicationConversionServiceTests.java | 149 ++++++++++++++++++ .../boot/convert/ParserConverterTests.java | 116 -------------- .../boot/convert/PrinterConverterTests.java | 93 ----------- 10 files changed, 249 insertions(+), 510 deletions(-) delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ParserConverter.java delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PrinterConverter.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ApplicationConversionServiceTests.java delete mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ParserConverterTests.java delete mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/PrinterConverterTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java index 9b7c7db5172..c7ebde4a345 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java @@ -17,7 +17,6 @@ package org.springframework.boot.autoconfigure.web.reactive; import java.time.Duration; -import java.util.Collection; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -38,20 +37,14 @@ import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceCh import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.autoconfigure.web.format.WebConversionService; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.convert.ParserConverter; -import org.springframework.boot.convert.PrinterConverter; +import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.boot.web.codec.CodecCustomizer; import org.springframework.boot.web.reactive.filter.OrderedHiddenHttpMethodFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.convert.converter.GenericConverter; -import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; -import org.springframework.format.Parser; -import org.springframework.format.Printer; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.util.ClassUtils; @@ -180,37 +173,13 @@ public class WebFluxAutoConfiguration { @Override public void addFormatters(FormatterRegistry registry) { - for (Converter converter : getBeansOfType(Converter.class)) { - registry.addConverter(converter); - } - for (GenericConverter converter : getBeansOfType(GenericConverter.class)) { - registry.addConverter(converter); - } - for (Formatter formatter : getBeansOfType(Formatter.class)) { - registry.addFormatter(formatter); - } - for (Printer printer : getBeansOfType(Printer.class)) { - if (!(printer instanceof Formatter)) { - registry.addConverter(new PrinterConverter(printer)); - - } - } - for (Parser parser : getBeansOfType(Parser.class)) { - if (!(parser instanceof Formatter)) { - registry.addConverter(new ParserConverter(parser)); - } - } - } - - private Collection getBeansOfType(Class type) { - return this.beanFactory.getBeansOfType(type).values(); + ApplicationConversionService.addBeans(registry, this.beanFactory); } private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) { if (this.resourceHandlerRegistrationCustomizer != null) { this.resourceHandlerRegistrationCustomizer.customize(registration); } - } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index d659774fdd2..b742edc6cdf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -19,7 +19,6 @@ package org.springframework.boot.autoconfigure.web.servlet; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.ListIterator; @@ -55,8 +54,7 @@ import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy; import org.springframework.boot.autoconfigure.web.format.WebConversionService; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.convert.ParserConverter; -import org.springframework.boot.convert.PrinterConverter; +import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter; import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter; import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter; @@ -68,16 +66,11 @@ import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.task.AsyncTaskExecutor; -import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; -import org.springframework.format.Parser; -import org.springframework.format.Printer; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.CacheControl; import org.springframework.http.MediaType; @@ -302,29 +295,7 @@ public class WebMvcAutoConfiguration { @Override public void addFormatters(FormatterRegistry registry) { - for (Converter converter : getBeansOfType(Converter.class)) { - registry.addConverter(converter); - } - for (GenericConverter converter : getBeansOfType(GenericConverter.class)) { - registry.addConverter(converter); - } - for (Formatter formatter : getBeansOfType(Formatter.class)) { - registry.addFormatter(formatter); - } - for (Printer printer : getBeansOfType(Printer.class)) { - if (!(printer instanceof Formatter)) { - registry.addConverter(new PrinterConverter(printer)); - } - } - for (Parser parser : getBeansOfType(Parser.class)) { - if (!(parser instanceof Formatter)) { - registry.addConverter(new ParserConverter(parser)); - } - } - } - - private Collection getBeansOfType(Class type) { - return this.beanFactory.getBeansOfType(type).values(); + ApplicationConversionService.addBeans(registry, this.beanFactory); } @Override diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java index dcbda43f7be..3a67b946304 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java @@ -381,10 +381,9 @@ class WebFluxAutoConfigurationTests { void customPrinterAndParserShouldBeRegisteredAsConverters() { this.contextRunner.withUserConfiguration(ParserConfiguration.class, PrinterConfiguration.class) .run((context) -> { - Foo foo = new Foo("bar"); - ConversionService conversionService = context.getBean(ConversionService.class); - assertThat(conversionService.convert(foo, String.class)).isEqualTo("bar"); - assertThat(conversionService.convert("bar", Foo.class)).extracting(Foo::toString).isEqualTo("bar"); + ConversionService service = context.getBean(ConversionService.class); + assertThat(service.convert(new Example("spring", new Date()), String.class)).isEqualTo("spring"); + assertThat(service.convert("boot", Example.class)).extracting(Example::getName).isEqualTo("boot"); }); } @@ -564,17 +563,8 @@ class WebFluxAutoConfigurationTests { static class PrinterConfiguration { @Bean - public Printer fooPrinter() { - return new FooPrinter(); - } - - private static class FooPrinter implements Printer { - - @Override - public String print(Foo foo, Locale locale) { - return foo.toString(); - } - + public Printer examplePrinter() { + return new ExamplePrinter(); } } @@ -583,32 +573,40 @@ class WebFluxAutoConfigurationTests { static class ParserConfiguration { @Bean - public Parser fooParser() { - return new FooParser(); + public Parser exampleParser() { + return new ExampleParser(); } - private static class FooParser implements Parser { + } - @Override - public Foo parse(String source, Locale locale) { - return new Foo(source); - } + static final class Example { + + private final String name; + private Example(String name, Date date) { + this.name = name; } - } + public String getName() { + return this.name; + } - static class Foo { + } - private final String name; + private static class ExamplePrinter implements Printer { - Foo(String name) { - this.name = name; + @Override + public String print(Example example, Locale locale) { + return example.getName(); } + } + + private static class ExampleParser implements Parser { + @Override - public String toString() { - return this.name; + public Example parse(String source, Locale locale) { + return new Example(source, new Date()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java index 103ff0cbade..dfb97202fda 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java @@ -779,10 +779,9 @@ class WebMvcAutoConfigurationTests { void customPrinterAndParserShouldBeRegisteredAsConverters() { this.contextRunner.withUserConfiguration(ParserConfiguration.class, PrinterConfiguration.class) .run((context) -> { - Foo foo = new Foo("bar"); - ConversionService conversionService = context.getBean(ConversionService.class); - assertThat(conversionService.convert(foo, String.class)).isEqualTo("bar"); - assertThat(conversionService.convert("bar", Foo.class)).extracting(Foo::toString).isEqualTo("bar"); + ConversionService service = context.getBean(ConversionService.class); + assertThat(service.convert(new Example("spring", new Date()), String.class)).isEqualTo("spring"); + assertThat(service.convert("boot", Example.class)).extracting(Example::getName).isEqualTo("boot"); }); } @@ -1110,17 +1109,8 @@ class WebMvcAutoConfigurationTests { static class PrinterConfiguration { @Bean - public Printer fooPrinter() { - return new FooPrinter(); - } - - private static class FooPrinter implements Printer { - - @Override - public String print(Foo foo, Locale locale) { - return foo.toString(); - } - + public Printer examplePrinter() { + return new ExamplePrinter(); } } @@ -1129,32 +1119,40 @@ class WebMvcAutoConfigurationTests { static class ParserConfiguration { @Bean - public Parser fooParser() { - return new FooParser(); + public Parser exampleParser() { + return new ExampleParser(); } - private static class FooParser implements Parser { + } - @Override - public Foo parse(String source, Locale locale) { - return new Foo(source); - } + static final class Example { + + private final String name; + private Example(String name, Date date) { + this.name = name; } - } + public String getName() { + return this.name; + } - static class Foo { + } - private final String name; + private static class ExamplePrinter implements Printer { - Foo(String name) { - this.name = name; + @Override + public String print(Example example, Locale locale) { + return example.getName(); } + } + + private static class ExampleParser implements Parser { + @Override - public String toString() { - return this.name; + public Example parse(String source, Locale locale) { + return new Example(source, new Date()); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java index 959745ef343..15e059a52c6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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,11 +16,20 @@ package org.springframework.boot.convert; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterRegistry; +import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; +import org.springframework.format.Parser; +import org.springframework.format.Printer; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.FormattingConversionService; import org.springframework.util.StringValueResolver; @@ -134,4 +143,36 @@ public class ApplicationConversionService extends FormattingConversionService { registry.addFormatter(new IsoOffsetFormatter()); } + /** + * Add {@link GenericConverter}, {@link Converter}, {@link Printer}, {@link Parser} + * and {@link Formatter} beans from the specified context. + * @param registry the service to register beans with + * @param beanFactory the bean factory to get the beans from + * @since 2.2.0 + */ + public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) { + Set beans = new LinkedHashSet<>(); + beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values()); + beans.addAll(beanFactory.getBeansOfType(Converter.class).values()); + beans.addAll(beanFactory.getBeansOfType(Printer.class).values()); + beans.addAll(beanFactory.getBeansOfType(Parser.class).values()); + for (Object bean : beans) { + if (bean instanceof GenericConverter) { + registry.addConverter((GenericConverter) bean); + } + else if (bean instanceof Converter) { + registry.addConverter((Converter) bean); + } + else if (bean instanceof Formatter) { + registry.addFormatter((Formatter) bean); + } + else if (bean instanceof Printer) { + registry.addPrinter((Printer) bean); + } + else if (bean instanceof Parser) { + registry.addParser((Parser) bean); + } + } + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ParserConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ParserConverter.java deleted file mode 100644 index ec10de45ffb..00000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ParserConverter.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2012-2019 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 - * - * https://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.boot.convert; - -import java.text.ParseException; -import java.util.Collections; -import java.util.Set; - -import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.core.DecoratingProxy; -import org.springframework.core.GenericTypeResolver; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.convert.converter.GenericConverter; -import org.springframework.format.Parser; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * {@link Converter} to convert from a {@link String} to {@code } using the underlying - * {@link Parser}{@code }. - * - * @author Dmytro Nosan - * @since 2.2.0 - */ -public class ParserConverter implements GenericConverter { - - private final Class type; - - private final Parser parser; - - /** - * Creates a {@code Converter} to convert {@code String} to a {@code T} via parser. - * @param parser parses {@code String} to a {@code T} - */ - public ParserConverter(Parser parser) { - Assert.notNull(parser, "Parser must not be null"); - this.type = getType(parser); - this.parser = parser; - } - - @Override - public Set getConvertibleTypes() { - return Collections.singleton(new ConvertiblePair(String.class, this.type)); - } - - @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - String value = (String) source; - if (!StringUtils.hasText(value)) { - return null; - } - try { - return this.parser.parse(value, LocaleContextHolder.getLocale()); - } - catch (ParseException ex) { - throw new IllegalArgumentException("Value [" + value + "] can not be parsed", ex); - } - } - - @Override - public String toString() { - return String.class.getName() + " -> " + this.type.getName() + " : " + this.parser; - } - - private static Class getType(Parser parser) { - Class type = GenericTypeResolver.resolveTypeArgument(parser.getClass(), Parser.class); - if (type == null && parser instanceof DecoratingProxy) { - type = GenericTypeResolver.resolveTypeArgument(((DecoratingProxy) parser).getDecoratedClass(), - Parser.class); - } - if (type == null) { - throw new IllegalArgumentException("Unable to extract the parameterized type from Parser: '" - + parser.getClass().getName() + "'. Does the class parameterize the generic type?"); - } - return type; - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PrinterConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PrinterConverter.java deleted file mode 100644 index 2af9a1af72a..00000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PrinterConverter.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2012-2019 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 - * - * https://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.boot.convert; - -import java.util.Collections; -import java.util.Set; - -import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.core.DecoratingProxy; -import org.springframework.core.GenericTypeResolver; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.convert.converter.GenericConverter; -import org.springframework.format.Printer; -import org.springframework.util.Assert; - -/** - * {@link Converter} to convert {@code } to a {@link String} using the underlying - * {@link Printer}{@code }. - * - * @author Dmytro Nosan - * @since 2.2.0 - */ -public class PrinterConverter implements GenericConverter { - - private final Printer printer; - - private final Class type; - - /** - * Creates a {@code Converter} to convert {@code T} to a {@code String} via printer. - * @param printer prints {@code T} to a {@code String} - */ - public PrinterConverter(Printer printer) { - Assert.notNull(printer, "Printer must not be null"); - this.type = getType(printer); - this.printer = printer; - } - - @Override - public Set getConvertibleTypes() { - return Collections.singleton(new ConvertiblePair(this.type, String.class)); - } - - @Override - @SuppressWarnings("unchecked") - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - if (source == null) { - return ""; - } - return this.printer.print(source, LocaleContextHolder.getLocale()); - } - - public String toString() { - return this.type.getName() + " -> " + String.class.getName() + " : " + this.printer; - } - - private static Class getType(Printer printer) { - Class type = GenericTypeResolver.resolveTypeArgument(printer.getClass(), Printer.class); - if (type == null && printer instanceof DecoratingProxy) { - type = GenericTypeResolver.resolveTypeArgument(((DecoratingProxy) printer).getDecoratedClass(), - Printer.class); - } - if (type == null) { - throw new IllegalArgumentException("Unable to extract the parameterized type from Printer: '" - + printer.getClass().getName() + "'. Does the class parameterize the generic type?"); - } - return type; - } - -} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ApplicationConversionServiceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ApplicationConversionServiceTests.java new file mode 100644 index 00000000000..0fae7814a15 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ApplicationConversionServiceTests.java @@ -0,0 +1,149 @@ +/* + * Copyright 2012-2019 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 + * + * https://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.boot.convert; + +import java.text.ParseException; +import java.util.Locale; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.format.Formatter; +import org.springframework.format.FormatterRegistry; +import org.springframework.format.Parser; +import org.springframework.format.Printer; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Tests for {@link ApplicationConversionService}. + * + * @author Phillip Webb + */ +class ApplicationConversionServiceTests { + + private FormatterRegistry registry = mock(FormatterRegistry.class); + + @Test + void addBeansWhenHasGenericConverterBeanAddConverter() { + try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( + ExampleGenericConverter.class)) { + ApplicationConversionService.addBeans(this.registry, context); + verify(this.registry).addConverter(context.getBean(ExampleGenericConverter.class)); + verifyNoMoreInteractions(this.registry); + } + } + + @Test + void addBeansWhenHasConverterBeanAddConverter() { + try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(ExampleConverter.class)) { + ApplicationConversionService.addBeans(this.registry, context); + verify(this.registry).addConverter(context.getBean(ExampleConverter.class)); + verifyNoMoreInteractions(this.registry); + } + } + + @Test + void addBeansWhenHasFormatterBeanAddsOnlyFormatter() { + try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(ExampleFormatter.class)) { + ApplicationConversionService.addBeans(this.registry, context); + verify(this.registry).addFormatter(context.getBean(ExampleFormatter.class)); + verifyNoMoreInteractions(this.registry); + } + } + + @Test + void addBeansWhenHasPrinterBeanAddPrinter() { + try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(ExamplePrinter.class)) { + ApplicationConversionService.addBeans(this.registry, context); + verify(this.registry).addPrinter(context.getBean(ExamplePrinter.class)); + verifyNoMoreInteractions(this.registry); + } + } + + @Test + void addBeansWhenHasParserBeanAddParser() { + try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(ExampleParser.class)) { + ApplicationConversionService.addBeans(this.registry, context); + verify(this.registry).addParser(context.getBean(ExampleParser.class)); + verifyNoMoreInteractions(this.registry); + } + } + + private static class ExampleGenericConverter implements GenericConverter { + + @Override + public Set getConvertibleTypes() { + return null; + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + return null; + } + + } + + private static class ExampleConverter implements Converter { + + @Override + public Integer convert(String source) { + return null; + } + + } + + private static class ExampleFormatter implements Formatter { + + @Override + public String print(Integer object, Locale locale) { + return null; + } + + @Override + public Integer parse(String text, Locale locale) throws ParseException { + return null; + } + + } + + private static class ExampleParser implements Parser { + + @Override + public Integer parse(String text, Locale locale) throws ParseException { + return null; + } + + } + + private static class ExamplePrinter implements Printer { + + @Override + public String print(Integer object, Locale locale) { + return null; + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ParserConverterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ParserConverterTests.java deleted file mode 100644 index 8614c76cabf..00000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ParserConverterTests.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2012-2019 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 - * - * https://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.boot.convert; - -import java.text.ParseException; -import java.time.Duration; -import java.util.Locale; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.core.convert.ConversionFailedException; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.format.Parser; -import org.springframework.util.unit.DataSize; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link ParserConverter}. - * - * @author Dmytro Nosan - */ -class ParserConverterTests { - - private final DefaultConversionService conversionService = new DefaultConversionService(); - - @BeforeEach - void addParsers() { - this.conversionService.addConverter(new ParserConverter(new DurationParser())); - this.conversionService - .addConverter(new ParserConverter(((Parser) new ProxyFactory(new DataSizeParser()).getProxy()))); - - } - - @Test - void convertStringToDataSize() { - assertThat(convert("1KB", DataSize.class)).isEqualTo(DataSize.ofKilobytes(1)); - assertThat(convert("", DataSize.class)).isNull(); - assertThat(convert(null, DataSize.class)).isNull(); - } - - @Test - void convertStringToDuration() { - assertThat(convert("PT1S", Duration.class)).isEqualTo(Duration.ofSeconds(1)); - assertThat(convert(null, Duration.class)).isNull(); - assertThat(convert("", Duration.class)).isNull(); - } - - @Test - void shouldFailParserGenericCanNotBeResolved() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.conversionService.addConverter(new ParserConverter((source, locale) -> ""))) - .withMessageContaining("Unable to extract the parameterized type from Parser"); - } - - @Test - void shouldFailParserThrowsParserException() { - this.conversionService.addConverter(new ParserConverter(new ObjectParser())); - assertThatExceptionOfType(ConversionFailedException.class).isThrownBy(() -> convert("Text", Object.class)) - .withCauseInstanceOf(IllegalArgumentException.class) - .withMessageContaining("Value [Text] can not be parsed"); - - } - - private T convert(String source, Class type) { - return type.cast(this.conversionService.convert(source, TypeDescriptor.valueOf(String.class), - TypeDescriptor.valueOf(type))); - } - - private static class DataSizeParser implements Parser { - - @Override - public DataSize parse(String value, Locale locale) { - return DataSize.parse(value); - } - - } - - private static class DurationParser implements Parser { - - @Override - public Duration parse(String value, Locale locale) { - return Duration.parse(value); - } - - } - - private static class ObjectParser implements Parser { - - @Override - public Object parse(String source, Locale locale) throws ParseException { - throw new ParseException("", 0); - } - - } - -} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/PrinterConverterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/PrinterConverterTests.java deleted file mode 100644 index 5fb0920c01c..00000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/PrinterConverterTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2012-2019 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 - * - * https://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.boot.convert; - -import java.time.Duration; -import java.util.Locale; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.format.Printer; -import org.springframework.util.unit.DataSize; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link PrinterConverter}. - * - * @author Dmytro Nosan - */ -class PrinterConverterTests { - - private final DefaultConversionService conversionService = new DefaultConversionService(); - - @BeforeEach - void addPrinters() { - this.conversionService.addConverter(new PrinterConverter(new DurationPrinter())); - this.conversionService - .addConverter(new PrinterConverter(((Printer) new ProxyFactory(new DataSizePrinter()).getProxy()))); - - } - - @Test - void convertDataSizeToString() { - assertThat(convert(DataSize.ofKilobytes(1), DataSize.class)).isEqualTo("1024B"); - assertThat(convert(null, DataSize.class)).isEmpty(); - } - - @Test - void convertDurationToString() { - assertThat(convert(Duration.ofSeconds(1), Duration.class)).isEqualTo("PT1S"); - assertThat(convert(null, Duration.class)).isEmpty(); - } - - @Test - void shouldFailPrinterGenericCanNotBeResolved() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.conversionService.addConverter(new PrinterConverter((source, locale) -> ""))) - .withMessageContaining("Unable to extract the parameterized type from Printer"); - } - - private String convert(T source, Class type) { - return (String) this.conversionService.convert(source, TypeDescriptor.valueOf(type), - TypeDescriptor.valueOf(String.class)); - } - - private static class DataSizePrinter implements Printer { - - @Override - public String print(DataSize dataSize, Locale locale) { - return dataSize.toString(); - } - - } - - private static class DurationPrinter implements Printer { - - @Override - public String print(Duration duration, Locale locale) { - return duration.toString(); - } - - } - -}