Skip to content

Commit a190c08

Browse files
committed
DATACMNS-1034 - Introduced API to easily register converters using Java 8 lambdas.
Introduced ConverterBuilder which exposes API to register Spring GenericConverters for the use with a store module's CustomConversions. This allows simple registration of such converters using Java 8 lambdas. ConverterAware converters = ConverterBuilder.reading(String.class, Long.class, it -> Long.valueOf(it)).andWriting(it -> Object::toString); The setup can also be done from the reading side which would just need the method invocations inverted. The resulting ConverterAware will expose the registered converters so that they can be easily handed to a CustomConversions instance. Partial creation of either reading or writing converters is possible, too with the returned instance then only exposing one of the two converters. CustomConversions now considers ConverterAware and treats them appropriately for ConvertiblePair registration as well as during the registration in the ConversionService. Tweaked parameter types in CustomConversions to rather accept a Collection<?> for the converters (previously List<?>). Also, registerConverterIn(…) now takes a ConverterRegistry over a GenericConversionService. Polished Javadoc and non-null assertions. Original pull request: #209.
1 parent 3fb7c87 commit a190c08

File tree

5 files changed

+479
-25
lines changed

5 files changed

+479
-25
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
* Copyright 2017 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+
* http://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+
package org.springframework.data.convert;
17+
18+
import java.util.Optional;
19+
import java.util.Set;
20+
import java.util.function.Function;
21+
22+
import org.springframework.core.convert.converter.GenericConverter;
23+
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
24+
import org.springframework.util.Assert;
25+
26+
/**
27+
* API to easily set up {@link GenericConverter} instances using Java 8 lambdas, mostly in bidirectional fashion for
28+
* easy registration as custom type converters of the Spring Data mapping subsystem. The registration starts either with
29+
* the definition of a reading or writing converter that can then be completed.
30+
*
31+
* @author Oliver Gierke
32+
* @since 2.0
33+
* @see #reading(Class, Class, Function)
34+
* @see #writing(Class, Class, Function)
35+
* @soundtrack John Mayer - Moving On and Getting Over (The Search for Everything)
36+
*/
37+
public interface ConverterBuilder {
38+
39+
/**
40+
* Creates a new {@link ReadingConverterBuilder} to produce a converter to read values of the given source (the store
41+
* type) into the given target (the domain type).
42+
*
43+
* @param source must not be {@literal null}.
44+
* @param target must not be {@literal null}.
45+
* @param function must not be {@literal null}.
46+
* @return
47+
*/
48+
static <S, T> ReadingConverterBuilder<S, T> reading(Class<S> source, Class<T> target,
49+
Function<? super S, ? extends T> function) {
50+
51+
Assert.notNull(source, "Source type must not be null!");
52+
Assert.notNull(target, "Target type must not be null!");
53+
Assert.notNull(function, "Conversion function must not be null!");
54+
55+
return new DefaultConverterBuilder<>(new ConvertiblePair(source, target), Optional.empty(), Optional.of(function));
56+
}
57+
58+
/**
59+
* Creates a new {@link WritingConverterBuilder} to produce a converter to write values of the given source (the
60+
* domain type) into the given target (the store type).
61+
*
62+
* @param source must not be {@literal null}.
63+
* @param target must not be {@literal null}.
64+
* @param function must not be {@literal null}.
65+
* @return
66+
*/
67+
static <S, T> WritingConverterBuilder<S, T> writing(Class<S> source, Class<T> target,
68+
Function<? super S, ? extends T> function) {
69+
70+
Assert.notNull(source, "Source type must not be null!");
71+
Assert.notNull(target, "Target type must not be null!");
72+
Assert.notNull(function, "Conversion function must not be null!");
73+
74+
return new DefaultConverterBuilder<>(new ConvertiblePair(target, source), Optional.of(function), Optional.empty());
75+
}
76+
77+
/**
78+
* Returns all {@link GenericConverter} instances to be registered for the current {@link ConverterBuilder}.
79+
*
80+
* @return
81+
*/
82+
Set<GenericConverter> getConverters();
83+
84+
/**
85+
* Exposes a writing converter.
86+
*
87+
* @author Oliver Gierke
88+
* @since 2.0
89+
*/
90+
interface WritingConverterAware {
91+
92+
/**
93+
* Returns the writing converter already created.
94+
*
95+
* @return
96+
*/
97+
GenericConverter getWritingConverter();
98+
}
99+
100+
/**
101+
* Exposes a reading converter.
102+
*
103+
* @author Oliver Gierke
104+
* @since 2.0
105+
*/
106+
interface ReadingConverterAware {
107+
108+
/**
109+
* Returns the reading converter already created.
110+
*
111+
* @return
112+
*/
113+
GenericConverter getReadingConverter();
114+
}
115+
116+
/**
117+
* Interface to represent an intermediate setup step of {@link ConverterAware} defining a reading converter first.
118+
*
119+
* @author Oliver Gierke
120+
* @since 2.0
121+
*/
122+
interface ReadingConverterBuilder<T, S> extends ConverterBuilder, ReadingConverterAware {
123+
124+
/**
125+
* Creates a new {@link ConverterAware} by registering the given {@link Function} to add a write converter.
126+
*
127+
* @param function must not be {@literal null}.
128+
* @return
129+
*/
130+
ConverterAware andWriting(Function<? super S, ? extends T> function);
131+
}
132+
133+
/**
134+
* Interface to represent an intermediate setup step of {@link ConverterAware} defining a writing converter first.
135+
*
136+
* @author Oliver Gierke
137+
* @since 2.0
138+
*/
139+
interface WritingConverterBuilder<S, T> extends ConverterBuilder, WritingConverterAware {
140+
141+
/**
142+
* Creates a new {@link ConverterAware} by registering the given {@link Function} to add a write converter.
143+
*
144+
* @param function must not be {@literal null}.
145+
* @return
146+
*/
147+
ConverterAware andReading(Function<? super T, ? extends S> function);
148+
}
149+
150+
/**
151+
* A {@link ConverterBuilder} aware of both a reading and writing converter.
152+
*
153+
* @author Oliver Gierke
154+
* @since 2.0
155+
*/
156+
interface ConverterAware extends ConverterBuilder, ReadingConverterAware, WritingConverterAware {}
157+
}

src/main/java/org/springframework/data/convert/CustomConversions.java

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@
3737
import org.springframework.core.GenericTypeResolver;
3838
import org.springframework.core.convert.converter.Converter;
3939
import org.springframework.core.convert.converter.ConverterFactory;
40+
import org.springframework.core.convert.converter.ConverterRegistry;
4041
import org.springframework.core.convert.converter.GenericConverter;
4142
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
4243
import org.springframework.core.convert.support.GenericConversionService;
44+
import org.springframework.data.convert.ConverterBuilder.ConverterAware;
4345
import org.springframework.data.mapping.model.SimpleTypeHolder;
4446
import org.springframework.data.util.Optionals;
4547
import org.springframework.data.util.Streamable;
@@ -90,10 +92,12 @@ public class CustomConversions {
9092
/**
9193
* Creates a new {@link CustomConversions} instance registering the given converters.
9294
*
93-
* @param converters
95+
* @param storeConversions must not be {@literal null}.
96+
* @param converters must not be {@literal null}.
9497
*/
95-
public CustomConversions(StoreConversions storeConversions, List<?> converters) {
98+
public CustomConversions(StoreConversions storeConversions, Collection<?> converters) {
9699

100+
Assert.notNull(storeConversions, "StoreConversions must not be null!");
97101
Assert.notNull(converters, "List of converters must not be null!");
98102

99103
this.readingPairs = new LinkedHashSet<>();
@@ -149,33 +153,46 @@ public boolean isSimpleType(Class<?> type) {
149153
*
150154
* @param conversionService
151155
*/
152-
public void registerConvertersIn(GenericConversionService conversionService) {
156+
public void registerConvertersIn(ConverterRegistry conversionService) {
153157

154158
Assert.notNull(conversionService, "ConversionService must not be null!");
155159

156-
converters.forEach(it -> {
160+
converters.forEach(it -> registerConverterIn(it, conversionService));
161+
}
157162

158-
boolean added = false;
163+
/**
164+
* Registers the given converter in the given {@link GenericConversionService}.
165+
*
166+
* @param candidate must not be {@literal null}.
167+
* @param conversionService must not be {@literal null}.
168+
*/
169+
private void registerConverterIn(Object candidate, ConverterRegistry conversionService) {
159170

160-
if (it instanceof Converter) {
161-
conversionService.addConverter(Converter.class.cast(it));
162-
added = true;
163-
}
171+
boolean added = false;
164172

165-
if (it instanceof ConverterFactory) {
166-
conversionService.addConverterFactory(ConverterFactory.class.cast(it));
167-
added = true;
168-
}
173+
if (candidate instanceof Converter) {
174+
conversionService.addConverter(Converter.class.cast(candidate));
175+
added = true;
176+
}
169177

170-
if (it instanceof GenericConverter) {
171-
conversionService.addConverter(GenericConverter.class.cast(it));
172-
added = true;
173-
}
178+
if (candidate instanceof ConverterFactory) {
179+
conversionService.addConverterFactory(ConverterFactory.class.cast(candidate));
180+
added = true;
181+
}
174182

175-
if (!added) {
176-
throw new IllegalArgumentException(String.format(NOT_A_CONVERTER, it));
177-
}
178-
});
183+
if (candidate instanceof GenericConverter) {
184+
conversionService.addConverter(GenericConverter.class.cast(candidate));
185+
added = true;
186+
}
187+
188+
if (candidate instanceof ConverterAware) {
189+
ConverterAware.class.cast(candidate).getConverters().forEach(it -> registerConverterIn(it, conversionService));
190+
added = true;
191+
}
192+
193+
if (!added) {
194+
throw new IllegalArgumentException(String.format(NOT_A_CONVERTER, candidate));
195+
}
179196
}
180197

181198
/**
@@ -460,10 +477,15 @@ public Streamable<ConverterRegistration> getRegistrationsFor(Object converter) {
460477
boolean isWriting = type.isAnnotationPresent(WritingConverter.class);
461478
boolean isReading = type.isAnnotationPresent(ReadingConverter.class);
462479

463-
if (converter instanceof GenericConverter) {
480+
if (converter instanceof ConverterAware) {
481+
482+
return Streamable.of(() -> ConverterAware.class.cast(converter).getConverters().stream()//
483+
.flatMap(it -> getRegistrationsFor(it).stream()));
484+
485+
} else if (converter instanceof GenericConverter) {
464486

465-
GenericConverter genericConverter = (GenericConverter) converter;
466-
return Streamable.of(genericConverter.getConvertibleTypes()).map(it -> register(it, isReading, isWriting));
487+
return Streamable.of(GenericConverter.class.cast(converter).getConvertibleTypes())//
488+
.map(it -> register(it, isReading, isWriting));
467489

468490
} else if (converter instanceof ConverterFactory) {
469491

@@ -474,7 +496,7 @@ public Streamable<ConverterRegistration> getRegistrationsFor(Object converter) {
474496
return getRegistrationFor(converter, Converter.class, isReading, isWriting);
475497

476498
} else {
477-
throw new IllegalArgumentException("Unsupported converter type!");
499+
throw new IllegalArgumentException(String.format("Unsupported converter type %s!", converter));
478500
}
479501
}
480502

0 commit comments

Comments
 (0)