Skip to content

Commit b22ef9e

Browse files
committed
Registers Printer<T> and Parser<T> as converters
if they have been exposed as beans. see spring-projectsgh-16171
1 parent 05ad955 commit b22ef9e

File tree

8 files changed

+573
-0
lines changed

8 files changed

+573
-0
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import org.springframework.boot.autoconfigure.web.ResourceProperties;
3939
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
4040
import org.springframework.boot.context.properties.EnableConfigurationProperties;
41+
import org.springframework.boot.convert.ParserConverter;
42+
import org.springframework.boot.convert.PrinterConverter;
4143
import org.springframework.boot.web.codec.CodecCustomizer;
4244
import org.springframework.boot.web.reactive.filter.OrderedHiddenHttpMethodFilter;
4345
import org.springframework.context.annotation.Bean;
@@ -48,6 +50,8 @@
4850
import org.springframework.core.convert.converter.GenericConverter;
4951
import org.springframework.format.Formatter;
5052
import org.springframework.format.FormatterRegistry;
53+
import org.springframework.format.Parser;
54+
import org.springframework.format.Printer;
5155
import org.springframework.format.support.FormattingConversionService;
5256
import org.springframework.http.codec.ServerCodecConfigurer;
5357
import org.springframework.util.ClassUtils;
@@ -192,6 +196,17 @@ public void addFormatters(FormatterRegistry registry) {
192196
for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
193197
registry.addFormatter(formatter);
194198
}
199+
for (Printer<?> printer : getBeansOfType(Printer.class)) {
200+
if (!(printer instanceof Formatter<?>)) {
201+
registry.addConverter(new PrinterConverter(printer));
202+
203+
}
204+
}
205+
for (Parser<?> parser : getBeansOfType(Parser.class)) {
206+
if (!(parser instanceof Formatter<?>)) {
207+
registry.addConverter(new ParserConverter(parser));
208+
}
209+
}
195210
}
196211

197212
private <T> Collection<T> getBeansOfType(Class<T> type) {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy;
5656
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
5757
import org.springframework.boot.context.properties.EnableConfigurationProperties;
58+
import org.springframework.boot.convert.ParserConverter;
59+
import org.springframework.boot.convert.PrinterConverter;
5860
import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter;
5961
import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter;
6062
import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
@@ -74,6 +76,8 @@
7476
import org.springframework.core.task.AsyncTaskExecutor;
7577
import org.springframework.format.Formatter;
7678
import org.springframework.format.FormatterRegistry;
79+
import org.springframework.format.Parser;
80+
import org.springframework.format.Printer;
7781
import org.springframework.format.support.FormattingConversionService;
7882
import org.springframework.http.CacheControl;
7983
import org.springframework.http.MediaType;
@@ -320,6 +324,16 @@ public void addFormatters(FormatterRegistry registry) {
320324
for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
321325
registry.addFormatter(formatter);
322326
}
327+
for (Printer<?> printer : getBeansOfType(Printer.class)) {
328+
if (!(printer instanceof Formatter<?>)) {
329+
registry.addConverter(new PrinterConverter(printer));
330+
}
331+
}
332+
for (Parser<?> parser : getBeansOfType(Parser.class)) {
333+
if (!(parser instanceof Formatter<?>)) {
334+
registry.addConverter(new ParserConverter(parser));
335+
}
336+
}
323337
}
324338

325339
private <T> Collection<T> getBeansOfType(Class<T> type) {

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Collections;
2020
import java.util.Date;
2121
import java.util.List;
22+
import java.util.Locale;
2223
import java.util.Map;
2324
import java.util.concurrent.TimeUnit;
2425

@@ -40,7 +41,10 @@
4041
import org.springframework.context.annotation.Import;
4142
import org.springframework.core.Ordered;
4243
import org.springframework.core.annotation.Order;
44+
import org.springframework.core.convert.ConversionService;
4345
import org.springframework.core.io.ClassPathResource;
46+
import org.springframework.format.Parser;
47+
import org.springframework.format.Printer;
4448
import org.springframework.format.support.FormattingConversionService;
4549
import org.springframework.http.CacheControl;
4650
import org.springframework.http.codec.ServerCodecConfigurer;
@@ -450,6 +454,20 @@ public void cacheControl() {
450454
Assertions.setExtractBareNamePropertyMethods(true);
451455
}
452456

457+
@Test
458+
void customPrinterAndParserShouldBeRegisteredAsConverters() {
459+
this.contextRunner.withUserConfiguration(ParserConfiguration.class,
460+
PrinterConfiguration.class).run((context) -> {
461+
Foo foo = new Foo("bar");
462+
ConversionService conversionService = context
463+
.getBean(ConversionService.class);
464+
assertThat(conversionService.convert(foo, String.class))
465+
.isEqualTo("bar");
466+
assertThat(conversionService.convert("bar", Foo.class))
467+
.extracting(Foo::toString).isEqualTo("bar");
468+
});
469+
}
470+
453471
private Map<PathPattern, Object> getHandlerMap(ApplicationContext context) {
454472
HandlerMapping mapping = context.getBean("resourceHandlerMapping",
455473
HandlerMapping.class);
@@ -625,4 +643,57 @@ private static class MyRequestMappingHandlerMapping
625643

626644
}
627645

646+
@Configuration(proxyBeanMethods = false)
647+
static class PrinterConfiguration {
648+
649+
@Bean
650+
public Printer<Foo> fooPrinter() {
651+
return new PrinterConfiguration.FooPrinter();
652+
}
653+
654+
private static class FooPrinter implements Printer<Foo> {
655+
656+
@Override
657+
public String print(Foo foo, Locale locale) {
658+
return foo.toString();
659+
}
660+
661+
}
662+
663+
}
664+
665+
@Configuration(proxyBeanMethods = false)
666+
static class ParserConfiguration {
667+
668+
@Bean
669+
public Parser<Foo> fooParser() {
670+
return new ParserConfiguration.FooParser();
671+
}
672+
673+
private static class FooParser implements Parser<Foo> {
674+
675+
@Override
676+
public Foo parse(String source, Locale locale) {
677+
return new Foo(source);
678+
}
679+
680+
}
681+
682+
}
683+
684+
static class Foo {
685+
686+
private final String name;
687+
688+
Foo(String name) {
689+
this.name = name;
690+
}
691+
692+
@Override
693+
public String toString() {
694+
return this.name;
695+
}
696+
697+
}
698+
628699
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
import org.springframework.core.io.ClassPathResource;
5757
import org.springframework.core.io.Resource;
5858
import org.springframework.core.task.AsyncTaskExecutor;
59+
import org.springframework.format.Parser;
60+
import org.springframework.format.Printer;
5961
import org.springframework.format.support.FormattingConversionService;
6062
import org.springframework.http.CacheControl;
6163
import org.springframework.http.HttpHeaders;
@@ -941,6 +943,20 @@ public void whenUserDefinesARequestContextFilterRegistrationTheAutoConfiguredFil
941943
});
942944
}
943945

946+
@Test
947+
void customPrinterAndParserShouldBeRegisteredAsConverters() {
948+
this.contextRunner.withUserConfiguration(ParserConfiguration.class,
949+
PrinterConfiguration.class).run((context) -> {
950+
Foo foo = new Foo("bar");
951+
ConversionService conversionService = context
952+
.getBean(ConversionService.class);
953+
assertThat(conversionService.convert(foo, String.class))
954+
.isEqualTo("bar");
955+
assertThat(conversionService.convert("bar", Foo.class))
956+
.extracting(Foo::toString).isEqualTo("bar");
957+
});
958+
}
959+
944960
private void assertCacheControl(AssertableWebApplicationContext context) {
945961
Map<String, Object> handlerMap = getHandlerMap(
946962
context.getBean("resourceHandlerMapping", HandlerMapping.class));
@@ -1280,4 +1296,57 @@ public FilterRegistrationBean<RequestContextFilter> customRequestContextFilterRe
12801296

12811297
}
12821298

1299+
@Configuration(proxyBeanMethods = false)
1300+
static class PrinterConfiguration {
1301+
1302+
@Bean
1303+
public Printer<Foo> fooPrinter() {
1304+
return new FooPrinter();
1305+
}
1306+
1307+
private static class FooPrinter implements Printer<Foo> {
1308+
1309+
@Override
1310+
public String print(Foo foo, Locale locale) {
1311+
return foo.toString();
1312+
}
1313+
1314+
}
1315+
1316+
}
1317+
1318+
@Configuration(proxyBeanMethods = false)
1319+
static class ParserConfiguration {
1320+
1321+
@Bean
1322+
public Parser<Foo> fooParser() {
1323+
return new FooParser();
1324+
}
1325+
1326+
private static class FooParser implements Parser<Foo> {
1327+
1328+
@Override
1329+
public Foo parse(String source, Locale locale) {
1330+
return new Foo(source);
1331+
}
1332+
1333+
}
1334+
1335+
}
1336+
1337+
static class Foo {
1338+
1339+
private final String name;
1340+
1341+
Foo(String name) {
1342+
this.name = name;
1343+
}
1344+
1345+
@Override
1346+
public String toString() {
1347+
return this.name;
1348+
}
1349+
1350+
}
1351+
12831352
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.convert;
18+
19+
import java.text.ParseException;
20+
import java.util.Collections;
21+
import java.util.Set;
22+
23+
import org.springframework.context.i18n.LocaleContextHolder;
24+
import org.springframework.core.DecoratingProxy;
25+
import org.springframework.core.GenericTypeResolver;
26+
import org.springframework.core.convert.TypeDescriptor;
27+
import org.springframework.core.convert.converter.Converter;
28+
import org.springframework.core.convert.converter.GenericConverter;
29+
import org.springframework.format.Parser;
30+
import org.springframework.util.Assert;
31+
import org.springframework.util.StringUtils;
32+
33+
/**
34+
* {@link Converter} to convert from a {@link String} to {@code <T>} using the underlying
35+
* {@link Parser}{@code <T>}.
36+
*
37+
* @author Dmytro Nosan
38+
* @since 2.2.0
39+
*/
40+
public class ParserConverter implements GenericConverter {
41+
42+
private final Class<?> type;
43+
44+
private final Parser<?> parser;
45+
46+
/**
47+
* Creates a {@code Converter} to convert {@code String} to a {@code T} via parser.
48+
* @param parser parses {@code String} to a {@code T}
49+
*/
50+
public ParserConverter(Parser<?> parser) {
51+
Assert.notNull(parser, "Parser must not be null");
52+
this.type = getType(parser);
53+
this.parser = parser;
54+
}
55+
56+
@Override
57+
public Set<ConvertiblePair> getConvertibleTypes() {
58+
return Collections.singleton(new ConvertiblePair(String.class, this.type));
59+
}
60+
61+
@Override
62+
public Object convert(Object source, TypeDescriptor sourceType,
63+
TypeDescriptor targetType) {
64+
String value = (String) source;
65+
if (!StringUtils.hasText(value)) {
66+
return null;
67+
}
68+
try {
69+
return this.parser.parse(value, LocaleContextHolder.getLocale());
70+
}
71+
catch (ParseException ex) {
72+
throw new IllegalArgumentException("Value [" + value + "] can not be parsed",
73+
ex);
74+
}
75+
}
76+
77+
@Override
78+
public String toString() {
79+
return String.class.getName() + " -> " + this.type.getName() + " : "
80+
+ this.parser;
81+
}
82+
83+
private static Class<?> getType(Parser<?> parser) {
84+
Class<?> type = GenericTypeResolver.resolveTypeArgument(parser.getClass(),
85+
Parser.class);
86+
if (type == null && parser instanceof DecoratingProxy) {
87+
type = GenericTypeResolver.resolveTypeArgument(
88+
((DecoratingProxy) parser).getDecoratedClass(), Parser.class);
89+
}
90+
if (type == null) {
91+
throw new IllegalArgumentException(
92+
"Unable to extract the parameterized type from Parser: '"
93+
+ parser.getClass().getName()
94+
+ "'. Does the class parameterize the <T> generic type?");
95+
}
96+
return type;
97+
}
98+
99+
}

0 commit comments

Comments
 (0)