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 true} if 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 true} if 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 true} if the {@link ReactiveLibrary} is available.
97 *
98 * @param reactiveLibrary must not be {@literal null}.
99 * @return {@literal true} if 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 true} if the {@code type} is a supported reactive wrapper type.
121 *
122 * @param type must not be {@literal null}.
123 * @return {@literal true} if 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 true} if {@code type} is a reactive wrapper type that contains no value.
146 *
147 * @param type must not be {@literal null}.
148 * @return {@literal true} if {@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 true} if {@code type} is a reactive wrapper type for a single value.
159 *
160 * @param type must not be {@literal null}.
161 * @return {@literal true} if {@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 true} if {@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 true} if {@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