diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveAdapter.java b/spring-core/src/main/java/org/springframework/core/ReactiveAdapter.java index a829562f2d..7737ba5d12 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapter.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapter.java @@ -37,7 +37,7 @@ public interface ReactiveAdapter { /** * Return a descriptor with further information about the adaptee. */ - Descriptor getDescriptor(); + ReactiveTypeDescriptor getDescriptor(); /** * Adapt the given Object to a {@link Mono} @@ -67,48 +67,4 @@ public interface ReactiveAdapter { */ Object fromPublisher(Publisher publisher); - - /** - * A descriptor with information about the adaptee stream semantics. - */ - class Descriptor { - - private final boolean isMultiValue; - - private final boolean supportsEmpty; - - private final boolean isNoValue; - - public Descriptor(boolean isMultiValue, boolean canBeEmpty, boolean isNoValue) { - this.isMultiValue = isMultiValue; - this.supportsEmpty = canBeEmpty; - this.isNoValue = isNoValue; - } - - /** - * Return {@code true} if the adaptee implies 0..N values can be produced - * and is therefore a good fit to adapt to {@link Flux}. A {@code false} - * return value implies the adaptee will produce 1 value at most and is - * therefore a good fit for {@link Mono}. - */ - public boolean isMultiValue() { - return this.isMultiValue; - } - - /** - * Return {@code true} if the adaptee can complete without values. - */ - public boolean supportsEmpty() { - return this.supportsEmpty; - } - - /** - * Return {@code true} if the adaptee implies no values will be produced, - * i.e. providing only completion or error signal. - */ - public boolean isNoValue() { - return this.isNoValue; - } - } - } diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java index 6be6025637..f30d9d4485 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java @@ -16,8 +16,8 @@ package org.springframework.core; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Function; @@ -29,19 +29,17 @@ import io.reactivex.Maybe; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import rx.Completable; -import rx.Observable; import rx.RxReactiveStreams; -import rx.Single; import org.springframework.util.ClassUtils; /** * A registry of adapters to adapt to {@link Flux} and {@link Mono}. * - *

By default there are adapters for {@link CompletableFuture}, RxJava 1, and - * also for a any Reactive Streams {@link Publisher}. Additional adapters can be - * registered via {@link #registerFluxAdapter} and {@link #registerMonoAdapter}. + *

By default, depending on classpath availability, adapters are registered + * for RxJava 1, RxJava 2 types, and {@link CompletableFuture}. In addition the + * registry contains adapters for Reactor's own Flux and Mono types (no-op) + * along with adaption for any other Reactive Streams {@link Publisher}. * * @author Rossen Stoyanchev * @author Sebastien Deleuze @@ -58,25 +56,30 @@ public class ReactiveAdapterRegistry { private static final boolean rxJava2Present = ClassUtils.isPresent("io.reactivex.Flowable", ReactiveAdapterRegistry.class.getClassLoader()); - private final Map, ReactiveAdapter> adapterMap = new LinkedHashMap<>(4); + + private final List adapters = new ArrayList<>(32); /** * Create a registry and auto-register default adapters. */ public ReactiveAdapterRegistry() { + // Flux and Mono ahead of Publisher... + registerMonoAdapter(Mono.class, source -> (Mono) source, source -> source, - new ReactiveAdapter.Descriptor(false, true, false)); - registerFluxAdapter( - Flux.class, source -> (Flux) source, source -> source); - registerFluxAdapter( - Publisher.class, source -> Flux.from((Publisher) source), source -> source); + ReactiveTypeDescriptor.singleOptionalValue(Mono.class)); + + registerFluxAdapter(Flux.class, + source -> (Flux) source, source -> source); + + registerFluxAdapter(Publisher.class, + source -> Flux.from((Publisher) source), source -> source); registerMonoAdapter(CompletableFuture.class, source -> Mono.fromFuture((CompletableFuture) source), Mono::toFuture, - new ReactiveAdapter.Descriptor(false, true, false) + ReactiveTypeDescriptor.singleOptionalValue(CompletableFuture.class) ); if (rxJava1Present && rxJava1Adapter) { @@ -93,10 +96,10 @@ public class ReactiveAdapterRegistry { *

The provided functions can assume that input will never be {@code null} * and also that any {@link Optional} wrapper is unwrapped. */ - public void registerMonoAdapter(Class adapteeType, Function> toAdapter, - Function, Object> fromAdapter, ReactiveAdapter.Descriptor descriptor) { + public void registerMonoAdapter(Class reactiveType, Function> toAdapter, + Function, Object> fromAdapter, ReactiveTypeDescriptor descriptor) { - this.adapterMap.put(adapteeType, new MonoReactiveAdapter(toAdapter, fromAdapter, descriptor)); + this.adapters.add(new MonoReactiveAdapter(toAdapter, fromAdapter, descriptor)); } /** @@ -104,57 +107,57 @@ public class ReactiveAdapterRegistry { *

The provided functions can assume that input will never be {@code null} * and also that any {@link Optional} wrapper is unwrapped. */ - public void registerFluxAdapter(Class adapteeType, Function> toAdapter, + public void registerFluxAdapter(Class reactiveType, Function> toAdapter, Function, Object> fromAdapter) { - this.adapterMap.put(adapteeType, new FluxReactiveAdapter(toAdapter, fromAdapter)); + this.adapters.add(new FluxReactiveAdapter(toAdapter, fromAdapter, + ReactiveTypeDescriptor.multiValue(reactiveType))); } /** - * Get the adapter for the given adaptee type to adapt from. + * Get the adapter for the given reactive type to adapt from. */ - public ReactiveAdapter getAdapterFrom(Class adapteeType) { - return getAdapterFrom(adapteeType, null); + public ReactiveAdapter getAdapterFrom(Class reactiveType) { + return getAdapterFrom(reactiveType, null); } /** - * Get the adapter for the given adaptee type to adapt from. + * Get the adapter for the given reactive type to adapt from. * If the instance is not {@code null} its actual type is used to check. */ - public ReactiveAdapter getAdapterFrom(Class adapteeType, Object adaptee) { - Class actualType = getActualType(adapteeType, adaptee); + public ReactiveAdapter getAdapterFrom(Class reactiveType, Object adaptee) { + Class actualType = getActualType(reactiveType, adaptee); return getAdapterInternal(supportedType -> supportedType.isAssignableFrom(actualType)); } /** - * Get the adapter for the given adaptee type to adapt to. + * Get the adapter for the given reactive type to adapt to. */ - public ReactiveAdapter getAdapterTo(Class adapteeType) { - return getAdapterTo(adapteeType, null); + public ReactiveAdapter getAdapterTo(Class reactiveType) { + return getAdapterTo(reactiveType, null); } /** - * Get the adapter for the given adaptee type to adapt to. + * Get the adapter for the given reactive type to adapt to. * If the instance is not {@code null} its actual type is used to check. */ - public ReactiveAdapter getAdapterTo(Class adapteeType, Object adaptee) { - Class actualType = getActualType(adapteeType, adaptee); + public ReactiveAdapter getAdapterTo(Class reactiveType, Object adaptee) { + Class actualType = getActualType(reactiveType, adaptee); return getAdapterInternal(supportedType -> supportedType.equals(actualType)); } - private ReactiveAdapter getAdapterInternal(Predicate> adapteeTypePredicate) { - return this.adapterMap.keySet().stream() - .filter(adapteeTypePredicate) - .map(this.adapterMap::get) + private ReactiveAdapter getAdapterInternal(Predicate> predicate) { + return this.adapters.stream() + .filter(adapter -> predicate.test(adapter.getDescriptor().getReactiveType())) .findFirst() .orElse(null); } - private static Class getActualType(Class adapteeType, Object adaptee) { + private static Class getActualType(Class reactiveType, Object adaptee) { adaptee = unwrapOptional(adaptee); - return (adaptee != null ? adaptee.getClass() : adapteeType); + return (adaptee != null ? adaptee.getClass() : reactiveType); } private static Object unwrapOptional(Object value) { @@ -169,17 +172,19 @@ public class ReactiveAdapterRegistry { private final Function, Object> fromAdapter; - private final Descriptor descriptor; + private final ReactiveTypeDescriptor descriptor; - MonoReactiveAdapter(Function> to, Function, Object> from, Descriptor descriptor) { + MonoReactiveAdapter(Function> to, Function, Object> from, + ReactiveTypeDescriptor descriptor) { + this.toAdapter = to; this.fromAdapter = from; this.descriptor = descriptor; } @Override - public Descriptor getDescriptor() { + public ReactiveTypeDescriptor getDescriptor() { return this.descriptor; } @@ -219,16 +224,19 @@ public class ReactiveAdapterRegistry { private final Function, Object> fromAdapter; - private final Descriptor descriptor = new Descriptor(true, true, false); + private final ReactiveTypeDescriptor descriptor; - FluxReactiveAdapter(Function> to, Function, Object> from) { + FluxReactiveAdapter(Function> to, Function, Object> from, + ReactiveTypeDescriptor descriptor) { + + this.descriptor = descriptor; this.toAdapter = to; this.fromAdapter = from; } @Override - public Descriptor getDescriptor() { + public ReactiveTypeDescriptor getDescriptor() { return this.descriptor; } @@ -265,19 +273,19 @@ public class ReactiveAdapterRegistry { private static class RxJava1AdapterRegistrar { public void register(ReactiveAdapterRegistry registry) { - registry.registerFluxAdapter(Observable.class, - source -> Flux.from(RxReactiveStreams.toPublisher((Observable) source)), + registry.registerFluxAdapter(rx.Observable.class, + source -> Flux.from(RxReactiveStreams.toPublisher((rx.Observable) source)), RxReactiveStreams::toObservable ); - registry.registerMonoAdapter(Single.class, - source -> Mono.from(RxReactiveStreams.toPublisher((Single) source)), + registry.registerMonoAdapter(rx.Single.class, + source -> Mono.from(RxReactiveStreams.toPublisher((rx.Single) source)), RxReactiveStreams::toSingle, - new ReactiveAdapter.Descriptor(false, false, false) + ReactiveTypeDescriptor.singleRequiredValue(rx.Single.class) ); - registry.registerMonoAdapter(Completable.class, - source -> Mono.from(RxReactiveStreams.toPublisher((Completable) source)), + registry.registerMonoAdapter(rx.Completable.class, + source -> Mono.from(RxReactiveStreams.toPublisher((rx.Completable) source)), RxReactiveStreams::toCompletable, - new ReactiveAdapter.Descriptor(false, true, true) + ReactiveTypeDescriptor.noValue(rx.Completable.class) ); } } @@ -296,17 +304,17 @@ public class ReactiveAdapterRegistry { registry.registerMonoAdapter(io.reactivex.Single.class, source -> Mono.from(((io.reactivex.Single) source).toFlowable()), source -> Flowable.fromPublisher(source).toObservable().singleElement().toSingle(), - new ReactiveAdapter.Descriptor(false, false, false) + ReactiveTypeDescriptor.singleRequiredValue(io.reactivex.Single.class) ); registry.registerMonoAdapter(Maybe.class, source -> Mono.from(((Maybe) source).toFlowable()), source -> Flowable.fromPublisher(source).toObservable().singleElement(), - new ReactiveAdapter.Descriptor(false, true, false) + ReactiveTypeDescriptor.singleOptionalValue(Maybe.class) ); registry.registerMonoAdapter(io.reactivex.Completable.class, source -> Mono.from(((io.reactivex.Completable) source).toFlowable()), source -> Flowable.fromPublisher(source).toObservable().ignoreElements(), - new ReactiveAdapter.Descriptor(false, true, true) + ReactiveTypeDescriptor.noValue(io.reactivex.Completable.class) ); } } diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java new file mode 100644 index 0000000000..07e0be2a0e --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java @@ -0,0 +1,116 @@ +/* + * Copyright 2002-2016 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 + * + * http://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.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.util.Assert; + +/** + * Descriptor for a reactive type with information its stream semantics, i.e. + * how many values it can produce. + * + * @author Rossen Stoyanchev + * @since 5.0 + */ +public class ReactiveTypeDescriptor { + + private final Class reactiveType; + + private final boolean multiValue; + + private final boolean supportsEmpty; + + private final boolean noValue; + + + /** + * Private constructor. See static factory methods. + */ + private ReactiveTypeDescriptor(Class reactiveType, boolean multiValue, + boolean canBeEmpty, boolean noValue) { + + Assert.notNull(reactiveType, "'reactiveType' must not be null"); + this.reactiveType = reactiveType; + this.multiValue = multiValue; + this.supportsEmpty = canBeEmpty; + this.noValue = noValue; + } + + + /** + * Return the reactive type the descriptor was created for. + */ + public Class getReactiveType() { + return this.reactiveType; + } + + /** + * Return {@code true} if the reactive type can produce more than 1 value + * can be produced and is therefore a good fit to adapt to {@link Flux}. + * A {@code false} return value implies the reactive type can produce 1 + * value at most and is therefore a good fit to adapt to {@link Mono}. + */ + public boolean isMultiValue() { + return this.multiValue; + } + + /** + * Return {@code true} if the reactive type can complete with no values. + */ + public boolean supportsEmpty() { + return this.supportsEmpty; + } + + /** + * Return {@code true} if the reactive type does not produce any values and + * only provides completion and error signals. + */ + public boolean isNoValue() { + return this.noValue; + } + + + /** + * Descriptor for a reactive type that can produce 0..N values. + */ + public static ReactiveTypeDescriptor multiValue(Class reactiveType) { + return new ReactiveTypeDescriptor(reactiveType, true, true, false); + } + + /** + * Descriptor for a reactive type that can produce 0..1 values. + */ + public static ReactiveTypeDescriptor singleOptionalValue(Class reactiveType) { + return new ReactiveTypeDescriptor(reactiveType, false, true, false); + } + + /** + * Descriptor for a reactive type that must produce 1 value to complete. + */ + public static ReactiveTypeDescriptor singleRequiredValue(Class reactiveType) { + return new ReactiveTypeDescriptor(reactiveType, false, false, false); + } + + /** + * Descriptor for a reactive type that does not produce any values. + */ + public static ReactiveTypeDescriptor noValue(Class reactiveType) { + return new ReactiveTypeDescriptor(reactiveType, false, true, true); + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/ReactiveAdapterRegistryTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/ReactiveAdapterRegistryTests.java index 44c44f9d65..2c57849d1b 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/ReactiveAdapterRegistryTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/ReactiveAdapterRegistryTests.java @@ -43,11 +43,13 @@ public class ReactiveAdapterRegistryTests { private ReactiveAdapterRegistry adapterRegistry; + @Before public void setUp() throws Exception { this.adapterRegistry = new ReactiveAdapterRegistry(); } + @Test public void getDefaultAdapters() throws Exception { testMonoAdapter(Mono.class); diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java index afff4e87fb..50289601af 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java @@ -25,7 +25,7 @@ import reactor.core.publisher.MonoProcessor; import org.springframework.beans.BeanUtils; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapter; -import org.springframework.core.ReactiveAdapter.Descriptor; +import org.springframework.core.ReactiveTypeDescriptor; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; @@ -107,7 +107,7 @@ public class ModelAttributeMethodArgumentResolver implements HandlerMethodArgume Class clazz = parameter.getParameterType(); ReactiveAdapter adapter = getAdapterRegistry().getAdapterFrom(clazz); if (adapter != null) { - Descriptor descriptor = adapter.getDescriptor(); + ReactiveTypeDescriptor descriptor = adapter.getDescriptor(); if (descriptor.isNoValue() || descriptor.isMultiValue()) { return false; }