Browse Source
We now expose reactive interfaces to facilitate reactive repository support in store-specific modules. Spring Data modules are free to implement their reactive support using either RxJava 1 or Project Reactor (Reactive Streams). We expose a set of base interfaces: * `ReactiveCrudRepository` * `ReactiveSortingRepository` * `RxJava1CrudRepository` * `RxJava1SortingRepository` Reactive repositories provide a similar feature coverage to blocking repositories. Reactive paging support is limited to a `Mono<Page>`/`Single<Page>`. Data is fetched in a deferred way to provide a paging experience similar to blocking paging. A store module can choose either Project Reactor or RxJava 1 to implement reactive repository support. Project Reactor and RxJava types are converted in both directions allowing repositories to be composed of Project Reactor and RxJava 1 query methods. Reactive wrapper type conversion handles wrapper type conversion at repository level. Query/implementation method selection uses multi-pass candidate selection to invoke the most appropriate method (exact arguments, convertible wrappers, assignable arguments). We also provide ReactiveWrappers to expose metadata about reactive types and their value multiplicity.pull/185/head
21 changed files with 2527 additions and 58 deletions
@ -0,0 +1,259 @@
@@ -0,0 +1,259 @@
|
||||
/* |
||||
* Copyright 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.data.repository.core.support; |
||||
|
||||
import static org.springframework.core.GenericTypeResolver.*; |
||||
|
||||
import java.lang.reflect.Method; |
||||
import java.lang.reflect.Type; |
||||
import java.util.function.BiPredicate; |
||||
|
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.core.convert.ConversionService; |
||||
import org.springframework.data.repository.core.RepositoryInformation; |
||||
import org.springframework.data.repository.core.RepositoryMetadata; |
||||
import org.springframework.data.repository.util.QueryExecutionConverters; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* This {@link RepositoryInformation} uses a {@link ConversionService} to check whether method arguments can be |
||||
* converted for invocation of implementation methods. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class ReactiveRepositoryInformation extends DefaultRepositoryInformation { |
||||
|
||||
private final ConversionService conversionService; |
||||
|
||||
/** |
||||
* Creates a new {@link ReactiveRepositoryInformation} for the given repository interface and repository base class
|
||||
* using a {@link ConversionService}. |
||||
* |
||||
* @param metadata must not be {@literal null}. |
||||
* @param repositoryBaseClass must not be {@literal null}. |
||||
* @param customImplementationClass |
||||
* @param conversionService must not be {@literal null}. |
||||
*/ |
||||
public ReactiveRepositoryInformation(RepositoryMetadata metadata, Class<?> repositoryBaseClass, |
||||
Class<?> customImplementationClass, ConversionService conversionService) { |
||||
|
||||
super(metadata, repositoryBaseClass, customImplementationClass); |
||||
|
||||
Assert.notNull(conversionService, "Conversion service must not be null!"); |
||||
|
||||
this.conversionService = conversionService; |
||||
} |
||||
|
||||
/** |
||||
* Returns the given target class' method if the given method (declared in the repository interface) was also declared |
||||
* at the target class. Returns the given method if the given base class does not declare the method given. Takes |
||||
* generics into account. |
||||
* |
||||
* @param method must not be {@literal null} |
||||
* @param baseClass |
||||
* @return |
||||
*/ |
||||
Method getTargetClassMethod(Method method, Class<?> baseClass) { |
||||
|
||||
if (baseClass == null) { |
||||
return method; |
||||
} |
||||
|
||||
boolean wantsWrappers = wantsMethodUsingReactiveWrapperParameters(method); |
||||
|
||||
if (wantsWrappers) { |
||||
Method candidate = getMethodCandidate(method, baseClass, new ExactWrapperMatch(method)); |
||||
|
||||
if (candidate != null) { |
||||
return candidate; |
||||
} |
||||
|
||||
candidate = getMethodCandidate(method, baseClass, new WrapperConversionMatch(method, conversionService)); |
||||
|
||||
if (candidate != null) { |
||||
return candidate; |
||||
} |
||||
} |
||||
|
||||
Method candidate = getMethodCandidate(method, baseClass, |
||||
new MatchParameterOrComponentType(method, getRepositoryInterface())); |
||||
|
||||
if (candidate != null) { |
||||
return candidate; |
||||
} |
||||
|
||||
return method; |
||||
} |
||||
|
||||
private boolean wantsMethodUsingReactiveWrapperParameters(Method method) { |
||||
|
||||
boolean wantsWrappers = false; |
||||
|
||||
for (Class<?> parameterType : method.getParameterTypes()) { |
||||
if (isNonunwrappingWrapper(parameterType)) { |
||||
wantsWrappers = true; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return wantsWrappers; |
||||
} |
||||
|
||||
private Method getMethodCandidate(Method method, Class<?> baseClass, BiPredicate<Class<?>, Integer> predicate) { |
||||
|
||||
for (Method baseClassMethod : baseClass.getMethods()) { |
||||
|
||||
// Wrong name
|
||||
if (!method.getName().equals(baseClassMethod.getName())) { |
||||
continue; |
||||
} |
||||
|
||||
// Wrong number of arguments
|
||||
if (!(method.getParameterTypes().length == baseClassMethod.getParameterTypes().length)) { |
||||
continue; |
||||
} |
||||
|
||||
// Check whether all parameters match
|
||||
if (!parametersMatch(method, baseClassMethod, predicate)) { |
||||
continue; |
||||
} |
||||
|
||||
return baseClassMethod; |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* Checks the given method's parameters to match the ones of the given base class method. Matches generic arguments |
||||
* against the ones bound in the given repository interface. |
||||
* |
||||
* @param method |
||||
* @param baseClassMethod |
||||
* @param predicate |
||||
* @return |
||||
*/ |
||||
private boolean parametersMatch(Method method, Method baseClassMethod, BiPredicate<Class<?>, Integer> predicate) { |
||||
|
||||
Type[] genericTypes = baseClassMethod.getGenericParameterTypes(); |
||||
Class<?>[] types = baseClassMethod.getParameterTypes(); |
||||
|
||||
for (int i = 0; i < genericTypes.length; i++) { |
||||
if (!predicate.test(types[i], i)) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Checks whether the type is a wrapper without unwrapping support. Reactive wrappers don't like to be unwrapped. |
||||
* |
||||
* @param parameterType |
||||
* @return |
||||
*/ |
||||
static boolean isNonunwrappingWrapper(Class<?> parameterType) { |
||||
return QueryExecutionConverters.supports(parameterType) |
||||
&& !QueryExecutionConverters.supportsUnwrapping(parameterType); |
||||
} |
||||
|
||||
static class WrapperConversionMatch implements BiPredicate<Class<?>, Integer> { |
||||
|
||||
final Method declaredMethod; |
||||
final Class<?>[] declaredParameterTypes; |
||||
final ConversionService conversionService; |
||||
|
||||
public WrapperConversionMatch(Method declaredMethod, ConversionService conversionService) { |
||||
|
||||
this.declaredMethod = declaredMethod; |
||||
this.declaredParameterTypes = declaredMethod.getParameterTypes(); |
||||
this.conversionService = conversionService; |
||||
} |
||||
|
||||
@Override |
||||
public boolean test(Class<?> candidateParameterType, Integer index) { |
||||
|
||||
// TODO: should check for component type
|
||||
if (isNonunwrappingWrapper(candidateParameterType) && isNonunwrappingWrapper(declaredParameterTypes[index])) { |
||||
|
||||
if (conversionService.canConvert(declaredParameterTypes[index], candidateParameterType)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
} |
||||
|
||||
static class ExactWrapperMatch implements BiPredicate<Class<?>, Integer> { |
||||
|
||||
final Method declaredMethod; |
||||
final Class<?>[] declaredParameterTypes; |
||||
|
||||
public ExactWrapperMatch(Method declaredMethod) { |
||||
|
||||
this.declaredMethod = declaredMethod; |
||||
this.declaredParameterTypes = declaredMethod.getParameterTypes(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean test(Class<?> candidateParameterType, Integer index) { |
||||
|
||||
// TODO: should check for component type
|
||||
if (isNonunwrappingWrapper(candidateParameterType) && isNonunwrappingWrapper(declaredParameterTypes[index])) { |
||||
|
||||
if (declaredParameterTypes[index].isAssignableFrom(candidateParameterType)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
} |
||||
|
||||
static class MatchParameterOrComponentType implements BiPredicate<Class<?>, Integer> { |
||||
|
||||
final Method declaredMethod; |
||||
final Class<?>[] declaredParameterTypes; |
||||
final Class<?> repositoryInterface; |
||||
|
||||
public MatchParameterOrComponentType(Method declaredMethod, Class<?> repositoryInterface) { |
||||
|
||||
this.declaredMethod = declaredMethod; |
||||
this.declaredParameterTypes = declaredMethod.getParameterTypes(); |
||||
this.repositoryInterface = repositoryInterface; |
||||
} |
||||
|
||||
@Override |
||||
public boolean test(Class<?> candidateParameterType, Integer index) { |
||||
|
||||
MethodParameter parameter = new MethodParameter(declaredMethod, index); |
||||
Class<?> parameterType = resolveParameterType(parameter, repositoryInterface); |
||||
|
||||
if (!candidateParameterType.isAssignableFrom(parameterType) |
||||
|| !candidateParameterType.equals(declaredParameterTypes[index])) { |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,267 @@
@@ -0,0 +1,267 @@
|
||||
/* |
||||
* Copyright 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.data.repository.query; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.function.Predicate; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
import org.springframework.core.convert.converter.Converter; |
||||
import org.springframework.core.convert.support.GenericConversionService; |
||||
import org.springframework.data.repository.util.QueryExecutionConverters; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
import rx.Observable; |
||||
import rx.Single; |
||||
|
||||
/** |
||||
* Conversion support for reactive wrapper types. |
||||
* |
||||
* @author Mark Paluch |
||||
* @since 2.0 |
||||
*/ |
||||
public abstract class ReactiveWrapperConverters { |
||||
|
||||
private static final boolean PROJECT_REACTOR_PRESENT = ClassUtils.isPresent("reactor.core.converter.DependencyUtils", |
||||
QueryExecutionConverters.class.getClassLoader()); |
||||
private static final boolean RXJAVA_SINGLE_PRESENT = ClassUtils.isPresent("rx.Single", |
||||
QueryExecutionConverters.class.getClassLoader()); |
||||
private static final boolean RXJAVA_OBSERVABLE_PRESENT = ClassUtils.isPresent("rx.Observable", |
||||
QueryExecutionConverters.class.getClassLoader()); |
||||
|
||||
private static final List<AbstractReactiveWrapper<?>> REACTIVE_WRAPPERS = new ArrayList<>(); |
||||
private static final GenericConversionService GENERIC_CONVERSION_SERVICE = new GenericConversionService(); |
||||
|
||||
static { |
||||
|
||||
if (PROJECT_REACTOR_PRESENT) { |
||||
REACTIVE_WRAPPERS.add(FluxWrapper.INSTANCE); |
||||
REACTIVE_WRAPPERS.add(MonoWrapper.INSTANCE); |
||||
REACTIVE_WRAPPERS.add(PublisherWrapper.INSTANCE); |
||||
} |
||||
|
||||
if (RXJAVA_SINGLE_PRESENT) { |
||||
REACTIVE_WRAPPERS.add(SingleWrapper.INSTANCE); |
||||
} |
||||
|
||||
if (RXJAVA_OBSERVABLE_PRESENT) { |
||||
REACTIVE_WRAPPERS.add(ObservableWrapper.INSTANCE); |
||||
} |
||||
|
||||
QueryExecutionConverters.registerConvertersIn(GENERIC_CONVERSION_SERVICE); |
||||
} |
||||
|
||||
private ReactiveWrapperConverters() { |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Returns whether the given type is a supported wrapper type. |
||||
* |
||||
* @param type must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
public static boolean supports(Class<?> type) { |
||||
return assignableStream(type).isPresent(); |
||||
} |
||||
|
||||
/** |
||||
* Returns whether the type is a single-like wrapper. |
||||
* |
||||
* @param type must not be {@literal null}. |
||||
* @return |
||||
* @see Single |
||||
* @see Mono |
||||
*/ |
||||
public static boolean isSingleLike(Class<?> type) { |
||||
return assignableStream(type).map(wrapper -> wrapper.getMultiplicity() == Multiplicity.ONE).orElse(false); |
||||
} |
||||
|
||||
/** |
||||
* Returns whether the type is a collection/multi-element-like wrapper. |
||||
* |
||||
* @param type must not be {@literal null}. |
||||
* @return |
||||
* @see Observable |
||||
* @see Flux |
||||
* @see Publisher |
||||
*/ |
||||
public static boolean isCollectionLike(Class<?> type) { |
||||
return assignableStream(type).map(wrapper -> wrapper.getMultiplicity() == Multiplicity.MANY).orElse(false); |
||||
} |
||||
|
||||
/** |
||||
* Casts or converts the given wrapper type into a different wrapper type. |
||||
* |
||||
* @param stream the stream, must not be {@literal null}. |
||||
* @param expectedWrapperType must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
public static <T> T toWrapper(Object stream, Class<? extends T> expectedWrapperType) { |
||||
|
||||
Assert.notNull(stream, "Stream must not be null!"); |
||||
Assert.notNull(expectedWrapperType, "Converter must not be null!"); |
||||
|
||||
if (expectedWrapperType.isAssignableFrom(stream.getClass())) { |
||||
return (T) stream; |
||||
} |
||||
|
||||
return GENERIC_CONVERSION_SERVICE.convert(stream, expectedWrapperType); |
||||
} |
||||
|
||||
/** |
||||
* Maps elements of a reactive element stream to other elements. |
||||
* |
||||
* @param stream must not be {@literal null}. |
||||
* @param converter must not be {@literal null}. |
||||
* @return |
||||
*/ |
||||
public static <T> T map(Object stream, Converter<Object, Object> converter) { |
||||
|
||||
Assert.notNull(stream, "Stream must not be null!"); |
||||
Assert.notNull(converter, "Converter must not be null!"); |
||||
|
||||
for (AbstractReactiveWrapper<?> reactiveWrapper : REACTIVE_WRAPPERS) { |
||||
|
||||
if (ClassUtils.isAssignable(reactiveWrapper.getWrapperClass(), stream.getClass())) { |
||||
return (T) reactiveWrapper.map(stream, converter); |
||||
} |
||||
} |
||||
|
||||
throw new IllegalStateException(String.format("Cannot apply converter to %s", stream)); |
||||
} |
||||
|
||||
private static Optional<AbstractReactiveWrapper<?>> assignableStream(Class<?> type) { |
||||
|
||||
Assert.notNull(type, "Type must not be null!"); |
||||
|
||||
return findWrapper(wrapper -> ClassUtils.isAssignable(wrapper.getWrapperClass(), type)); |
||||
} |
||||
|
||||
private static Optional<AbstractReactiveWrapper<?>> findWrapper( |
||||
Predicate<? super AbstractReactiveWrapper<?>> predicate) { |
||||
|
||||
return REACTIVE_WRAPPERS.stream().filter(predicate).findFirst(); |
||||
} |
||||
|
||||
private abstract static class AbstractReactiveWrapper<T> { |
||||
|
||||
private final Class<? super T> wrapperClass; |
||||
private final Multiplicity multiplicity; |
||||
|
||||
public AbstractReactiveWrapper(Class<? super T> wrapperClass, Multiplicity multiplicity) { |
||||
this.wrapperClass = wrapperClass; |
||||
this.multiplicity = multiplicity; |
||||
} |
||||
|
||||
public Class<? super T> getWrapperClass() { |
||||
return wrapperClass; |
||||
} |
||||
|
||||
public Multiplicity getMultiplicity() { |
||||
return multiplicity; |
||||
} |
||||
|
||||
public abstract Object map(Object wrapper, Converter<Object, Object> converter); |
||||
} |
||||
|
||||
private static class MonoWrapper extends AbstractReactiveWrapper<Mono<?>> { |
||||
|
||||
static final MonoWrapper INSTANCE = new MonoWrapper(); |
||||
|
||||
private MonoWrapper() { |
||||
super(Mono.class, Multiplicity.ONE); |
||||
} |
||||
|
||||
public Mono<?> map(Object wrapper, Converter<Object, Object> converter) { |
||||
return ((Mono<?>) wrapper).map(converter::convert); |
||||
} |
||||
} |
||||
|
||||
private static class FluxWrapper extends AbstractReactiveWrapper<Flux<?>> { |
||||
|
||||
static final FluxWrapper INSTANCE = new FluxWrapper(); |
||||
|
||||
private FluxWrapper() { |
||||
super(Flux.class, Multiplicity.MANY); |
||||
} |
||||
|
||||
public Flux<?> map(Object wrapper, Converter<Object, Object> converter) { |
||||
return ((Flux<?>) wrapper).map(converter::convert); |
||||
} |
||||
} |
||||
|
||||
private static class PublisherWrapper extends AbstractReactiveWrapper<Publisher<?>> { |
||||
|
||||
static final PublisherWrapper INSTANCE = new PublisherWrapper(); |
||||
|
||||
public PublisherWrapper() { |
||||
super(Publisher.class, Multiplicity.MANY); |
||||
} |
||||
|
||||
@Override |
||||
public Publisher<?> map(Object wrapper, Converter<Object, Object> converter) { |
||||
|
||||
if (wrapper instanceof Mono) { |
||||
return MonoWrapper.INSTANCE.map((Mono<?>) wrapper, converter); |
||||
} |
||||
|
||||
if (wrapper instanceof Flux) { |
||||
return FluxWrapper.INSTANCE.map((Flux<?>) wrapper, converter); |
||||
} |
||||
|
||||
return FluxWrapper.INSTANCE.map(Flux.from((Publisher<?>) wrapper), converter); |
||||
} |
||||
} |
||||
|
||||
private static class SingleWrapper extends AbstractReactiveWrapper<Single<?>> { |
||||
|
||||
static final SingleWrapper INSTANCE = new SingleWrapper(); |
||||
|
||||
private SingleWrapper() { |
||||
super(Single.class, Multiplicity.ONE); |
||||
} |
||||
|
||||
@Override |
||||
public Single<?> map(Object wrapper, Converter<Object, Object> converter) { |
||||
return ((Single<?>) wrapper).map(converter::convert); |
||||
} |
||||
} |
||||
|
||||
private static class ObservableWrapper extends AbstractReactiveWrapper<Observable<?>> { |
||||
|
||||
static final ObservableWrapper INSTANCE = new ObservableWrapper(); |
||||
|
||||
private ObservableWrapper() { |
||||
super(Observable.class, Multiplicity.MANY); |
||||
} |
||||
|
||||
@Override |
||||
public Observable<?> map(Object wrapper, Converter<Object, Object> converter) { |
||||
return ((Observable<?>) wrapper).map(converter::convert); |
||||
} |
||||
} |
||||
|
||||
private enum Multiplicity { |
||||
ONE, MANY, |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,168 @@
@@ -0,0 +1,168 @@
|
||||
/* |
||||
* Copyright 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.data.repository.reactive; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
import org.springframework.data.repository.NoRepositoryBean; |
||||
import org.springframework.data.repository.Repository; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
/** |
||||
* Interface for generic CRUD operations on a repository for a specific type. This repository follows reactive paradigms |
||||
* and uses Project Reactor types which are built on top of Reactive Streams. |
||||
* |
||||
* @author Mark Paluch |
||||
* @see Mono |
||||
* @see Flux |
||||
* @see ReactiveStreamsCrudRepository |
||||
*/ |
||||
@NoRepositoryBean |
||||
public interface ReactiveCrudRepository<T, ID extends Serializable> extends Repository<T, ID> { |
||||
|
||||
/** |
||||
* Saves a given entity. Use the returned instance for further operations as the save operation might have changed the |
||||
* entity instance completely. |
||||
* |
||||
* @param entity |
||||
* @return the saved entity |
||||
*/ |
||||
<S extends T> Mono<S> save(S entity); |
||||
|
||||
/** |
||||
* Saves all given entities. |
||||
* |
||||
* @param entities must not be {@literal null}. |
||||
* @return the saved entities |
||||
* @throws IllegalArgumentException in case the given entity is {@literal null}. |
||||
*/ |
||||
<S extends T> Flux<S> save(Iterable<S> entities); |
||||
|
||||
/** |
||||
* Saves all given entities. |
||||
* |
||||
* @param entityStream must not be {@literal null}. |
||||
* @return the saved entities |
||||
* @throws IllegalArgumentException in case the given {@code Publisher} is {@literal null}. |
||||
*/ |
||||
<S extends T> Flux<S> save(Publisher<S> entityStream); |
||||
|
||||
/** |
||||
* Retrieves an entity by its id. |
||||
* |
||||
* @param id must not be {@literal null}. |
||||
* @return the entity with the given id or {@literal null} if none found |
||||
* @throws IllegalArgumentException if {@code id} is {@literal null} |
||||
*/ |
||||
Mono<T> findOne(ID id); |
||||
|
||||
/** |
||||
* Retrieves an entity by its id supplied by a {@link Mono}. |
||||
* |
||||
* @param id must not be {@literal null}. |
||||
* @return the entity with the given id or {@literal null} if none found |
||||
* @throws IllegalArgumentException if {@code id} is {@literal null} |
||||
*/ |
||||
Mono<T> findOne(Mono<ID> id); |
||||
|
||||
/** |
||||
* Returns whether an entity with the given id exists. |
||||
* |
||||
* @param id must not be {@literal null}. |
||||
* @return true if an entity with the given id exists, {@literal false} otherwise |
||||
* @throws IllegalArgumentException if {@code id} is {@literal null} |
||||
*/ |
||||
Mono<Boolean> exists(ID id); |
||||
|
||||
/** |
||||
* Returns whether an entity with the given id exists supplied by a {@link Mono}. |
||||
* |
||||
* @param id must not be {@literal null}. |
||||
* @return true if an entity with the given id exists, {@literal false} otherwise |
||||
* @throws IllegalArgumentException if {@code id} is {@literal null} |
||||
*/ |
||||
Mono<Boolean> exists(Mono<ID> id); |
||||
|
||||
/** |
||||
* Returns all instances of the type. |
||||
* |
||||
* @return all entities |
||||
*/ |
||||
Flux<T> findAll(); |
||||
|
||||
/** |
||||
* Returns all instances of the type with the given IDs. |
||||
* |
||||
* @param ids |
||||
* @return |
||||
*/ |
||||
Flux<T> findAll(Iterable<ID> ids); |
||||
|
||||
/** |
||||
* Returns all instances of the type with the given IDs. |
||||
* |
||||
* @param idStream |
||||
* @return |
||||
*/ |
||||
Flux<T> findAll(Publisher<ID> idStream); |
||||
|
||||
/** |
||||
* Returns the number of entities available. |
||||
* |
||||
* @return the number of entities |
||||
*/ |
||||
Mono<Long> count(); |
||||
|
||||
/** |
||||
* Deletes the entity with the given id. |
||||
* |
||||
* @param id must not be {@literal null}. |
||||
* @throws IllegalArgumentException in case the given {@code id} is {@literal null} |
||||
*/ |
||||
Mono<Void> delete(ID id); |
||||
|
||||
/** |
||||
* Deletes a given entity. |
||||
* |
||||
* @param entity |
||||
* @throws IllegalArgumentException in case the given entity is {@literal null}. |
||||
*/ |
||||
Mono<Void> delete(T entity); |
||||
|
||||
/** |
||||
* Deletes the given entities. |
||||
* |
||||
* @param entities |
||||
* @throws IllegalArgumentException in case the given {@link Iterable} is {@literal null}. |
||||
*/ |
||||
Mono<Void> delete(Iterable<? extends T> entities); |
||||
|
||||
/** |
||||
* Deletes the given entities. |
||||
* |
||||
* @param entityStream |
||||
* @throws IllegalArgumentException in case the given {@link Publisher} is {@literal null}. |
||||
*/ |
||||
Mono<Void> delete(Publisher<? extends T> entityStream); |
||||
|
||||
/** |
||||
* Deletes all entities managed by the repository. |
||||
*/ |
||||
Mono<Void> deleteAll(); |
||||
} |
||||
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
/* |
||||
* Copyright 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.data.repository.reactive; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
import org.springframework.data.domain.Page; |
||||
import org.springframework.data.domain.Pageable; |
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.repository.NoRepositoryBean; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
/** |
||||
* Extension of {@link ReactiveCrudRepository} to provide additional methods to retrieve entities using the pagination |
||||
* and sorting abstraction. |
||||
* |
||||
* @author Mark Paluch |
||||
* @see Sort |
||||
* @see Pageable |
||||
* @see Mono |
||||
* @see Flux |
||||
*/ |
||||
@NoRepositoryBean |
||||
public interface ReactivePagingAndSortingRepository<T, ID extends Serializable> extends ReactiveCrudRepository<T, ID> { |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.reactive.ReactiveCrudRepository#findAll() |
||||
*/ |
||||
@Override |
||||
Flux<T> findAll(); |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.reactive.ReactiveCrudRepository#findAll(java.lang.Iterable) |
||||
*/ |
||||
@Override |
||||
Flux<T> findAll(Iterable<ID> ids); |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.reactive.ReactiveCrudRepository#findAll(org.reactivestreams.Publisher) |
||||
*/ |
||||
@Override |
||||
Flux<T> findAll(Publisher<ID> idStream); |
||||
|
||||
/** |
||||
* Returns all entities sorted by the given options. |
||||
* |
||||
* @param sort |
||||
* @return all entities sorted by the given options |
||||
*/ |
||||
Flux<T> findAll(Sort sort); |
||||
|
||||
/** |
||||
* Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object. |
||||
* |
||||
* @param pageable |
||||
* @return a page of entities |
||||
*/ |
||||
Mono<Page<T>> findAll(Pageable pageable); |
||||
} |
||||
@ -0,0 +1,167 @@
@@ -0,0 +1,167 @@
|
||||
/* |
||||
* Copyright 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.data.repository.reactive; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
import org.springframework.data.repository.NoRepositoryBean; |
||||
import org.springframework.data.repository.Repository; |
||||
|
||||
import rx.Observable; |
||||
import rx.Single; |
||||
|
||||
/** |
||||
* Interface for generic CRUD operations on a repository for a specific type. This repository follows reactive paradigms |
||||
* and uses RxJava types. |
||||
* |
||||
* @author Mark Paluch |
||||
* @see Single |
||||
* @see Observable |
||||
*/ |
||||
@NoRepositoryBean |
||||
public interface RxJavaCrudRepository<T, ID extends Serializable> extends Repository<T, ID> { |
||||
|
||||
/** |
||||
* Saves a given entity. Use the returned instance for further operations as the save operation might have changed the |
||||
* entity instance completely. |
||||
* |
||||
* @param entity |
||||
* @return the saved entity |
||||
*/ |
||||
<S extends T> Single<S> save(S entity); |
||||
|
||||
/** |
||||
* Saves all given entities. |
||||
* |
||||
* @param entities must not be {@literal null}. |
||||
* @return the saved entities |
||||
* @throws IllegalArgumentException in case the given entity is {@literal null}. |
||||
*/ |
||||
<S extends T> Observable<S> save(Iterable<S> entities); |
||||
|
||||
/** |
||||
* Saves all given entities. |
||||
* |
||||
* @param entityStream must not be {@literal null}. |
||||
* @return the saved entities |
||||
* @throws IllegalArgumentException in case the given {@code Publisher} is {@literal null}. |
||||
*/ |
||||
<S extends T> Observable<S> save(Observable<S> entityStream); |
||||
|
||||
/** |
||||
* Retrieves an entity by its id. |
||||
* |
||||
* @param id must not be {@literal null}. |
||||
* @return the entity with the given id or {@literal null} if none found |
||||
* @throws IllegalArgumentException if {@code id} is {@literal null} |
||||
*/ |
||||
Single<T> findOne(ID id); |
||||
|
||||
/** |
||||
* Retrieves an entity by its id supplied by a {@link Single}. |
||||
* |
||||
* @param id must not be {@literal null}. |
||||
* @return the entity with the given id or {@literal null} if none found |
||||
* @throws IllegalArgumentException if {@code id} is {@literal null} |
||||
*/ |
||||
Single<T> findOne(Single<ID> id); |
||||
|
||||
/** |
||||
* Returns whether an entity with the given id exists. |
||||
* |
||||
* @param id must not be {@literal null}. |
||||
* @return true if an entity with the given id exists, {@literal false} otherwise |
||||
* @throws IllegalArgumentException if {@code id} is {@literal null} |
||||
*/ |
||||
Single<Boolean> exists(ID id); |
||||
|
||||
/** |
||||
* Returns whether an entity with the given id exists supplied by a {@link Single}. |
||||
* |
||||
* @param id must not be {@literal null}. |
||||
* @return true if an entity with the given id exists, {@literal false} otherwise |
||||
* @throws IllegalArgumentException if {@code id} is {@literal null} |
||||
*/ |
||||
Single<Boolean> exists(Single<ID> id); |
||||
|
||||
/** |
||||
* Returns all instances of the type. |
||||
* |
||||
* @return all entities |
||||
*/ |
||||
Observable<T> findAll(); |
||||
|
||||
/** |
||||
* Returns all instances of the type with the given IDs. |
||||
* |
||||
* @param ids |
||||
* @return |
||||
*/ |
||||
Observable<T> findAll(Iterable<ID> ids); |
||||
|
||||
/** |
||||
* Returns all instances of the type with the given IDs. |
||||
* |
||||
* @param idStream |
||||
* @return |
||||
*/ |
||||
Observable<T> findAll(Observable<ID> idStream); |
||||
|
||||
/** |
||||
* Returns the number of entities available. |
||||
* |
||||
* @return the number of entities |
||||
*/ |
||||
Single<Long> count(); |
||||
|
||||
/** |
||||
* Deletes the entity with the given id. |
||||
* |
||||
* @param id must not be {@literal null}. |
||||
* @throws IllegalArgumentException in case the given {@code id} is {@literal null} |
||||
*/ |
||||
Single<Void> delete(ID id); |
||||
|
||||
/** |
||||
* Deletes a given entity. |
||||
* |
||||
* @param entity |
||||
* @throws IllegalArgumentException in case the given entity is {@literal null}. |
||||
*/ |
||||
Single<Void> delete(T entity); |
||||
|
||||
/** |
||||
* Deletes the given entities. |
||||
* |
||||
* @param entities |
||||
* @throws IllegalArgumentException in case the given {@link Iterable} is {@literal null}. |
||||
*/ |
||||
Single<Void> delete(Iterable<? extends T> entities); |
||||
|
||||
/** |
||||
* Deletes the given entities. |
||||
* |
||||
* @param entityStream |
||||
* @throws IllegalArgumentException in case the given {@link Publisher} is {@literal null}. |
||||
*/ |
||||
Single<Void> delete(Observable<? extends T> entityStream); |
||||
|
||||
/** |
||||
* Deletes all entities managed by the repository. |
||||
*/ |
||||
Single<Void> deleteAll(); |
||||
} |
||||
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
/* |
||||
* Copyright 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.data.repository.reactive; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
import org.springframework.data.domain.Page; |
||||
import org.springframework.data.domain.Pageable; |
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.repository.NoRepositoryBean; |
||||
|
||||
import rx.Observable; |
||||
import rx.Single; |
||||
|
||||
/** |
||||
* Extension of {@link RxJavaCrudRepository} to provide additional methods to retrieve entities using the pagination and sorting |
||||
* abstraction. |
||||
* |
||||
* @author Mark Paluch |
||||
* @see Sort |
||||
* @see Pageable |
||||
* @see Single |
||||
* @see Observable |
||||
* @see RxJavaCrudRepository |
||||
*/ |
||||
@NoRepositoryBean |
||||
public interface RxJavaPagingAndSortingRepository<T, ID extends Serializable> extends RxJavaCrudRepository<T, ID> { |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.reactive.ReactiveCrudRepository#findAll() |
||||
*/ |
||||
@Override |
||||
Observable<T> findAll(); |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.reactive.ReactiveCrudRepository#findAll(java.lang.Iterable) |
||||
*/ |
||||
@Override |
||||
Observable<T> findAll(Iterable<ID> ids); |
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* @see org.springframework.data.repository.reactive.ReactiveCrudRepository#findAll(org.reactivestreams.Publisher) |
||||
*/ |
||||
@Override |
||||
Observable<T> findAll(Observable<ID> idStream); |
||||
|
||||
/** |
||||
* Returns all entities sorted by the given options. |
||||
* |
||||
* @param sort |
||||
* @return all entities sorted by the given options |
||||
*/ |
||||
Observable<T> findAll(Sort sort); |
||||
|
||||
/** |
||||
* Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object. |
||||
* |
||||
* @param pageable |
||||
* @return a page of entities |
||||
*/ |
||||
Single<Page<T>> findAll(Pageable pageable); |
||||
} |
||||
@ -0,0 +1,149 @@
@@ -0,0 +1,149 @@
|
||||
/* |
||||
* Copyright 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.data.repository.core.support; |
||||
|
||||
import static org.hamcrest.Matchers.*; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import rx.Observable; |
||||
|
||||
import java.io.Serializable; |
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.runners.MockitoJUnitRunner; |
||||
import org.reactivestreams.Publisher; |
||||
import org.springframework.core.convert.support.DefaultConversionService; |
||||
import org.springframework.data.repository.core.RepositoryMetadata; |
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository; |
||||
import org.springframework.data.repository.reactive.ReactivePagingAndSortingRepository; |
||||
import org.springframework.data.repository.reactive.RxJavaCrudRepository; |
||||
import org.springframework.data.repository.util.QueryExecutionConverters; |
||||
|
||||
/** |
||||
* Unit tests for {@link ConvertingMethodParameterRepositoryInformation}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class ReactiveRepositoryInformationUnitTests { |
||||
|
||||
static final Class<ReactiveJavaInterfaceWithGenerics> REPOSITORY = ReactiveJavaInterfaceWithGenerics.class; |
||||
|
||||
@Test |
||||
public void discoversMethodWithoutComparingReturnType() throws Exception { |
||||
|
||||
Method method = RxJavaInterfaceWithGenerics.class.getMethod("deleteAll"); |
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(RxJavaInterfaceWithGenerics.class); |
||||
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata, REPOSITORY, null); |
||||
|
||||
Method reference = information.getTargetClassMethod(method); |
||||
assertEquals(ReactiveCrudRepository.class, reference.getDeclaringClass()); |
||||
assertThat(reference.getName(), is("deleteAll")); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversMethodWithConvertibleArguments() throws Exception { |
||||
|
||||
DefaultConversionService conversionService = new DefaultConversionService(); |
||||
QueryExecutionConverters.registerConvertersIn(conversionService); |
||||
|
||||
Method method = RxJavaInterfaceWithGenerics.class.getMethod("save", Observable.class); |
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(RxJavaInterfaceWithGenerics.class); |
||||
DefaultRepositoryInformation information = new ReactiveRepositoryInformation(metadata, REPOSITORY, null, |
||||
conversionService); |
||||
|
||||
Method reference = information.getTargetClassMethod(method); |
||||
assertEquals(ReactiveCrudRepository.class, reference.getDeclaringClass()); |
||||
assertThat(reference.getName(), is("save")); |
||||
assertThat(reference.getParameterTypes()[0], is(equalTo(Publisher.class))); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversMethodAssignableArguments() throws Exception { |
||||
|
||||
DefaultConversionService conversionService = new DefaultConversionService(); |
||||
QueryExecutionConverters.registerConvertersIn(conversionService); |
||||
|
||||
Method method = ReactivePagingAndSortingRepository.class.getMethod("save", Publisher.class); |
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ReactiveJavaInterfaceWithGenerics.class); |
||||
DefaultRepositoryInformation information = new ReactiveRepositoryInformation(metadata, REPOSITORY, null, |
||||
conversionService); |
||||
|
||||
Method reference = information.getTargetClassMethod(method); |
||||
assertEquals(ReactiveCrudRepository.class, reference.getDeclaringClass()); |
||||
assertThat(reference.getName(), is("save")); |
||||
assertThat(reference.getParameterTypes()[0], is(equalTo(Publisher.class))); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversMethodExactIterableArguments() throws Exception { |
||||
|
||||
DefaultConversionService conversionService = new DefaultConversionService(); |
||||
QueryExecutionConverters.registerConvertersIn(conversionService); |
||||
|
||||
Method method = ReactiveJavaInterfaceWithGenerics.class.getMethod("save", Iterable.class); |
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ReactiveJavaInterfaceWithGenerics.class); |
||||
DefaultRepositoryInformation information = new ReactiveRepositoryInformation(metadata, REPOSITORY, null, |
||||
conversionService); |
||||
|
||||
Method reference = information.getTargetClassMethod(method); |
||||
assertEquals(ReactiveCrudRepository.class, reference.getDeclaringClass()); |
||||
assertThat(reference.getName(), is("save")); |
||||
assertThat(reference.getParameterTypes()[0], is(equalTo(Iterable.class))); |
||||
} |
||||
|
||||
@Test |
||||
public void discoversMethodExactObjectArguments() throws Exception { |
||||
|
||||
DefaultConversionService conversionService = new DefaultConversionService(); |
||||
QueryExecutionConverters.registerConvertersIn(conversionService); |
||||
|
||||
Method method = ReactiveJavaInterfaceWithGenerics.class.getMethod("save", Object.class); |
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ReactiveJavaInterfaceWithGenerics.class); |
||||
DefaultRepositoryInformation information = new ReactiveRepositoryInformation(metadata, REPOSITORY, null, |
||||
conversionService); |
||||
|
||||
Method reference = information.getTargetClassMethod(method); |
||||
assertEquals(ReactiveCrudRepository.class, reference.getDeclaringClass()); |
||||
assertThat(reference.getName(), is("save")); |
||||
assertThat(reference.getParameterTypes()[0], is(equalTo(Object.class))); |
||||
} |
||||
|
||||
interface RxJavaInterfaceWithGenerics extends RxJavaCrudRepository<User, String> {} |
||||
|
||||
interface ReactiveJavaInterfaceWithGenerics extends ReactiveCrudRepository<User, String> {} |
||||
|
||||
static abstract class DummyGenericReactiveRepositorySupport<T, ID extends Serializable> |
||||
implements ReactiveCrudRepository<T, ID> { |
||||
|
||||
} |
||||
|
||||
static class User { |
||||
|
||||
String id; |
||||
|
||||
public String getId() { |
||||
return id; |
||||
} |
||||
|
||||
public void setId(String id) { |
||||
this.id = id; |
||||
} |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,133 @@
@@ -0,0 +1,133 @@
|
||||
/* |
||||
* Copyright 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.data.repository.core.support; |
||||
|
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.junit.rules.ExpectedException; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.runners.MockitoJUnitRunner; |
||||
import org.springframework.core.convert.support.DefaultConversionService; |
||||
import org.springframework.data.repository.Repository; |
||||
import org.springframework.data.repository.reactive.ReactivePagingAndSortingRepository; |
||||
import org.springframework.data.repository.util.QueryExecutionConverters; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
import rx.Single; |
||||
|
||||
/** |
||||
* Unit tests for {@link RepositoryFactorySupport} using reactive wrapper types. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class ReactiveWrapperRepositoryFactorySupportUnitTests { |
||||
|
||||
public @Rule ExpectedException exception = ExpectedException.none(); |
||||
|
||||
DummyRepositoryFactory factory; |
||||
|
||||
@Mock ReactivePagingAndSortingRepository<Object, Serializable> backingRepo; |
||||
@Mock ObjectRepositoryCustom customImplementation; |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
|
||||
DefaultConversionService defaultConversionService = new DefaultConversionService(); |
||||
QueryExecutionConverters.registerConvertersIn(defaultConversionService); |
||||
|
||||
factory = new DummyRepositoryFactory(backingRepo); |
||||
factory.setConversionService(defaultConversionService); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void invokesCustomMethodIfItRedeclaresACRUDOne() { |
||||
|
||||
ObjectRepository repository = factory.getRepository(ObjectRepository.class, customImplementation); |
||||
repository.findOne(1); |
||||
|
||||
verify(customImplementation, times(1)).findOne(1); |
||||
verify(backingRepo, times(0)).findOne(1); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void callsMethodOnBaseImplementationWithExactArguments() { |
||||
|
||||
Serializable id = 1L; |
||||
ConvertingRepository repository = factory.getRepository(ConvertingRepository.class); |
||||
repository.exists(id); |
||||
|
||||
verify(backingRepo, times(1)).exists(id); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void doesNotCallMethodOnBaseEvenIfDeclaredTypeCouldBeConverted() { |
||||
|
||||
Long id = 1L; |
||||
ConvertingRepository repository = factory.getRepository(ConvertingRepository.class); |
||||
repository.exists(id); |
||||
|
||||
verifyZeroInteractions(backingRepo); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void callsMethodOnBaseImplementationWithTypeConversion() { |
||||
|
||||
Single<Long> ids = Single.just(1L); |
||||
|
||||
ConvertingRepository repository = factory.getRepository(ConvertingRepository.class); |
||||
repository.exists(ids); |
||||
|
||||
verify(backingRepo, times(1)).exists(any(Mono.class)); |
||||
} |
||||
|
||||
interface ConvertingRepository extends Repository<Object, Long> { |
||||
|
||||
Single<Boolean> exists(Single<Long> id); |
||||
|
||||
Single<Boolean> exists(Serializable id); |
||||
|
||||
Single<Boolean> exists(Long id); |
||||
} |
||||
|
||||
interface ObjectRepository |
||||
extends Repository<Object, Serializable>, RepositoryFactorySupportUnitTests.ObjectRepositoryCustom { |
||||
|
||||
} |
||||
|
||||
interface ObjectRepositoryCustom { |
||||
|
||||
Object findOne(Serializable id); |
||||
} |
||||
} |
||||
@ -0,0 +1,158 @@
@@ -0,0 +1,158 @@
|
||||
/* |
||||
* Copyright 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.data.repository.query; |
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.*; |
||||
|
||||
import org.junit.Test; |
||||
import org.reactivestreams.Publisher; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
import rx.Completable; |
||||
import rx.Observable; |
||||
import rx.Single; |
||||
|
||||
/** |
||||
* Unit tests for {@link ReactiveWrapperConverters}. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
public class ReactiveWrapperConvertersUnitTests { |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void shouldSupportReactorTypes() { |
||||
|
||||
assertThat(ReactiveWrapperConverters.supports(Mono.class)).isTrue(); |
||||
assertThat(ReactiveWrapperConverters.supports(Flux.class)).isTrue(); |
||||
assertThat(ReactiveWrapperConverters.supports(Publisher.class)).isTrue(); |
||||
assertThat(ReactiveWrapperConverters.supports(Object.class)).isFalse(); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void shouldSupportRxJavaTypes() { |
||||
|
||||
assertThat(ReactiveWrapperConverters.supports(Single.class)).isTrue(); |
||||
assertThat(ReactiveWrapperConverters.supports(Observable.class)).isTrue(); |
||||
assertThat(ReactiveWrapperConverters.supports(Completable.class)).isFalse(); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void isSingleLikeShouldReportCorrectSingleTypes() { |
||||
|
||||
assertThat(ReactiveWrapperConverters.isSingleLike(Mono.class)).isTrue(); |
||||
assertThat(ReactiveWrapperConverters.isSingleLike(Flux.class)).isFalse(); |
||||
assertThat(ReactiveWrapperConverters.isSingleLike(Single.class)).isTrue(); |
||||
assertThat(ReactiveWrapperConverters.isSingleLike(Observable.class)).isFalse(); |
||||
assertThat(ReactiveWrapperConverters.isSingleLike(Publisher.class)).isFalse(); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void isCollectionLikeShouldReportCorrectCollectionTypes() { |
||||
|
||||
assertThat(ReactiveWrapperConverters.isCollectionLike(Mono.class)).isFalse(); |
||||
assertThat(ReactiveWrapperConverters.isCollectionLike(Flux.class)).isTrue(); |
||||
assertThat(ReactiveWrapperConverters.isCollectionLike(Single.class)).isFalse(); |
||||
assertThat(ReactiveWrapperConverters.isCollectionLike(Observable.class)).isTrue(); |
||||
assertThat(ReactiveWrapperConverters.isCollectionLike(Publisher.class)).isTrue(); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void toWrapperShouldCastMonoToMono() { |
||||
|
||||
Mono<String> foo = Mono.just("foo"); |
||||
assertThat(ReactiveWrapperConverters.toWrapper(foo, Mono.class)).isSameAs(foo); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void toWrapperShouldConvertMonoToSingle() { |
||||
|
||||
Mono<String> foo = Mono.just("foo"); |
||||
assertThat(ReactiveWrapperConverters.toWrapper(foo, Single.class)).isInstanceOf(Single.class); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void toWrapperShouldConvertMonoToFlux() { |
||||
|
||||
Mono<String> foo = Mono.just("foo"); |
||||
assertThat(ReactiveWrapperConverters.toWrapper(foo, Flux.class)).isInstanceOf(Flux.class); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void shouldMapMono() { |
||||
|
||||
Mono<String> foo = Mono.just("foo"); |
||||
Mono<Long> map = ReactiveWrapperConverters.map(foo, source -> 1L); |
||||
assertThat(map.block()).isEqualTo(1L); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void shouldMapFlux() { |
||||
|
||||
Flux<String> foo = Flux.just("foo"); |
||||
Flux<Long> map = ReactiveWrapperConverters.map(foo, source -> 1L); |
||||
assertThat(map.next().block()).isEqualTo(1L); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void shouldMapSingle() { |
||||
|
||||
Single<String> foo = Single.just("foo"); |
||||
Single<Long> map = ReactiveWrapperConverters.map(foo, source -> 1L); |
||||
assertThat(map.toBlocking().value()).isEqualTo(1L); |
||||
} |
||||
|
||||
/** |
||||
* @see DATACMNS-836 |
||||
*/ |
||||
@Test |
||||
public void shouldMapObservable() { |
||||
|
||||
Observable<String> foo = Observable.just("foo"); |
||||
Observable<Long> map = ReactiveWrapperConverters.map(foo, source -> 1L); |
||||
assertThat(map.toBlocking().first()).isEqualTo(1L); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue