/*
 * Copyright 2016-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.repository.util;

import lombok.experimental.UtilityClass;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Completable;
import rx.Observable;
import rx.Single;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.stream.Collectors;

import org.reactivestreams.Publisher;
import org.springframework.core.ReactiveTypeDescriptor;
import org.springframework.data.util.ProxyUtils;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

Utility class to expose details about reactive wrapper types. This class exposes whether a reactive wrapper is supported in general and whether a particular type is suitable for no-value/single-value/multi-value usage.

Supported types are discovered by their availability on the class path. This class is typically used to determine multiplicity and whether a reactive wrapper type is acceptable for a specific operation.

Author:Mark Paluch, Christoph Strobl, Oliver Gierke
See Also:
Since:2.0
/** * Utility class to expose details about reactive wrapper types. This class exposes whether a reactive wrapper is * supported in general and whether a particular type is suitable for no-value/single-value/multi-value usage. * <p> * Supported types are discovered by their availability on the class path. This class is typically used to determine * multiplicity and whether a reactive wrapper type is acceptable for a specific operation. * * @author Mark Paluch * @author Christoph Strobl * @author Oliver Gierke * @since 2.0 * @see org.reactivestreams.Publisher * @see rx.Single * @see rx.Observable * @see rx.Completable * @see io.reactivex.Single * @see io.reactivex.Maybe * @see io.reactivex.Observable * @see io.reactivex.Completable * @see io.reactivex.Flowable * @see Mono * @see Flux */
@UtilityClass public class ReactiveWrappers { private static final boolean PROJECT_REACTOR_PRESENT = ClassUtils.isPresent("reactor.core.publisher.Mono", ReactiveWrappers.class.getClassLoader()); private static final boolean RXJAVA1_PRESENT = ClassUtils.isPresent("rx.Completable", ReactiveWrappers.class.getClassLoader()); private static final boolean RXJAVA2_PRESENT = ClassUtils.isPresent("io.reactivex.Flowable", ReactiveWrappers.class.getClassLoader()); private static final Collection<ReactiveTypeDescriptor> REACTIVE_WRAPPERS;
Enumeration of supported reactive libraries.
Author:Mark Paluch
/** * Enumeration of supported reactive libraries. * * @author Mark Paluch */
public enum ReactiveLibrary { PROJECT_REACTOR, RXJAVA1, RXJAVA2; } static { Collection<ReactiveTypeDescriptor> reactiveWrappers = new ArrayList<>(5); if (RXJAVA1_PRESENT) { reactiveWrappers.add(ReactiveTypeDescriptor.singleRequiredValue(Single.class)); reactiveWrappers.add(ReactiveTypeDescriptor.noValue(Completable.class, Completable::complete)); reactiveWrappers.add(ReactiveTypeDescriptor.multiValue(Observable.class, Observable::empty)); } if (RXJAVA2_PRESENT) { reactiveWrappers.add(ReactiveTypeDescriptor.singleRequiredValue(io.reactivex.Single.class)); reactiveWrappers .add(ReactiveTypeDescriptor.singleOptionalValue(io.reactivex.Maybe.class, io.reactivex.Maybe::empty)); reactiveWrappers .add(ReactiveTypeDescriptor.noValue(io.reactivex.Completable.class, io.reactivex.Completable::complete)); reactiveWrappers .add(ReactiveTypeDescriptor.multiValue(io.reactivex.Flowable.class, io.reactivex.Flowable::empty)); reactiveWrappers .add(ReactiveTypeDescriptor.multiValue(io.reactivex.Observable.class, io.reactivex.Observable::empty)); } if (PROJECT_REACTOR_PRESENT) { reactiveWrappers.add(ReactiveTypeDescriptor.singleOptionalValue(Mono.class, Mono::empty)); reactiveWrappers.add(ReactiveTypeDescriptor.multiValue(Flux.class, Flux::empty)); reactiveWrappers.add(ReactiveTypeDescriptor.multiValue(Publisher.class, Flux::empty)); } REACTIVE_WRAPPERS = Collections.unmodifiableCollection(reactiveWrappers); }
Returns true if reactive support is available. More specifically, whether any of the libraries defined in ReactiveLibrary are on the class path.
Returns:true if reactive support is available.
/** * Returns {@literal true} if reactive support is available. More specifically, whether any of the libraries defined * in {@link ReactiveLibrary} are on the class path. * * @return {@literal true} if reactive support is available. */
public static boolean isAvailable() { return Arrays.stream(ReactiveLibrary.values()).anyMatch(ReactiveWrappers::isAvailable); }
Returns true if the ReactiveLibrary is available.
Params:
  • reactiveLibrary – must not be null.
Returns:true if the ReactiveLibrary is available.
/** * Returns {@literal true} if the {@link ReactiveLibrary} is available. * * @param reactiveLibrary must not be {@literal null}. * @return {@literal true} if the {@link ReactiveLibrary} is available. */
public static boolean isAvailable(ReactiveLibrary reactiveLibrary) { Assert.notNull(reactiveLibrary, "Reactive library must not be null!"); switch (reactiveLibrary) { case PROJECT_REACTOR: return PROJECT_REACTOR_PRESENT; case RXJAVA1: return RXJAVA1_PRESENT; case RXJAVA2: return RXJAVA2_PRESENT; default: throw new IllegalArgumentException(String.format("Reactive library %s not supported", reactiveLibrary)); } }
Returns true if the type is a supported reactive wrapper type.
Params:
  • type – must not be null.
Returns:true if the type is a supported reactive wrapper type.
/** * Returns {@literal true} if the {@code type} is a supported reactive wrapper type. * * @param type must not be {@literal null}. * @return {@literal true} if the {@code type} is a supported reactive wrapper type. */
public static boolean supports(Class<?> type) { return isWrapper(ProxyUtils.getUserClass(type)); }
Returns whether the given type uses any reactive wrapper type in its method signatures.
Params:
  • type – must not be null.
Returns:
/** * Returns whether the given type uses any reactive wrapper type in its method signatures. * * @param type must not be {@literal null}. * @return */
public static boolean usesReactiveType(Class<?> type) { Assert.notNull(type, "Type must not be null!"); return Arrays.stream(type.getMethods())// .flatMap(ReflectionUtils::returnTypeAndParameters)// .anyMatch(ReactiveWrappers::supports); }
Returns true if type is a reactive wrapper type that contains no value.
Params:
  • type – must not be null.
Returns:true if type is a reactive wrapper type that contains no value.
/** * Returns {@literal true} if {@code type} is a reactive wrapper type that contains no value. * * @param type must not be {@literal null}. * @return {@literal true} if {@code type} is a reactive wrapper type that contains no value. */
public static boolean isNoValueType(Class<?> type) { Assert.notNull(type, "Candidate type must not be null!"); return findDescriptor(type).map(ReactiveTypeDescriptor::isNoValue).orElse(false); }
Returns true if type is a reactive wrapper type for a single value.
Params:
  • type – must not be null.
Returns:true if type is a reactive wrapper type for a single value.
/** * Returns {@literal true} if {@code type} is a reactive wrapper type for a single value. * * @param type must not be {@literal null}. * @return {@literal true} if {@code type} is a reactive wrapper type for a single value. */
public static boolean isSingleValueType(Class<?> type) { Assert.notNull(type, "Candidate type must not be null!"); return findDescriptor(type).map(it -> !it.isMultiValue() && !it.isNoValue()).orElse(false); }
Returns true if type is a reactive wrapper type supporting multiple values (0..N elements).
Params:
  • type – must not be null.
Returns:true if type is a reactive wrapper type supporting multiple values (0..N elements).
/** * Returns {@literal true} if {@code type} is a reactive wrapper type supporting multiple values ({@code 0..N} * elements). * * @param type must not be {@literal null}. * @return {@literal true} if {@code type} is a reactive wrapper type supporting multiple values ({@code 0..N} * elements). */
public static boolean isMultiValueType(Class<?> type) { Assert.notNull(type, "Candidate type must not be null!"); // Prevent single-types with a multi-hierarchy supertype to be reported as multi type // See Mono implements Publisher return isSingleValueType(type) ? false : findDescriptor(type).map(ReactiveTypeDescriptor::isMultiValue).orElse(false); }
Returns a collection of no-value wrapper types.
Returns:a collection of no-value wrapper types.
/** * Returns a collection of no-value wrapper types. * * @return a collection of no-value wrapper types. */
public static Collection<Class<?>> getNoValueTypes() { return REACTIVE_WRAPPERS.stream()// .filter(ReactiveTypeDescriptor::isNoValue)// .map(ReactiveTypeDescriptor::getReactiveType)// .collect(Collectors.toList()); }
Returns a collection of single-value wrapper types.
Returns:a collection of single-value wrapper types.
/** * Returns a collection of single-value wrapper types. * * @return a collection of single-value wrapper types. */
public static Collection<Class<?>> getSingleValueTypes() { return REACTIVE_WRAPPERS.stream()// .filter(entry -> !entry.isMultiValue())// .map(ReactiveTypeDescriptor::getReactiveType).collect(Collectors.toList()); }
Returns a collection of multi-value wrapper types.
Returns:a collection of multi-value wrapper types.
/** * Returns a collection of multi-value wrapper types. * * @return a collection of multi-value wrapper types. */
public static Collection<Class<?>> getMultiValueTypes() { return REACTIVE_WRAPPERS.stream()// .filter(ReactiveTypeDescriptor::isMultiValue)// .map(ReactiveTypeDescriptor::getReactiveType)// .collect(Collectors.toList()); }
Returns whether the given type is a reactive wrapper type.
Params:
  • type – must not be null.
Returns:
/** * Returns whether the given type is a reactive wrapper type. * * @param type must not be {@literal null}. * @return */
private static boolean isWrapper(Class<?> type) { Assert.notNull(type, "Candidate type must not be null!"); return isNoValueType(type) || isSingleValueType(type) || isMultiValueType(type); }
Looks up a ReactiveTypeDescriptor for the given wrapper type.
Params:
  • type – must not be null.
Returns:
/** * Looks up a {@link ReactiveTypeDescriptor} for the given wrapper type. * * @param type must not be {@literal null}. * @return */
private static Optional<ReactiveTypeDescriptor> findDescriptor(Class<?> type) { Assert.notNull(type, "Wrapper type must not be null!"); return REACTIVE_WRAPPERS.stream()// .filter(it -> ClassUtils.isAssignable(it.getReactiveType(), type))// .findFirst(); } }