Skip to content

Commit 887f17b

Browse files
committed
Convert AggregateReference by converters instead of custom code paths.
Closes #992
1 parent 5db67b4 commit 887f17b

File tree

5 files changed

+232
-18
lines changed

5 files changed

+232
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright 2021 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+
package org.springframework.data.jdbc.core.convert;
17+
18+
import java.util.Collections;
19+
import java.util.Set;
20+
21+
import org.springframework.core.ResolvableType;
22+
import org.springframework.core.convert.ConversionService;
23+
import org.springframework.core.convert.TypeDescriptor;
24+
import org.springframework.core.convert.converter.GenericConverter;
25+
import org.springframework.data.convert.ReadingConverter;
26+
import org.springframework.data.convert.WritingConverter;
27+
import org.springframework.data.jdbc.core.mapping.AggregateReference;
28+
import org.springframework.lang.Nullable;
29+
30+
/**
31+
* Converters for aggregate references. They need a {@link ConversionService} in order to delegate the conversion of the
32+
* content of the {@link AggregateReference}.
33+
*
34+
* @author Jens Schauder
35+
* @since 2.6
36+
*/
37+
final class AggregateReferenceConverters {
38+
/**
39+
* Prevent instantiation.
40+
*/
41+
private AggregateReferenceConverters() {}
42+
43+
/**
44+
* Converts from an AggregateReference to its id, leaving the conversion of the id to the ultimate target type to the
45+
* delegate {@link ConversionService}.
46+
*/
47+
@WritingConverter
48+
public static class AggregateReferenceToSimpleTypeConverter implements GenericConverter {
49+
50+
private final ConversionService delegate;
51+
52+
public AggregateReferenceToSimpleTypeConverter(ConversionService delegate) {
53+
this.delegate = delegate;
54+
}
55+
56+
@Override
57+
public Set<ConvertiblePair> getConvertibleTypes() {
58+
return Collections.singleton(new ConvertiblePair(AggregateReference.class, Object.class));
59+
}
60+
61+
@Override
62+
public Object convert(@Nullable Object source, TypeDescriptor sourceDescriptor, TypeDescriptor targetDescriptor) {
63+
64+
if (source == null) {
65+
return null;
66+
}
67+
68+
// if the target type is an AggregateReference we just going to assume it is of the correct type,
69+
// because it was already converted.
70+
Class<?> objectType = targetDescriptor.getObjectType();
71+
if (objectType.isAssignableFrom(AggregateReference.class)) {
72+
return source;
73+
}
74+
75+
Object id = ((AggregateReference<?, ?>) source).getId();
76+
77+
if (id == null) {
78+
throw new IllegalStateException(
79+
String.format("Aggregate references id must not be null when converting to %s from %s to %s", source,
80+
sourceDescriptor, targetDescriptor));
81+
}
82+
83+
return delegate.convert(id, TypeDescriptor.valueOf(id.getClass()), targetDescriptor);
84+
}
85+
}
86+
87+
/**
88+
* Convert any simple type to an {@link AggregateReference}. If the {@literal targetDescriptor} contains information
89+
* about the generic type id will properly get converted to the desired type by the delegate
90+
* {@link ConversionService}.
91+
*/
92+
@ReadingConverter
93+
public static class SimpleTypeToAggregateReferenceConverter implements GenericConverter {
94+
95+
private final ConversionService delegate;
96+
97+
public SimpleTypeToAggregateReferenceConverter(ConversionService delegate) {
98+
this.delegate = delegate;
99+
}
100+
101+
@Override
102+
public Set<ConvertiblePair> getConvertibleTypes() {
103+
return Collections.singleton(new ConvertiblePair(Object.class, AggregateReference.class));
104+
}
105+
106+
@Override
107+
public Object convert(@Nullable Object source, TypeDescriptor sourceDescriptor, TypeDescriptor targetDescriptor) {
108+
109+
if (source == null) {
110+
return null;
111+
}
112+
113+
// TODO check
114+
ResolvableType componentType = targetDescriptor.getResolvableType().getGenerics()[1];
115+
TypeDescriptor targetType = TypeDescriptor.valueOf(componentType.resolve());
116+
Object convertedId = delegate.convert(source, TypeDescriptor.valueOf(source.getClass()), targetType);
117+
118+
return AggregateReference.to(convertedId);
119+
}
120+
}
121+
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java

+9-16
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import org.slf4j.LoggerFactory;
2727
import org.springframework.context.ApplicationContext;
2828
import org.springframework.context.ApplicationContextAware;
29+
import org.springframework.core.ResolvableType;
2930
import org.springframework.core.convert.ConverterNotFoundException;
31+
import org.springframework.core.convert.TypeDescriptor;
3032
import org.springframework.core.convert.converter.Converter;
3133
import org.springframework.data.convert.CustomConversions;
3234
import org.springframework.data.jdbc.core.mapping.AggregateReference;
@@ -220,16 +222,12 @@ public Object readValue(@Nullable Object value, TypeInformation<?> type) {
220222
}
221223

222224
if (getConversions().hasCustomReadTarget(value.getClass(), type.getType())) {
223-
return getConversionService().convert(value, type.getType());
224-
}
225-
226-
if (AggregateReference.class.isAssignableFrom(type.getType())) {
227225

228-
if (type.getType().isAssignableFrom(value.getClass())) {
229-
return value;
230-
}
226+
TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(value.getClass());
227+
TypeDescriptor targetDescriptor = typeInformationToTypeDescriptor(type);
231228

232-
return readAggregateReference(value, type);
229+
return getConversionService().convert(value, sourceDescriptor,
230+
targetDescriptor);
233231
}
234232

235233
if (value instanceof Array) {
@@ -243,12 +241,11 @@ public Object readValue(@Nullable Object value, TypeInformation<?> type) {
243241
return super.readValue(value, type);
244242
}
245243

246-
@SuppressWarnings("ConstantConditions")
247-
private Object readAggregateReference(@Nullable Object value, TypeInformation<?> type) {
244+
private static TypeDescriptor typeInformationToTypeDescriptor(TypeInformation<?> type) {
248245

249-
TypeInformation<?> idType = type.getSuperTypeInformation(AggregateReference.class).getTypeArguments().get(1);
246+
Class<?>[] generics = type.getTypeArguments().stream().map(TypeInformation::getType).toArray(Class[]::new);
250247

251-
return AggregateReference.to(readValue(value, idType));
248+
return new TypeDescriptor(ResolvableType.forClassWithGenerics(type.getType(), generics), null, null);
252249
}
253250

254251
/*
@@ -263,10 +260,6 @@ public Object writeValue(@Nullable Object value, TypeInformation<?> type) {
263260
return null;
264261
}
265262

266-
if (AggregateReference.class.isAssignableFrom(value.getClass())) {
267-
return writeValue(((AggregateReference) value).getId(), type);
268-
}
269-
270263
return super.writeValue(value, type);
271264
}
272265

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcCustomConversions.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@
1515
*/
1616
package org.springframework.data.jdbc.core.convert;
1717

18+
import java.util.ArrayList;
1819
import java.util.Collection;
1920
import java.util.Collections;
2021
import java.util.List;
2122

23+
import org.springframework.core.convert.ConversionService;
24+
import org.springframework.core.convert.converter.Converter;
2225
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
26+
import org.springframework.core.convert.support.DefaultConversionService;
2327
import org.springframework.data.convert.CustomConversions;
2428
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
2529

@@ -36,8 +40,19 @@
3640
*/
3741
public class JdbcCustomConversions extends CustomConversions {
3842

39-
private static final Collection<Object> STORE_CONVERTERS = Collections
40-
.unmodifiableCollection(Jsr310TimestampBasedConverters.getConvertersToRegister());
43+
private static final Collection<Object> STORE_CONVERTERS;
44+
45+
static {
46+
47+
List<Object> converters = new ArrayList<>(Jsr310TimestampBasedConverters.getConvertersToRegister());
48+
49+
ConversionService conversionService = DefaultConversionService.getSharedInstance();
50+
converters.add(new AggregateReferenceConverters.AggregateReferenceToSimpleTypeConverter(conversionService));
51+
converters.add(new AggregateReferenceConverters.SimpleTypeToAggregateReferenceConverter(conversionService));
52+
53+
STORE_CONVERTERS = Collections.unmodifiableCollection(converters);
54+
55+
}
4156
private static final StoreConversions STORE_CONVERSIONS = StoreConversions.of(JdbcSimpleTypes.HOLDER,
4257
STORE_CONVERTERS);
4358

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2021 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+
package org.springframework.data.jdbc.core.convert;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.springframework.core.ResolvableType;
20+
import org.springframework.core.convert.TypeDescriptor;
21+
import org.springframework.core.convert.support.DefaultConversionService;
22+
import org.springframework.data.jdbc.core.mapping.AggregateReference;
23+
24+
import static org.assertj.core.api.Assertions.*;
25+
26+
/**
27+
* Tests for converters from an to {@link org.springframework.data.jdbc.core.mapping.AggregateReference}.
28+
*
29+
* @author Jens Schauder
30+
*/
31+
class AggregateReferenceConvertersUnitTests {
32+
33+
AggregateReferenceConverters.SimpleTypeToAggregateReferenceConverter simpleToAggregate = new AggregateReferenceConverters.SimpleTypeToAggregateReferenceConverter(DefaultConversionService.getSharedInstance());
34+
AggregateReferenceConverters.AggregateReferenceToSimpleTypeConverter aggregateToSimple = new AggregateReferenceConverters.AggregateReferenceToSimpleTypeConverter(DefaultConversionService.getSharedInstance());
35+
36+
@Test // #992
37+
void convertsFromSimpleValue() {
38+
39+
ResolvableType aggregateReferenceWithIdTypeInteger = ResolvableType.forClassWithGenerics(AggregateReference.class, String.class, Integer.class);
40+
final Object converted = simpleToAggregate.convert(23, TypeDescriptor.forObject(23), new TypeDescriptor(aggregateReferenceWithIdTypeInteger, null, null));
41+
42+
assertThat(converted).isEqualTo(AggregateReference.to(23));
43+
}
44+
45+
@Test // #992
46+
void convertsFromSimpleValueThatNeedsSeparateConversion() {
47+
48+
ResolvableType aggregateReferenceWithIdTypeInteger = ResolvableType.forClassWithGenerics(AggregateReference.class, String.class, Long.class);
49+
final Object converted = simpleToAggregate.convert(23, TypeDescriptor.forObject(23), new TypeDescriptor(aggregateReferenceWithIdTypeInteger, null, null));
50+
51+
assertThat(converted).isEqualTo(AggregateReference.to(23L));
52+
}
53+
54+
@Test // #992
55+
void convertsFromSimpleValueWithMissingTypeInformation() {
56+
57+
final Object converted = simpleToAggregate.convert(23, TypeDescriptor.forObject(23), TypeDescriptor.valueOf(AggregateReference.class));
58+
59+
assertThat(converted).isEqualTo(AggregateReference.to(23));
60+
}
61+
62+
@Test // #992
63+
void convertsToSimpleValue() {
64+
65+
final AggregateReference<Object, Integer> source = AggregateReference.to(23);
66+
67+
final Object converted = aggregateToSimple.convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(Integer.class));
68+
69+
assertThat(converted).isEqualTo(23);
70+
}
71+
72+
@Test // #992
73+
void convertsToSimpleValueThatNeedsSeparateConversion() {
74+
75+
final AggregateReference<Object, Integer> source = AggregateReference.to(23);
76+
77+
final Object converted = aggregateToSimple.convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(Long.class));
78+
79+
assertThat(converted).isEqualTo(23L);
80+
}
81+
82+
83+
}

spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java

+2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import java.util.Optional;
2020
import java.util.function.Function;
2121

22+
import org.springframework.core.ResolvableType;
2223
import org.springframework.core.convert.ConversionService;
24+
import org.springframework.core.convert.TypeDescriptor;
2325
import org.springframework.core.convert.support.ConfigurableConversionService;
2426
import org.springframework.core.convert.support.DefaultConversionService;
2527
import org.springframework.data.convert.CustomConversions;

0 commit comments

Comments
 (0)