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
This commit is contained in:
		
							parent
							
								
									955eaa87ae
								
							
						
					
					
						commit
						9db20313a1
					
				|  | @ -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 <T> Collection<T> getBeansOfType(Class<T> 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); | ||||
| 			} | ||||
| 
 | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
|  |  | |||
|  | @ -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 <T> Collection<T> getBeansOfType(Class<T> type) { | ||||
| 			return this.beanFactory.getBeansOfType(type).values(); | ||||
| 			ApplicationConversionService.addBeans(registry, this.beanFactory); | ||||
| 		} | ||||
| 
 | ||||
| 		@Override | ||||
|  |  | |||
|  | @ -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<Foo> fooPrinter() { | ||||
| 			return new FooPrinter(); | ||||
| 		} | ||||
| 
 | ||||
| 		private static class FooPrinter implements Printer<Foo> { | ||||
| 
 | ||||
| 			@Override | ||||
| 			public String print(Foo foo, Locale locale) { | ||||
| 				return foo.toString(); | ||||
| 			} | ||||
| 
 | ||||
| 		public Printer<Example> examplePrinter() { | ||||
| 			return new ExamplePrinter(); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
|  | @ -583,34 +573,42 @@ class WebFluxAutoConfigurationTests { | |||
| 	static class ParserConfiguration { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		public Parser<Foo> fooParser() { | ||||
| 			return new FooParser(); | ||||
| 		} | ||||
| 
 | ||||
| 		private static class FooParser implements Parser<Foo> { | ||||
| 
 | ||||
| 			@Override | ||||
| 			public Foo parse(String source, Locale locale) { | ||||
| 				return new Foo(source); | ||||
| 			} | ||||
| 
 | ||||
| 		public Parser<Example> exampleParser() { | ||||
| 			return new ExampleParser(); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	static class Foo { | ||||
| 	static final class Example { | ||||
| 
 | ||||
| 		private final String name; | ||||
| 
 | ||||
| 		Foo(String name) { | ||||
| 		private Example(String name, Date date) { | ||||
| 			this.name = name; | ||||
| 		} | ||||
| 
 | ||||
| 		@Override | ||||
| 		public String toString() { | ||||
| 		public String getName() { | ||||
| 			return this.name; | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static class ExamplePrinter implements Printer<Example> { | ||||
| 
 | ||||
| 		@Override | ||||
| 		public String print(Example example, Locale locale) { | ||||
| 			return example.getName(); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static class ExampleParser implements Parser<Example> { | ||||
| 
 | ||||
| 		@Override | ||||
| 		public Example parse(String source, Locale locale) { | ||||
| 			return new Example(source, new Date()); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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<Foo> fooPrinter() { | ||||
| 			return new FooPrinter(); | ||||
| 		} | ||||
| 
 | ||||
| 		private static class FooPrinter implements Printer<Foo> { | ||||
| 
 | ||||
| 			@Override | ||||
| 			public String print(Foo foo, Locale locale) { | ||||
| 				return foo.toString(); | ||||
| 			} | ||||
| 
 | ||||
| 		public Printer<Example> examplePrinter() { | ||||
| 			return new ExamplePrinter(); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
|  | @ -1129,34 +1119,42 @@ class WebMvcAutoConfigurationTests { | |||
| 	static class ParserConfiguration { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		public Parser<Foo> fooParser() { | ||||
| 			return new FooParser(); | ||||
| 		} | ||||
| 
 | ||||
| 		private static class FooParser implements Parser<Foo> { | ||||
| 
 | ||||
| 			@Override | ||||
| 			public Foo parse(String source, Locale locale) { | ||||
| 				return new Foo(source); | ||||
| 			} | ||||
| 
 | ||||
| 		public Parser<Example> exampleParser() { | ||||
| 			return new ExampleParser(); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	static class Foo { | ||||
| 	static final class Example { | ||||
| 
 | ||||
| 		private final String name; | ||||
| 
 | ||||
| 		Foo(String name) { | ||||
| 		private Example(String name, Date date) { | ||||
| 			this.name = name; | ||||
| 		} | ||||
| 
 | ||||
| 		@Override | ||||
| 		public String toString() { | ||||
| 		public String getName() { | ||||
| 			return this.name; | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static class ExamplePrinter implements Printer<Example> { | ||||
| 
 | ||||
| 		@Override | ||||
| 		public String print(Example example, Locale locale) { | ||||
| 			return example.getName(); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static class ExampleParser implements Parser<Example> { | ||||
| 
 | ||||
| 		@Override | ||||
| 		public Example parse(String source, Locale locale) { | ||||
| 			return new Example(source, new Date()); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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<Object> 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); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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 <T>} using the underlying | ||||
|  * {@link Parser}{@code <T>}. | ||||
|  * | ||||
|  * @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<ConvertiblePair> 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 <T> generic type?"); | ||||
| 		} | ||||
| 		return type; | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -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 <T>} to a {@link String} using the underlying | ||||
|  * {@link Printer}{@code <T>}. | ||||
|  * | ||||
|  * @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<ConvertiblePair> 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 <T> generic type?"); | ||||
| 		} | ||||
| 		return type; | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -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<ConvertiblePair> getConvertibleTypes() { | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 		@Override | ||||
| 		public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static class ExampleConverter implements Converter<String, Integer> { | ||||
| 
 | ||||
| 		@Override | ||||
| 		public Integer convert(String source) { | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static class ExampleFormatter implements Formatter<Integer> { | ||||
| 
 | ||||
| 		@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<Integer> { | ||||
| 
 | ||||
| 		@Override | ||||
| 		public Integer parse(String text, Locale locale) throws ParseException { | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static class ExamplePrinter implements Printer<Integer> { | ||||
| 
 | ||||
| 		@Override | ||||
| 		public String print(Integer object, Locale locale) { | ||||
| 			return 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> T convert(String source, Class<T> type) { | ||||
| 		return type.cast(this.conversionService.convert(source, TypeDescriptor.valueOf(String.class), | ||||
| 				TypeDescriptor.valueOf(type))); | ||||
| 	} | ||||
| 
 | ||||
| 	private static class DataSizeParser implements Parser<DataSize> { | ||||
| 
 | ||||
| 		@Override | ||||
| 		public DataSize parse(String value, Locale locale) { | ||||
| 			return DataSize.parse(value); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static class DurationParser implements Parser<Duration> { | ||||
| 
 | ||||
| 		@Override | ||||
| 		public Duration parse(String value, Locale locale) { | ||||
| 			return Duration.parse(value); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static class ObjectParser implements Parser<Object> { | ||||
| 
 | ||||
| 		@Override | ||||
| 		public Object parse(String source, Locale locale) throws ParseException { | ||||
| 			throw new ParseException("", 0); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -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 <T> String convert(T source, Class<T> type) { | ||||
| 		return (String) this.conversionService.convert(source, TypeDescriptor.valueOf(type), | ||||
| 				TypeDescriptor.valueOf(String.class)); | ||||
| 	} | ||||
| 
 | ||||
| 	private static class DataSizePrinter implements Printer<DataSize> { | ||||
| 
 | ||||
| 		@Override | ||||
| 		public String print(DataSize dataSize, Locale locale) { | ||||
| 			return dataSize.toString(); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static class DurationPrinter implements Printer<Duration> { | ||||
| 
 | ||||
| 		@Override | ||||
| 		public String print(Duration duration, Locale locale) { | ||||
| 			return duration.toString(); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
		Loading…
	
		Reference in New Issue