Skip to content

Commit 580e55a

Browse files
DATACMNS-1615 - Allow fine grained store specific converter registration.
CustomConversions now registers all given user defined converters and selects only those converters from the StoreConversions that convert to or from a store supported simple type. By doing so we make sure to only register supported converters which removes warning messages from the log and reduces the number of converters within the ConversionService.
1 parent 6af485b commit 580e55a

File tree

2 files changed

+196
-22
lines changed

2 files changed

+196
-22
lines changed

Diff for: src/main/java/org/springframework/data/convert/CustomConversions.java

+170-20
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.*;
2626
import java.util.concurrent.ConcurrentHashMap;
2727
import java.util.function.Function;
28+
import java.util.stream.Collectors;
2829

2930
import org.springframework.core.GenericTypeResolver;
3031
import org.springframework.core.convert.converter.Converter;
@@ -57,6 +58,10 @@ public class CustomConversions {
5758
private static final String READ_CONVERTER_NOT_SIMPLE = "Registering converter from %s to %s as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.";
5859
private static final String WRITE_CONVERTER_NOT_SIMPLE = "Registering converter from %s to %s as writing converter although it doesn't convert to a store-supported type! You might wanna check you annotation setup at the converter implementation.";
5960
private static final String NOT_A_CONVERTER = "Converter %s is neither a Spring Converter, GenericConverter or ConverterFactory!";
61+
private static final String CONVERTER_FILTER = "converter from %s to %s as %s converter.";
62+
private static final String ADD_CONVERTER = "Adding %s" + CONVERTER_FILTER;
63+
private static final String SKIP_CONVERTER = "Skipping " + CONVERTER_FILTER
64+
+ "%s is not a store supported simple type!";
6065
private static final List<Object> DEFAULT_CONVERTERS;
6166

6267
static {
@@ -89,7 +94,10 @@ public class CustomConversions {
8994
convertiblePair.getSourceType(), null, writingPairs);
9095

9196
/**
92-
* Creates a new {@link CustomConversions} instance registering the given converters.
97+
* Creates a new {@link CustomConversions} instance registering all given user defined converters and selecting
98+
* {@link Converter converters} from {@link StoreConversions} depending on
99+
* {@link StoreConversions#getSimpleTypeHolder() store simple types} only considering those that either convert
100+
* to/from a store supported type.
93101
*
94102
* @param storeConversions must not be {@literal null}.
95103
* @param converters must not be {@literal null}.
@@ -99,21 +107,47 @@ public CustomConversions(StoreConversions storeConversions, Collection<?> conver
99107
Assert.notNull(storeConversions, "StoreConversions must not be null!");
100108
Assert.notNull(converters, "List of converters must not be null!");
101109

102-
List<Object> toRegister = new ArrayList<>();
110+
List<Object> registeredConverters = collectPotentialConverterRegistrations(storeConversions, converters).stream() //
111+
.filter(this::isSupportedConverter) //
112+
.map(ConverterRegistrationIntent::getConverterRegistration) //
113+
.map(this::register) //
114+
.distinct() //
115+
.collect(Collectors.toList());
103116

104-
// Add user provided converters to make sure they can override the defaults
105-
toRegister.addAll(converters);
106-
toRegister.addAll(storeConversions.getStoreConverters());
107-
toRegister.addAll(DEFAULT_CONVERTERS);
117+
Collections.reverse(registeredConverters);
108118

109-
toRegister.stream()//
110-
.flatMap(it -> storeConversions.getRegistrationsFor(it).stream())//
111-
.forEach(this::register);
119+
this.converters = Collections.unmodifiableList(registeredConverters);
120+
this.simpleTypeHolder = new SimpleTypeHolder(customSimpleTypes, storeConversions.getStoreTypeHolder());
121+
}
112122

113-
Collections.reverse(toRegister);
123+
/**
124+
* Validate a given {@link ConverterRegistration} in a specific setup. <br />
125+
* Non {@link ReadingConverter reading} and user defined {@link Converter converters} are only considered supported if
126+
* the {@link ConverterRegistrationIntent#isSimpleTargetType() target type} is considered to be a store simple type.
127+
*
128+
* @param registration
129+
* @return
130+
* @since 2.3
131+
*/
132+
protected boolean isSupportedConverter(ConverterRegistrationIntent registration) {
114133

115-
this.converters = Collections.unmodifiableList(toRegister);
116-
this.simpleTypeHolder = new SimpleTypeHolder(customSimpleTypes, storeConversions.getStoreTypeHolder());
134+
boolean register = registration.isUserConverter() || (registration.isReading() && registration.isSimpleSourceType())
135+
|| (registration.isWriting() && registration.isSimpleTargetType());
136+
137+
if (LOG.isDebugEnabled()) {
138+
139+
if (register) {
140+
LOG.debug(String.format(ADD_CONVERTER, registration.isUserConverter() ? "user defined " : "",
141+
registration.getSourceType(), registration.getTargetType(),
142+
registration.isReading() ? "reading" : "writing"));
143+
} else {
144+
LOG.debug(String.format(SKIP_CONVERTER, registration.getSourceType(), registration.getTargetType(),
145+
registration.isReading() ? "reading" : "writing",
146+
registration.isReading() ? registration.getSourceType() : registration.getTargetType()));
147+
}
148+
}
149+
150+
return register;
117151
}
118152

119153
/**
@@ -152,6 +186,36 @@ public void registerConvertersIn(ConverterRegistry conversionService) {
152186
converters.forEach(it -> registerConverterIn(it, conversionService));
153187
}
154188

189+
/**
190+
* Get all converters and add some origin information
191+
*
192+
* @param storeConversions
193+
* @param converters
194+
* @return
195+
*/
196+
private List<ConverterRegistrationIntent> collectPotentialConverterRegistrations(StoreConversions storeConversions,
197+
Collection<?> converters) {
198+
199+
List<ConverterRegistrationIntent> converterRegistrations = new ArrayList<>();
200+
201+
converterRegistrations.addAll(converters.stream() //
202+
.flatMap(it -> storeConversions.getRegistrationsFor(it).stream()) //
203+
.map(ConverterRegistrationIntent::userConverters) //
204+
.collect(Collectors.toList()));
205+
206+
converterRegistrations.addAll(storeConversions.getStoreConverters().stream() //
207+
.flatMap(it -> storeConversions.getRegistrationsFor(it).stream()) //
208+
.map(ConverterRegistrationIntent::storeConverters) //
209+
.collect(Collectors.toList()));
210+
211+
converterRegistrations.addAll(DEFAULT_CONVERTERS.stream() //
212+
.flatMap(it -> storeConversions.getRegistrationsFor(it).stream()) //
213+
.map(ConverterRegistrationIntent::defaultConverters) //
214+
.collect(Collectors.toList()));
215+
216+
return converterRegistrations;
217+
}
218+
155219
/**
156220
* Registers the given converter in the given {@link GenericConversionService}.
157221
*
@@ -193,7 +257,7 @@ private void registerConverterIn(Object candidate, ConverterRegistry conversionS
193257
*
194258
* @param converterRegistration
195259
*/
196-
private void register(ConverterRegistration converterRegistration) {
260+
private Object register(ConverterRegistration converterRegistration) {
197261

198262
Assert.notNull(converterRegistration, "Converter registration must not be null!");
199263

@@ -217,6 +281,8 @@ private void register(ConverterRegistration converterRegistration) {
217281
LOG.warn(String.format(WRITE_CONVERTER_NOT_SIMPLE, pair.getSourceType(), pair.getTargetType()));
218282
}
219283
}
284+
285+
return converterRegistration.getConverter();
220286
}
221287

222288
/**
@@ -442,15 +508,90 @@ public Class<?> computeIfAbsent(Class<?> targetType, Function<ConvertiblePair, C
442508
}
443509
}
444510

511+
/**
512+
* Value class tying together a {@link ConverterRegistration} and its {@link ConverterOrigin origin} to allow fine
513+
* grained registration based on store supported types.
514+
*
515+
* @since 2.3
516+
* @author Christoph Strobl
517+
*/
518+
protected static class ConverterRegistrationIntent {
519+
520+
private final ConverterRegistration delegate;
521+
private final ConverterOrigin origin;
522+
523+
ConverterRegistrationIntent(ConverterRegistration delegate, ConverterOrigin origin) {
524+
this.delegate = delegate;
525+
this.origin = origin;
526+
}
527+
528+
static ConverterRegistrationIntent userConverters(ConverterRegistration delegate) {
529+
return new ConverterRegistrationIntent(delegate, ConverterOrigin.USER_DEFINED);
530+
}
531+
532+
static ConverterRegistrationIntent storeConverters(ConverterRegistration delegate) {
533+
return new ConverterRegistrationIntent(delegate, ConverterOrigin.STORE);
534+
}
535+
536+
static ConverterRegistrationIntent defaultConverters(ConverterRegistration delegate) {
537+
return new ConverterRegistrationIntent(delegate, ConverterOrigin.DEFAULT);
538+
}
539+
540+
Class<?> getSourceType() {
541+
return delegate.getConvertiblePair().getSourceType();
542+
}
543+
544+
Class<?> getTargetType() {
545+
return delegate.getConvertiblePair().getTargetType();
546+
}
547+
548+
public boolean isWriting() {
549+
return delegate.isWriting();
550+
}
551+
552+
public boolean isReading() {
553+
return delegate.isReading();
554+
}
555+
556+
public boolean isSimpleSourceType() {
557+
return delegate.isSimpleSourceType();
558+
}
559+
560+
public boolean isSimpleTargetType() {
561+
return delegate.isSimpleTargetType();
562+
}
563+
564+
public boolean isUserConverter() {
565+
return isConverterOfSource(ConverterOrigin.USER_DEFINED);
566+
}
567+
568+
public boolean isDefaultConveter() {
569+
return isConverterOfSource(ConverterOrigin.DEFAULT);
570+
}
571+
572+
public ConverterRegistration getConverterRegistration() {
573+
return delegate;
574+
}
575+
576+
private boolean isConverterOfSource(ConverterOrigin source) {
577+
return origin.equals(source);
578+
}
579+
580+
protected enum ConverterOrigin {
581+
DEFAULT, USER_DEFINED, STORE
582+
}
583+
}
584+
445585
/**
446586
* Conversion registration information.
447587
*
448588
* @author Oliver Gierke
449589
* @author Mark Paluch
450590
*/
451591
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
452-
static class ConverterRegistration {
592+
private static class ConverterRegistration {
453593

594+
private final Object converter;
454595
private final @NonNull ConvertiblePair convertiblePair;
455596
private final @NonNull StoreConversions storeConversions;
456597
private final boolean reading;
@@ -500,6 +641,13 @@ public boolean isSimpleSourceType() {
500641
public boolean isSimpleTargetType() {
501642
return storeConversions.isStoreSimpleType(convertiblePair.getTargetType());
502643
}
644+
645+
/**
646+
* @return
647+
*/
648+
Object getConverter() {
649+
return converter;
650+
}
503651
}
504652

505653
/**
@@ -575,7 +723,7 @@ public Streamable<ConverterRegistration> getRegistrationsFor(Object converter) {
575723

576724
return convertibleTypes == null //
577725
? Streamable.empty() //
578-
: Streamable.of(convertibleTypes).map(it -> register(it, isReading, isWriting));
726+
: Streamable.of(convertibleTypes).map(it -> register(converter, it, isReading, isWriting));
579727

580728
} else if (converter instanceof ConverterFactory) {
581729

@@ -600,15 +748,17 @@ private Streamable<ConverterRegistration> getRegistrationFor(Object converter, C
600748
throw new IllegalStateException(String.format("Couldn't resolve type arguments for %s!", converterType));
601749
}
602750

603-
return Streamable.of(register(arguments[0], arguments[1], isReading, isWriting));
751+
return Streamable.of(register(converter, arguments[0], arguments[1], isReading, isWriting));
604752
}
605753

606-
private ConverterRegistration register(Class<?> source, Class<?> target, boolean isReading, boolean isWriting) {
607-
return register(new ConvertiblePair(source, target), isReading, isWriting);
754+
private ConverterRegistration register(Object converter, Class<?> source, Class<?> target, boolean isReading,
755+
boolean isWriting) {
756+
return register(converter, new ConvertiblePair(source, target), isReading, isWriting);
608757
}
609758

610-
private ConverterRegistration register(ConvertiblePair pair, boolean isReading, boolean isWriting) {
611-
return new ConverterRegistration(pair, this, isReading, isWriting);
759+
private ConverterRegistration register(Object converter, ConvertiblePair pair, boolean isReading,
760+
boolean isWriting) {
761+
return new ConverterRegistration(converter, pair, this, isReading, isWriting);
612762
}
613763

614764
private boolean isStoreSimpleType(Class<?> type) {

Diff for: src/test/java/org/springframework/data/convert/CustomConversionsUnitTests.java

+26-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.convert;
1717

1818
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.Mockito.*;
1920

2021
import java.text.DateFormat;
2122
import java.text.Format;
@@ -27,17 +28,17 @@
2728

2829
import org.joda.time.DateTime;
2930
import org.junit.Test;
30-
3131
import org.springframework.aop.framework.ProxyFactory;
3232
import org.springframework.core.convert.converter.Converter;
3333
import org.springframework.core.convert.converter.ConverterFactory;
34+
import org.springframework.core.convert.converter.ConverterRegistry;
3435
import org.springframework.core.convert.support.ConfigurableConversionService;
3536
import org.springframework.core.convert.support.DefaultConversionService;
3637
import org.springframework.core.convert.support.GenericConversionService;
3738
import org.springframework.data.convert.ConverterBuilder.ConverterAware;
3839
import org.springframework.data.convert.CustomConversions.StoreConversions;
40+
import org.springframework.data.convert.ThreeTenBackPortConverters.LocalDateTimeToJavaTimeInstantConverter;
3941
import org.springframework.data.mapping.model.SimpleTypeHolder;
40-
4142
import org.threeten.bp.LocalDateTime;
4243

4344
/**
@@ -194,6 +195,29 @@ public void registersConverterFromConverterAware() {
194195
assertThat(conversionService.canConvert(Locale.class, CustomType.class)).isTrue();
195196
}
196197

198+
@Test // DATACMNS-1615
199+
public void skipsUnsupportedDefaultWritingConverter() {
200+
201+
ConverterRegistry registry = mock(ConverterRegistry.class);
202+
StoreConversions storeConversions = StoreConversions
203+
.of(new SimpleTypeHolder(Collections.singleton(Date.class), true) {
204+
205+
@Override
206+
public boolean isSimpleType(Class<?> type) {
207+
208+
if (type.getName().startsWith("java.time")) {
209+
return false;
210+
}
211+
212+
return super.isSimpleType(type);
213+
}
214+
});
215+
216+
new CustomConversions(storeConversions, Collections.emptyList()).registerConvertersIn(registry);
217+
218+
verify(registry, never()).addConverter(any(LocalDateTimeToJavaTimeInstantConverter.class));
219+
}
220+
197221
private static Class<?> createProxyTypeFor(Class<?> type) {
198222

199223
ProxyFactory factory = new ProxyFactory();

0 commit comments

Comments
 (0)