Skip to content

Commit

Permalink
Add Printer<T> and Parser<T> to FormatterRegistry if they have been e…
Browse files Browse the repository at this point in the history
…xposed as beans.

see gh-16171
  • Loading branch information
nosan committed Jun 8, 2019
1 parent d9c93bd commit 7ae0f88
Show file tree
Hide file tree
Showing 8 changed files with 550 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
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;
Expand All @@ -48,6 +50,8 @@
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;
Expand Down Expand Up @@ -185,6 +189,17 @@ public void addFormatters(FormatterRegistry registry) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
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;
Expand All @@ -74,6 +76,8 @@
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;
Expand Down Expand Up @@ -307,6 +311,16 @@ public void addFormatters(FormatterRegistry registry) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;

Expand All @@ -40,7 +41,10 @@
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;
Expand Down Expand Up @@ -373,6 +377,17 @@ void cacheControl() {
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<PathPattern, Object> getHandlerMap(ApplicationContext context) {
HandlerMapping mapping = context.getBean("resourceHandlerMapping", HandlerMapping.class);
if (mapping instanceof SimpleUrlHandlerMapping) {
Expand Down Expand Up @@ -545,4 +560,57 @@ private static class MyRequestMappingHandlerMapping extends RequestMappingHandle

}

@Configuration(proxyBeanMethods = false)
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();
}

}

}

@Configuration(proxyBeanMethods = false)
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);
}

}

}

static class Foo {

private final String name;

Foo(String name) {
this.name = name;
}

@Override
public String toString() {
return this.name;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
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;
Expand Down Expand Up @@ -773,6 +775,17 @@ void whenUserDefinesARequestContextFilterRegistrationTheAutoConfiguredFilterBack
});
}

@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<String, Object> handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class));
assertThat(handlerMap).hasSize(2);
Expand Down Expand Up @@ -1093,4 +1106,57 @@ public FilterRegistrationBean<RequestContextFilter> customRequestContextFilterRe

}

@Configuration(proxyBeanMethods = false)
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();
}

}

}

@Configuration(proxyBeanMethods = false)
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);
}

}

}

static class Foo {

private final String name;

Foo(String name) {
this.name = name;
}

@Override
public String toString() {
return this.name;
}

}

}
Original file line number Diff line number Diff line change
@@ -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 <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;
}

}
Loading

0 comments on commit 7ae0f88

Please sign in to comment.