1
16 package org.springframework.data.repository.support;
17
18 import java.util.Collections;
19 import java.util.Optional;
20 import java.util.Set;
21
22 import javax.annotation.Nonnull;
23
24 import org.springframework.context.ApplicationContext;
25 import org.springframework.context.ApplicationContextAware;
26 import org.springframework.core.convert.ConversionService;
27 import org.springframework.core.convert.TypeDescriptor;
28 import org.springframework.core.convert.converter.ConditionalGenericConverter;
29 import org.springframework.core.convert.converter.ConverterRegistry;
30 import org.springframework.data.repository.CrudRepository;
31 import org.springframework.data.repository.core.EntityInformation;
32 import org.springframework.data.repository.core.RepositoryInformation;
33 import org.springframework.data.util.Lazy;
34 import org.springframework.lang.Nullable;
35 import org.springframework.util.Assert;
36 import org.springframework.util.StringUtils;
37
38
47 public class DomainClassConverter<T extends ConversionService & ConverterRegistry>
48 implements ConditionalGenericConverter, ApplicationContextAware {
49
50 private final T conversionService;
51 private Lazy<Repositories> repositories = Lazy.of(Repositories.NONE);
52 private Optional<ToEntityConverter> toEntityConverter = Optional.empty();
53 private Optional<ToIdConverter> toIdConverter = Optional.empty();
54
55
60 public DomainClassConverter(T conversionService) {
61
62 Assert.notNull(conversionService, "ConversionService must not be null!");
63
64 this.conversionService = conversionService;
65 this.conversionService.addConverter(this);
66 }
67
68
72 @Nonnull
73 @Override
74 public Set<ConvertiblePair> getConvertibleTypes() {
75 return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
76 }
77
78
82 @Nullable
83 @Override
84 public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
85 return getConverter(targetType).map(it -> it.convert(source, sourceType, targetType)).orElse(null);
86 }
87
88
92 @Override
93 public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
94 return getConverter(targetType).map(it -> it.matches(sourceType, targetType)).orElse(false);
95 }
96
97
101 private Optional<? extends ConditionalGenericConverter> getConverter(TypeDescriptor targetType) {
102 return repositories.get().hasRepositoryFor(targetType.getType()) ? toEntityConverter : toIdConverter;
103 }
104
105
109 public void setApplicationContext(ApplicationContext context) {
110
111 this.repositories = Lazy.of(() -> {
112
113 Repositories repositories = new Repositories(context);
114
115 this.toEntityConverter = Optional.of(new ToEntityConverter(repositories, conversionService));
116 this.toIdConverter = Optional.of(new ToIdConverter(repositories, conversionService));
117
118 return repositories;
119 });
120 }
121
122
128 private static class ToEntityConverter implements ConditionalGenericConverter {
129
130 private final RepositoryInvokerFactory repositoryInvokerFactory;
131 private final Repositories repositories;
132 private final ConversionService conversionService;
133
134
140 public ToEntityConverter(Repositories repositories, ConversionService conversionService) {
141
142 this.repositoryInvokerFactory = new DefaultRepositoryInvokerFactory(repositories, conversionService);
143 this.repositories = repositories;
144 this.conversionService = conversionService;
145 }
146
147
151 @Nonnull
152 @Override
153 public Set<ConvertiblePair> getConvertibleTypes() {
154 return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
155 }
156
157
161 @Nullable
162 @Override
163 public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
164
165 if (source == null || !StringUtils.hasText(source.toString())) {
166 return null;
167 }
168
169 if (sourceType.equals(targetType)) {
170 return source;
171 }
172
173 Class<?> domainType = targetType.getType();
174 RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(domainType);
175 RepositoryInformation information = repositories.getRequiredRepositoryInformation(domainType);
176
177 Object id = conversionService.convert(source, information.getIdType());
178
179 return id == null ? null : invoker.invokeFindById(id).orElse(null);
180 }
181
182
186 @Override
187 public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
188
189 if (sourceType.isAssignableTo(targetType)) {
190 return false;
191 }
192
193 Class<?> domainType = targetType.getType();
194
195 if (!repositories.hasRepositoryFor(domainType)) {
196 return false;
197 }
198
199 Optional<RepositoryInformation> repositoryInformation = repositories.getRepositoryInformationFor(domainType);
200
201 return repositoryInformation.map(it -> {
202
203 Class<?> rawIdType = it.getIdType();
204
205 return sourceType.equals(TypeDescriptor.valueOf(rawIdType))
206 || conversionService.canConvert(sourceType.getType(), rawIdType);
207 }).orElseThrow(
208 () -> new IllegalStateException(String.format("Couldn't find RepositoryInformation for %s!", domainType)));
209 }
210 }
211
212
218 static class ToIdConverter implements ConditionalGenericConverter {
219
220 private final Repositories repositories;
221 private final ConversionService conversionService;
222
223 public ToIdConverter(Repositories repositories, ConversionService conversionService) {
224
225 this.repositories = repositories;
226 this.conversionService = conversionService;
227 }
228
229
233 @Nonnull
234 @Override
235 public Set<ConvertiblePair> getConvertibleTypes() {
236 return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
237 }
238
239
243 @Nullable
244 @Override
245 public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
246
247 if (source == null || !StringUtils.hasText(source.toString())) {
248 return null;
249 }
250
251 if (sourceType.equals(targetType)) {
252 return source;
253 }
254
255 Class<?> domainType = sourceType.getType();
256
257 EntityInformation<Object, ?> entityInformation = repositories.getEntityInformationFor(domainType);
258
259 return conversionService.convert(entityInformation.getId(source), targetType.getType());
260 }
261
262
266 @Override
267 public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
268
269 if (sourceType.isAssignableTo(targetType)) {
270 return false;
271 }
272
273 Class<?> domainType = sourceType.getType();
274
275 if (!repositories.hasRepositoryFor(domainType)) {
276 return false;
277 }
278
279 Optional<RepositoryInformation> information = repositories.getRepositoryInformationFor(domainType);
280
281 return information.map(it -> {
282
283 Class<?> rawIdType = it.getIdType();
284
285 return targetType.equals(TypeDescriptor.valueOf(rawIdType))
286 || conversionService.canConvert(rawIdType, targetType.getType());
287
288 }).orElseThrow(
289 () -> new IllegalStateException(String.format("Couldn't find RepositoryInformation for %s!", domainType)));
290 }
291 }
292 }
293