1 /*
2  * Copyright 2016-2020 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.repository.util;
17
18 import lombok.experimental.UtilityClass;
19 import reactor.core.publisher.Flux;
20 import reactor.core.publisher.Mono;
21
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.Optional;
26
27 import org.springframework.core.ReactiveAdapter;
28 import org.springframework.core.ReactiveAdapterRegistry;
29 import org.springframework.core.ReactiveTypeDescriptor;
30 import org.springframework.data.util.ProxyUtils;
31 import org.springframework.data.util.ReflectionUtils;
32 import org.springframework.util.Assert;
33 import org.springframework.util.ClassUtils;
34
35 /**
36  * Utility class to expose details about reactive wrapper types. This class exposes whether a reactive wrapper is
37  * supported in general and whether a particular type is suitable for no-value/single-value/multi-value usage.
38  * <p>
39  * Supported types are discovered by their availability on the class path. This class is typically used to determine
40  * multiplicity and whether a reactive wrapper type is acceptable for a specific operation.
41  *
42  * @author Mark Paluch
43  * @author Christoph Strobl
44  * @author Oliver Gierke
45  * @author Gerrit Meier
46  * @since 2.0
47  * @see org.reactivestreams.Publisher
48  * @see rx.Single
49  * @see rx.Observable
50  * @see rx.Completable
51  * @see io.reactivex.Single
52  * @see io.reactivex.Maybe
53  * @see io.reactivex.Observable
54  * @see io.reactivex.Completable
55  * @see io.reactivex.Flowable
56  * @see Mono
57  * @see Flux
58  */

59 @UtilityClass
60 public class ReactiveWrappers {
61
62     private static final boolean PROJECT_REACTOR_PRESENT = ClassUtils.isPresent("reactor.core.publisher.Flux",
63             ReactiveWrappers.class.getClassLoader());
64
65     private static final boolean RXJAVA1_PRESENT = ClassUtils.isPresent("rx.Observable",
66             ReactiveWrappers.class.getClassLoader())
67             && ClassUtils.isPresent("rx.RxReactiveStreams",
68             ReactiveWrappers.class.getClassLoader());
69
70     private static final boolean RXJAVA2_PRESENT = ClassUtils.isPresent("io.reactivex.Flowable",
71             ReactiveWrappers.class.getClassLoader());
72
73     private static final boolean KOTLIN_COROUTINES_PRESENT = ClassUtils.isPresent("kotlinx.coroutines.reactor.MonoKt",
74             ReactiveWrappers.class.getClassLoader());
75
76     /**
77      * Enumeration of supported reactive libraries.
78      *
79      * @author Mark Paluch
80      */

81     public enum ReactiveLibrary {
82         PROJECT_REACTOR, RXJAVA1, RXJAVA2, KOTLIN_COROUTINES;
83     }
84
85     /**
86      * Returns {@literal trueif reactive support is available. More specifically, whether any of the libraries defined
87      * in {@link ReactiveLibrary} are on the class path.
88      *
89      * @return {@literal trueif reactive support is available.
90      */

91     public static boolean isAvailable() {
92         return Arrays.stream(ReactiveLibrary.values()).anyMatch(ReactiveWrappers::isAvailable);
93     }
94
95     /**
96      * Returns {@literal trueif the {@link ReactiveLibrary} is available.
97      *
98      * @param reactiveLibrary must not be {@literal null}.
99      * @return {@literal trueif the {@link ReactiveLibrary} is available.
100      */

101     public static boolean isAvailable(ReactiveLibrary reactiveLibrary) {
102
103         Assert.notNull(reactiveLibrary, "Reactive library must not be null!");
104
105         switch (reactiveLibrary) {
106             case PROJECT_REACTOR:
107                 return PROJECT_REACTOR_PRESENT;
108             case RXJAVA1:
109                 return RXJAVA1_PRESENT;
110             case RXJAVA2:
111                 return RXJAVA2_PRESENT;
112             case KOTLIN_COROUTINES:
113                 return PROJECT_REACTOR_PRESENT && KOTLIN_COROUTINES_PRESENT;
114             default:
115                 throw new IllegalArgumentException(String.format("Reactive library %s not supported", reactiveLibrary));
116         }
117     }
118
119     /**
120      * Returns {@literal trueif the {@code type} is a supported reactive wrapper type.
121      *
122      * @param type must not be {@literal null}.
123      * @return {@literal trueif the {@code type} is a supported reactive wrapper type.
124      */

125     public static boolean supports(Class<?> type) {
126         return isAvailable() && isWrapper(ProxyUtils.getUserClass(type));
127     }
128
129     /**
130      * Returns whether the given type uses any reactive wrapper type in its method signatures.
131      *
132      * @param type must not be {@literal null}.
133      * @return
134      */

135     public static boolean usesReactiveType(Class<?> type) {
136
137         Assert.notNull(type, "Type must not be null!");
138
139         return Arrays.stream(type.getMethods())//
140                 .flatMap(ReflectionUtils::returnTypeAndParameters)//
141                 .anyMatch(ReactiveWrappers::supports);
142     }
143
144     /**
145      * Returns {@literal trueif {@code type} is a reactive wrapper type that contains no value.
146      *
147      * @param type must not be {@literal null}.
148      * @return {@literal trueif {@code type} is a reactive wrapper type that contains no value.
149      */

150     public static boolean isNoValueType(Class<?> type) {
151
152         Assert.notNull(type, "Candidate type must not be null!");
153
154         return findDescriptor(type).map(ReactiveTypeDescriptor::isNoValue).orElse(false);
155     }
156
157     /**
158      * Returns {@literal trueif {@code type} is a reactive wrapper type for a single value.
159      *
160      * @param type must not be {@literal null}.
161      * @return {@literal trueif {@code type} is a reactive wrapper type for a single value.
162      */

163     public static boolean isSingleValueType(Class<?> type) {
164
165         Assert.notNull(type, "Candidate type must not be null!");
166
167         return findDescriptor(type).map(it -> !it.isMultiValue() && !it.isNoValue()).orElse(false);
168     }
169
170     /**
171      * Returns {@literal trueif {@code type} is a reactive wrapper type supporting multiple values ({@code 0..N}
172      * elements).
173      *
174      * @param type must not be {@literal null}.
175      * @return {@literal trueif {@code type} is a reactive wrapper type supporting multiple values ({@code 0..N}
176      *         elements).
177      */

178     public static boolean isMultiValueType(Class<?> type) {
179
180         Assert.notNull(type, "Candidate type must not be null!");
181
182         // Prevent single-types with a multi-hierarchy supertype to be reported as multi type
183         // See Mono implements Publisher
184         return isSingleValueType(type) ? false
185                 : findDescriptor(type).map(ReactiveTypeDescriptor::isMultiValue).orElse(false);
186     }
187
188     /**
189      * Returns a collection of no-value wrapper types.
190      *
191      * @return a collection of no-value wrapper types.
192      * @deprecated not supported anymore.
193      */

194     @Deprecated
195     public static Collection<Class<?>> getNoValueTypes() {
196         return Collections.emptyList();
197     }
198
199     /**
200      * Returns a collection of single-value wrapper types.
201      *
202      * @return a collection of single-value wrapper types.
203      * @deprecated not supported anymore.
204      */

205     @Deprecated
206     public static Collection<Class<?>> getSingleValueTypes() {
207         return Collections.emptyList();
208     }
209
210     /**
211      * Returns a collection of multi-value wrapper types.
212      *
213      * @return a collection of multi-value wrapper types.
214      * @deprecated not supported anymore.
215      */

216     @Deprecated
217     public static Collection<Class<?>> getMultiValueTypes() {
218         return Collections.emptyList();
219     }
220
221     /**
222      * Returns whether the given type is a reactive wrapper type.
223      *
224      * @param type must not be {@literal null}.
225      * @return
226      */

227     private static boolean isWrapper(Class<?> type) {
228
229         Assert.notNull(type, "Candidate type must not be null!");
230
231         return isNoValueType(type) || isSingleValueType(type) || isMultiValueType(type);
232     }
233
234     /**
235      * Looks up a {@link ReactiveTypeDescriptor} for the given wrapper type.
236      *
237      * @param type must not be {@literal null}.
238      * @return
239      */

240     private static Optional<ReactiveTypeDescriptor> findDescriptor(Class<?> type) {
241
242         Assert.notNull(type, "Wrapper type must not be null!");
243
244         ReactiveAdapterRegistry adapterRegistry = ReactiveWrapperConverters.RegistryHolder.REACTIVE_ADAPTER_REGISTRY;
245
246         if (adapterRegistry == null) {
247             return Optional.empty();
248         }
249
250         ReactiveAdapter adapter = adapterRegistry.getAdapter(type);
251         if (adapter != null && adapter.getDescriptor().isDeferred()) {
252             return Optional.of(adapter.getDescriptor());
253         }
254
255         return Optional.empty();
256     }
257 }
258