1
16 package org.springframework.data.repository.util;
17
18 import lombok.AccessLevel;
19 import lombok.Getter;
20 import lombok.NonNull;
21 import lombok.RequiredArgsConstructor;
22 import lombok.Value;
23 import scala.Function0;
24 import scala.Option;
25 import scala.runtime.AbstractFunction0;
26
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.concurrent.CompletableFuture;
36 import java.util.concurrent.ConcurrentHashMap;
37 import java.util.concurrent.Future;
38 import java.util.stream.Stream;
39
40 import javax.annotation.Nonnull;
41
42 import org.springframework.core.convert.ConversionService;
43 import org.springframework.core.convert.TypeDescriptor;
44 import org.springframework.core.convert.converter.ConditionalGenericConverter;
45 import org.springframework.core.convert.converter.Converter;
46 import org.springframework.core.convert.converter.GenericConverter;
47 import org.springframework.core.convert.support.ConfigurableConversionService;
48 import org.springframework.core.convert.support.DefaultConversionService;
49 import org.springframework.data.domain.Page;
50 import org.springframework.data.domain.Slice;
51 import org.springframework.data.geo.GeoResults;
52 import org.springframework.data.util.StreamUtils;
53 import org.springframework.data.util.Streamable;
54 import org.springframework.data.util.TypeInformation;
55 import org.springframework.lang.Nullable;
56 import org.springframework.scheduling.annotation.AsyncResult;
57 import org.springframework.util.Assert;
58 import org.springframework.util.ClassUtils;
59 import org.springframework.util.ConcurrentReferenceHashMap;
60 import org.springframework.util.concurrent.ListenableFuture;
61
62 import com.google.common.base.Optional;
63
64
89 public abstract class QueryExecutionConverters {
90
91 private static final boolean GUAVA_PRESENT = ClassUtils.isPresent("com.google.common.base.Optional",
92 QueryExecutionConverters.class.getClassLoader());
93 private static final boolean JDK_8_PRESENT = ClassUtils.isPresent("java.util.Optional",
94 QueryExecutionConverters.class.getClassLoader());
95 private static final boolean SCALA_PRESENT = ClassUtils.isPresent("scala.Option",
96 QueryExecutionConverters.class.getClassLoader());
97 private static final boolean VAVR_PRESENT = ClassUtils.isPresent("io.vavr.control.Option",
98 QueryExecutionConverters.class.getClassLoader());
99
100 private static final Set<WrapperType> WRAPPER_TYPES = new HashSet<WrapperType>();
101 private static final Set<WrapperType> UNWRAPPER_TYPES = new HashSet<WrapperType>();
102 private static final Set<Converter<Object, Object>> UNWRAPPERS = new HashSet<Converter<Object, Object>>();
103 private static final Set<Class<?>> ALLOWED_PAGEABLE_TYPES = new HashSet<Class<?>>();
104 private static final Map<Class<?>, ExecutionAdapter> EXECUTION_ADAPTER = new HashMap<>();
105 private static final Map<Class<?>, Boolean> SUPPORTS_CACHE = new ConcurrentReferenceHashMap<>();
106
107 static {
108
109 WRAPPER_TYPES.add(WrapperType.singleValue(Future.class));
110 UNWRAPPER_TYPES.add(WrapperType.singleValue(Future.class));
111 WRAPPER_TYPES.add(WrapperType.singleValue(ListenableFuture.class));
112 UNWRAPPER_TYPES.add(WrapperType.singleValue(ListenableFuture.class));
113
114 ALLOWED_PAGEABLE_TYPES.add(Slice.class);
115 ALLOWED_PAGEABLE_TYPES.add(Page.class);
116 ALLOWED_PAGEABLE_TYPES.add(List.class);
117
118 if (GUAVA_PRESENT) {
119 WRAPPER_TYPES.add(NullableWrapperToGuavaOptionalConverter.getWrapperType());
120 UNWRAPPER_TYPES.add(NullableWrapperToGuavaOptionalConverter.getWrapperType());
121 UNWRAPPERS.add(GuavaOptionalUnwrapper.INSTANCE);
122 }
123
124 if (JDK_8_PRESENT) {
125 WRAPPER_TYPES.add(NullableWrapperToJdk8OptionalConverter.getWrapperType());
126 UNWRAPPER_TYPES.add(NullableWrapperToJdk8OptionalConverter.getWrapperType());
127 UNWRAPPERS.add(Jdk8OptionalUnwrapper.INSTANCE);
128 }
129
130 if (JDK_8_PRESENT) {
131 WRAPPER_TYPES.add(NullableWrapperToCompletableFutureConverter.getWrapperType());
132 UNWRAPPER_TYPES.add(NullableWrapperToCompletableFutureConverter.getWrapperType());
133 }
134
135 if (SCALA_PRESENT) {
136 WRAPPER_TYPES.add(NullableWrapperToScalaOptionConverter.getWrapperType());
137 UNWRAPPER_TYPES.add(NullableWrapperToScalaOptionConverter.getWrapperType());
138 UNWRAPPERS.add(ScalOptionUnwrapper.INSTANCE);
139 }
140
141 if (VAVR_PRESENT) {
142
143 WRAPPER_TYPES.add(NullableWrapperToVavrOptionConverter.getWrapperType());
144 WRAPPER_TYPES.add(VavrCollections.ToJavaConverter.INSTANCE.getWrapperType());
145
146 UNWRAPPERS.add(VavrOptionUnwrapper.INSTANCE);
147
148
149 WRAPPER_TYPES.add(WrapperType.singleValue(io.vavr.control.Try.class));
150 EXECUTION_ADAPTER.put(io.vavr.control.Try.class, it -> io.vavr.control.Try.of(it::get));
151
152 ALLOWED_PAGEABLE_TYPES.add(io.vavr.collection.Seq.class);
153 }
154 }
155
156 private QueryExecutionConverters() {}
157
158
164 public static boolean supports(Class<?> type) {
165
166 Assert.notNull(type, "Type must not be null!");
167
168 return SUPPORTS_CACHE.computeIfAbsent(type, key -> {
169
170 for (WrapperType candidate : WRAPPER_TYPES) {
171 if (candidate.getType().isAssignableFrom(key)) {
172 return true;
173 }
174 }
175
176 if (ReactiveWrappers.supports(type)) {
177 return true;
178 }
179
180 return false;
181 });
182 }
183
184
190 public static boolean supportsUnwrapping(Class<?> type) {
191
192 Assert.notNull(type, "Type must not be null!");
193
194 for (WrapperType candidate : UNWRAPPER_TYPES) {
195 if (candidate.getType().isAssignableFrom(type)) {
196 return true;
197 }
198 }
199
200 return false;
201 }
202
203 public static boolean isSingleValue(Class<?> type) {
204
205 for (WrapperType candidate : WRAPPER_TYPES) {
206 if (candidate.getType().isAssignableFrom(type)) {
207 return candidate.isSingleValue();
208 }
209 }
210
211 if (ReactiveWrappers.supports(type) && ReactiveWrappers.isSingleValueType(type)) {
212 return true;
213 }
214
215 return false;
216 }
217
218
224 public static Set<Class<?>> getAllowedPageableTypes() {
225 return Collections.unmodifiableSet(ALLOWED_PAGEABLE_TYPES);
226 }
227
228
233 public static void registerConvertersIn(ConfigurableConversionService conversionService) {
234
235 Assert.notNull(conversionService, "ConversionService must not be null!");
236
237 conversionService.removeConvertible(Collection.class, Object.class);
238
239 if (GUAVA_PRESENT) {
240 conversionService.addConverter(new NullableWrapperToGuavaOptionalConverter(conversionService));
241 }
242
243 if (JDK_8_PRESENT) {
244 conversionService.addConverter(new NullableWrapperToJdk8OptionalConverter(conversionService));
245 conversionService.addConverter(new NullableWrapperToCompletableFutureConverter(conversionService));
246 }
247
248 if (SCALA_PRESENT) {
249 conversionService.addConverter(new NullableWrapperToScalaOptionConverter(conversionService));
250 }
251
252 if (VAVR_PRESENT) {
253 conversionService.addConverter(new NullableWrapperToVavrOptionConverter(conversionService));
254 conversionService.addConverter(VavrCollections.FromJavaConverter.INSTANCE);
255 }
256
257 conversionService.addConverter(new NullableWrapperToFutureConverter(conversionService));
258 conversionService.addConverter(new IterableToStreamableConverter());
259 }
260
261
267 @Nullable
268 public static Object unwrap(@Nullable Object source) {
269
270 if (source == null || !supports(source.getClass())) {
271 return source;
272 }
273
274 for (Converter<Object, Object> converter : UNWRAPPERS) {
275
276 Object result = converter.convert(source);
277
278 if (result != source) {
279 return result;
280 }
281 }
282
283 return source;
284 }
285
286
292 public static TypeInformation<?> unwrapWrapperTypes(TypeInformation<?> type) {
293
294 Assert.notNull(type, "type must not be null");
295
296 Class<?> rawType = type.getType();
297
298 boolean needToUnwrap = type.isCollectionLike()
299 || Slice.class.isAssignableFrom(rawType)
300 || GeoResults.class.isAssignableFrom(rawType)
301 || rawType.isArray()
302 || supports(rawType)
303 || Stream.class.isAssignableFrom(rawType);
304
305 return needToUnwrap ? unwrapWrapperTypes(type.getRequiredComponentType()) : type;
306 }
307
308
314 @Nullable
315 public static ExecutionAdapter getExecutionAdapter(Class<?> returnType) {
316
317 Assert.notNull(returnType, "Return type must not be null!");
318
319 return EXECUTION_ADAPTER.get(returnType);
320 }
321
322 public interface ThrowingSupplier {
323 Object get() throws Throwable;
324 }
325
326 public interface ExecutionAdapter {
327 Object apply(ThrowingSupplier supplier) throws Throwable;
328 }
329
330
336 @RequiredArgsConstructor
337 private static abstract class AbstractWrapperTypeConverter implements GenericConverter {
338
339 private final @NonNull ConversionService conversionService;
340 private final @NonNull Object nullValue;
341 private final @NonNull Iterable<Class<?>> wrapperTypes;
342
343
349 protected AbstractWrapperTypeConverter(ConversionService conversionService, Object nullValue) {
350
351 Assert.notNull(conversionService, "ConversionService must not be null!");
352 Assert.notNull(nullValue, "Null value must not be null!");
353
354 this.conversionService = conversionService;
355 this.nullValue = nullValue;
356 this.wrapperTypes = Collections.singleton(nullValue.getClass());
357 }
358
359
363 @Nonnull
364 @Override
365 public Set<ConvertiblePair> getConvertibleTypes() {
366
367 return Streamable.of(wrapperTypes)
368 .map(it -> new ConvertiblePair(NullableWrapper.class, it))
369 .stream().collect(StreamUtils.toUnmodifiableSet());
370 }
371
372
376 @Nullable
377 @Override
378 public final Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
379
380 if (source == null) {
381 return null;
382 }
383
384 NullableWrapper wrapper = (NullableWrapper) source;
385 Object value = wrapper.getValue();
386
387
388 return value == null ? nullValue : wrap(value);
389 }
390
391
397 protected abstract Object wrap(Object source);
398 }
399
400
405 private static class NullableWrapperToGuavaOptionalConverter extends AbstractWrapperTypeConverter {
406
407
412 public NullableWrapperToGuavaOptionalConverter(ConversionService conversionService) {
413 super(conversionService, Optional.absent(), Collections.singleton(Optional.class));
414 }
415
416
420 @Override
421 protected Object wrap(Object source) {
422 return Optional.of(source);
423 }
424
425 public static WrapperType getWrapperType() {
426 return WrapperType.singleValue(Optional.class);
427 }
428 }
429
430
435 private static class NullableWrapperToJdk8OptionalConverter extends AbstractWrapperTypeConverter {
436
437
442 public NullableWrapperToJdk8OptionalConverter(ConversionService conversionService) {
443 super(conversionService, java.util.Optional.empty());
444 }
445
446
450 @Override
451 protected Object wrap(Object source) {
452 return java.util.Optional.of(source);
453 }
454
455 public static WrapperType getWrapperType() {
456 return WrapperType.singleValue(java.util.Optional.class);
457 }
458 }
459
460
465 private static class NullableWrapperToFutureConverter extends AbstractWrapperTypeConverter {
466
467
472 public NullableWrapperToFutureConverter(ConversionService conversionService) {
473 super(conversionService, new AsyncResult<>(null), Arrays.asList(Future.class, ListenableFuture.class));
474 }
475
476
480 @Override
481 protected Object wrap(Object source) {
482 return new AsyncResult<>(source);
483 }
484 }
485
486
491 private static class NullableWrapperToCompletableFutureConverter extends AbstractWrapperTypeConverter {
492
493
498 public NullableWrapperToCompletableFutureConverter(ConversionService conversionService) {
499 super(conversionService, CompletableFuture.completedFuture(null));
500 }
501
502
506 @Override
507 protected Object wrap(Object source) {
508 return source instanceof CompletableFuture ? source : CompletableFuture.completedFuture(source);
509 }
510
511 public static WrapperType getWrapperType() {
512 return WrapperType.singleValue(CompletableFuture.class);
513 }
514 }
515
516
522 private static class NullableWrapperToScalaOptionConverter extends AbstractWrapperTypeConverter {
523
524 public NullableWrapperToScalaOptionConverter(ConversionService conversionService) {
525 super(conversionService, Option.empty(), Collections.singleton(Option.class));
526 }
527
528
532 @Override
533 protected Object wrap(Object source) {
534 return Option.apply(source);
535 }
536
537 public static WrapperType getWrapperType() {
538 return WrapperType.singleValue(Option.class);
539 }
540 }
541
542
548 private static class NullableWrapperToVavrOptionConverter extends AbstractWrapperTypeConverter {
549
550
555 public NullableWrapperToVavrOptionConverter(ConversionService conversionService) {
556 super(conversionService, io.vavr.control.Option.none(), Collections.singleton(io.vavr.control.Option.class));
557 }
558
559 public static WrapperType getWrapperType() {
560 return WrapperType.singleValue(io.vavr.control.Option.class);
561 }
562
563
567 @Override
568 protected Object wrap(Object source) {
569 return io.vavr.control.Option.of(source);
570 }
571 }
572
573
579 private enum GuavaOptionalUnwrapper implements Converter<Object, Object> {
580
581 INSTANCE;
582
583
587 @Nullable
588 @Override
589 public Object convert(Object source) {
590 return source instanceof Optional ? ((Optional<?>) source).orNull() : source;
591 }
592 }
593
594
600 private enum Jdk8OptionalUnwrapper implements Converter<Object, Object> {
601
602 INSTANCE;
603
604
608 @Nullable
609 @Override
610 public Object convert(Object source) {
611 return source instanceof java.util.Optional ? ((java.util.Optional<?>) source).orElse(null) : source;
612 }
613 }
614
615
622 private enum ScalOptionUnwrapper implements Converter<Object, Object> {
623
624 INSTANCE;
625
626 private final Function0<Object> alternative = new AbstractFunction0<Object>() {
627
628
632 @Nullable
633 @Override
634 public Option<Object> apply() {
635 return null;
636 }
637 };
638
639
643 @Nullable
644 @Override
645 public Object convert(Object source) {
646 return source instanceof Option ? ((Option<?>) source).getOrElse(alternative) : source;
647 }
648 }
649
650
656 private enum VavrOptionUnwrapper implements Converter<Object, Object> {
657
658 INSTANCE;
659
660
664 @Nullable
665 @Override
666 @SuppressWarnings("unchecked")
667 public Object convert(Object source) {
668
669 if (source instanceof io.vavr.control.Option) {
670 return ((io.vavr.control.Option<Object>) source).getOrElse(() -> null);
671 }
672
673 if (source instanceof io.vavr.collection.Traversable) {
674 return VavrCollections.ToJavaConverter.INSTANCE.convert(source);
675 }
676
677 return source;
678 }
679 }
680
681 @RequiredArgsConstructor
682 private static class IterableToStreamableConverter implements ConditionalGenericConverter {
683
684 private static final TypeDescriptor STREAMABLE = TypeDescriptor.valueOf(Streamable.class);
685
686 private final Map<TypeDescriptor, Boolean> TARGET_TYPE_CACHE = new ConcurrentHashMap<>();
687 private final ConversionService conversionService = DefaultConversionService.getSharedInstance();
688
689
693 @org.springframework.lang.NonNull
694 @Override
695 public Set<ConvertiblePair> getConvertibleTypes() {
696 return Collections.singleton(new ConvertiblePair(Iterable.class, Object.class));
697 }
698
699
703 @Override
704 public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
705
706 if (sourceType.isAssignableTo(targetType)) {
707 return false;
708 }
709
710 if (!Iterable.class.isAssignableFrom(sourceType.getType())) {
711 return false;
712 }
713
714 if (Streamable.class.equals(targetType.getType())) {
715 return true;
716 }
717
718 return TARGET_TYPE_CACHE.computeIfAbsent(targetType, it -> {
719 return conversionService.canConvert(STREAMABLE, targetType);
720 });
721 }
722
723
727 @SuppressWarnings("unchecked")
728 @Nullable
729 @Override
730 public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
731
732 Streamable<Object> streamable = source == null
733 ? Streamable.empty()
734 : Streamable.of(Iterable.class.cast(source));
735
736 return Streamable.class.equals(targetType.getType())
737 ? streamable
738 : conversionService.convert(streamable, STREAMABLE, targetType);
739 }
740 }
741
742 @Value
743 @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
744 public static class WrapperType {
745
746 enum Cardinality {
747 NONE, SINGLE, MULTI;
748 }
749
750 Class<?> type;
751 @Getter(AccessLevel.NONE) Cardinality cardinality;
752
753 public static WrapperType singleValue(Class<?> type) {
754 return new WrapperType(type, Cardinality.SINGLE);
755 }
756
757 public static WrapperType multiValue(Class<?> type) {
758 return new WrapperType(type, Cardinality.MULTI);
759 }
760
761 public static WrapperType noValue(Class<?> type) {
762 return new WrapperType(type, Cardinality.NONE);
763 }
764
765 boolean isSingleValue() {
766 return cardinality.equals(Cardinality.SINGLE);
767 }
768 }
769 }
770