Commit 0f642df5 authored by Phillip Webb's avatar Phillip Webb

Merge pull request #17064 from nosan

* pr/17064:
  Polish "Add Printer and Parser beans to conversion service"
  Add Printer and Parser beans to conversion service

Closes gh-17064
parents 30cfe7b4 9db20313
......@@ -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,15 +37,13 @@ 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.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.support.FormattingConversionService;
import org.springframework.http.codec.ServerCodecConfigurer;
......@@ -176,26 +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);
}
}
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,6 +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.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;
......@@ -66,13 +66,10 @@ 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.support.FormattingConversionService;
import org.springframework.http.CacheControl;
......@@ -298,19 +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);
}
}
private <T> Collection<T> getBeansOfType(Class<T> type) {
return this.beanFactory.getBeansOfType(type).values();
ApplicationConversionService.addBeans(registry, this.beanFactory);
}
@Override
......
......@@ -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,16 @@ class WebFluxAutoConfigurationTests {
Assertions.setExtractBareNamePropertyMethods(true);
}
@Test
void customPrinterAndParserShouldBeRegisteredAsConverters() {
this.contextRunner.withUserConfiguration(ParserConfiguration.class, PrinterConfiguration.class)
.run((context) -> {
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");
});
}
private Map<PathPattern, Object> getHandlerMap(ApplicationContext context) {
HandlerMapping mapping = context.getBean("resourceHandlerMapping", HandlerMapping.class);
if (mapping instanceof SimpleUrlHandlerMapping) {
......@@ -545,4 +559,56 @@ class WebFluxAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class PrinterConfiguration {
@Bean
public Printer<Example> examplePrinter() {
return new ExamplePrinter();
}
}
@Configuration(proxyBeanMethods = false)
static class ParserConfiguration {
@Bean
public Parser<Example> exampleParser() {
return new ExampleParser();
}
}
static final class Example {
private final String name;
private Example(String name, Date date) {
this.name = name;
}
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());
}
}
}
......@@ -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,16 @@ class WebMvcAutoConfigurationTests {
});
}
@Test
void customPrinterAndParserShouldBeRegisteredAsConverters() {
this.contextRunner.withUserConfiguration(ParserConfiguration.class, PrinterConfiguration.class)
.run((context) -> {
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");
});
}
private void assertCacheControl(AssertableWebApplicationContext context) {
Map<String, Object> handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class));
assertThat(handlerMap).hasSize(2);
......@@ -1093,4 +1105,56 @@ class WebMvcAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class PrinterConfiguration {
@Bean
public Printer<Example> examplePrinter() {
return new ExamplePrinter();
}
}
@Configuration(proxyBeanMethods = false)
static class ParserConfiguration {
@Bean
public Parser<Example> exampleParser() {
return new ExampleParser();
}
}
static final class Example {
private final String name;
private Example(String name, Date date) {
this.name = name;
}
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());
}
}
}
/*
* 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);
}
}
}
}
/*
* 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;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment