Skip to content

Commit 3f73bc7

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.
1 parent 0c479e1 commit 3f73bc7

File tree

3 files changed

+405
-0
lines changed

3 files changed

+405
-0
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<T, S> reading(Class<T> source, Class<S> target,
49+
Function<? super T, ? extends S> 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 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} ware 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+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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 lombok.AccessLevel;
19+
import lombok.EqualsAndHashCode;
20+
import lombok.NonNull;
21+
import lombok.RequiredArgsConstructor;
22+
import lombok.experimental.Wither;
23+
24+
import java.util.Collections;
25+
import java.util.Optional;
26+
import java.util.Set;
27+
import java.util.function.Function;
28+
import java.util.stream.Collectors;
29+
30+
import org.springframework.core.convert.TypeDescriptor;
31+
import org.springframework.core.convert.converter.Converter;
32+
import org.springframework.core.convert.converter.GenericConverter;
33+
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
34+
import org.springframework.data.convert.ConverterBuilder.ConverterAware;
35+
import org.springframework.data.convert.ConverterBuilder.ReadingConverterBuilder;
36+
import org.springframework.data.convert.ConverterBuilder.WritingConverterBuilder;
37+
import org.springframework.data.util.Optionals;
38+
39+
/**
40+
* Builder to easily set up (bi-directional) {@link Converter} instances for Spring Data type mapping using Lambdas. Use
41+
* factory methods on {@link ConverterBuilder} to create instances of this class.
42+
*
43+
* @author Oliver Gierke
44+
* @since 2.0
45+
* @see ConverterBuilder#writing(Class, Class, Function)
46+
* @see ConverterBuilder#reading(Class, Class, Function)
47+
* @soundtrack John Mayer - Still Feel Like Your Man (The Search for Everything)
48+
*/
49+
@Wither(AccessLevel.PACKAGE)
50+
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
51+
class DefaultConverterBuilder<S, T>
52+
implements ConverterAware, ReadingConverterBuilder<T, S>, WritingConverterBuilder<S, T> {
53+
54+
private final @NonNull ConvertiblePair convertiblePair;
55+
private final @NonNull Optional<Function<? super S, ? extends T>> writing;
56+
private final @NonNull Optional<Function<? super T, ? extends S>> reading;
57+
58+
/*
59+
* (non-Javadoc)
60+
* @see org.springframework.data.convert.WritingConverterBuilder#andReading(java.util.function.Function)
61+
*/
62+
@Override
63+
public ConverterAware andReading(Function<? super T, ? extends S> function) {
64+
return withReading(Optional.of(function));
65+
}
66+
67+
/*
68+
* (non-Javadoc)
69+
* @see org.springframework.data.convert.ReadingConverterBuilder#andWriting(java.util.function.Function)
70+
*/
71+
@Override
72+
public ConverterAware andWriting(Function<? super S, ? extends T> function) {
73+
return withWriting(Optional.of(function));
74+
}
75+
76+
/*
77+
* (non-Javadoc)
78+
* @see org.springframework.data.convert.ReadingConverterBuilder#getRequiredReadingConverter()
79+
*/
80+
@Override
81+
public GenericConverter getReadingConverter() {
82+
return getOptionalReadingConverter()
83+
.orElseThrow(() -> new IllegalStateException("No reading converter specified!"));
84+
}
85+
86+
/*
87+
* (non-Javadoc)
88+
* @see org.springframework.data.convert.WritingConverterBuilder#getRequiredWritingConverter()
89+
*/
90+
@Override
91+
public GenericConverter getWritingConverter() {
92+
return getOptionalWritingConverter()
93+
.orElseThrow(() -> new IllegalStateException("No writing converter specified!"));
94+
}
95+
96+
/*
97+
* (non-Javadoc)
98+
* @see org.springframework.data.convert.ConverterBuilder#getConverters()
99+
*/
100+
@Override
101+
public Set<GenericConverter> getConverters() {
102+
return Optionals.toStream(getOptionalReadingConverter(), getOptionalWritingConverter()).collect(Collectors.toSet());
103+
}
104+
105+
private Optional<GenericConverter> getOptionalReadingConverter() {
106+
return reading.map(it -> new ConfigurableGenericConverter.Reading<>(convertiblePair, it));
107+
}
108+
109+
private Optional<GenericConverter> getOptionalWritingConverter() {
110+
return writing.map(it -> new ConfigurableGenericConverter.Writing<>(invertedPair(), it));
111+
}
112+
113+
private ConvertiblePair invertedPair() {
114+
return new ConvertiblePair(convertiblePair.getTargetType(), convertiblePair.getSourceType());
115+
}
116+
117+
@RequiredArgsConstructor
118+
@EqualsAndHashCode
119+
private static class ConfigurableGenericConverter<S, T> implements GenericConverter {
120+
121+
private final ConvertiblePair convertiblePair;
122+
private final Function<? super S, ? extends T> function;
123+
124+
/*
125+
* (non-Javadoc)
126+
* @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
127+
*/
128+
@Override
129+
@SuppressWarnings("unchecked")
130+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
131+
return function.apply((S) source);
132+
}
133+
134+
/*
135+
* (non-Javadoc)
136+
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
137+
*/
138+
@Override
139+
public Set<ConvertiblePair> getConvertibleTypes() {
140+
return Collections.singleton(convertiblePair);
141+
}
142+
143+
@WritingConverter
144+
private static class Writing<S, T> extends ConfigurableGenericConverter<S, T> {
145+
146+
public Writing(ConvertiblePair convertiblePair, Function<? super S, ? extends T> function) {
147+
super(convertiblePair, function);
148+
}
149+
}
150+
151+
@ReadingConverter
152+
private static class Reading<S, T> extends ConfigurableGenericConverter<S, T> {
153+
154+
public Reading(ConvertiblePair convertiblePair, Function<? super S, ? extends T> function) {
155+
super(convertiblePair, function);
156+
}
157+
}
158+
}
159+
}

0 commit comments

Comments
 (0)