DATACMNS-1764 - Capture reactive invocation on per subscription basis.

Original Pull Request: #455
This commit is contained in:
Christoph Strobl
2020-08-11 13:53:30 +02:00
parent 35a3d3cde9
commit b667dfa779
12 changed files with 805 additions and 508 deletions

View File

@@ -23,11 +23,12 @@ import java.util.Optional;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.ResolvableType;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.DefaultRepositoryInvocationMulticaster;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.NoOpRepositoryInvocationMulticaster;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
@@ -43,6 +44,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
*
* @author Oliver Gierke
* @author Mark Paluch
* @author Christoph Strobl
*/
class QueryExecutorMethodInterceptor implements MethodInterceptor {
@@ -52,7 +54,7 @@ class QueryExecutorMethodInterceptor implements MethodInterceptor {
private final QueryExecutionResultHandler resultHandler;
private final NamedQueries namedQueries;
private final List<QueryCreationListener<?>> queryPostProcessors;
private final List<RepositoryMethodInvocationListener> methodInvocationListeners;
private final RepositoryInvocationMulticaster invocationMulticaster;
/**
* Creates a new {@link QueryExecutorMethodInterceptor}. Builds a model of {@link QueryMethod}s to be invoked on
@@ -66,7 +68,8 @@ class QueryExecutorMethodInterceptor implements MethodInterceptor {
this.repositoryInformation = repositoryInformation;
this.namedQueries = namedQueries;
this.queryPostProcessors = queryPostProcessors;
this.methodInvocationListeners = methodInvocationListeners;
this.invocationMulticaster = methodInvocationListeners.isEmpty() ? NoOpRepositoryInvocationMulticaster.INSTANCE
: new DefaultRepositoryInvocationMulticaster(methodInvocationListeners);
this.resultHandler = new QueryExecutionResultHandler(RepositoryFactorySupport.CONVERSION_SERVICE);
@@ -138,7 +141,6 @@ class QueryExecutorMethodInterceptor implements MethodInterceptor {
if (hasQueryFor(method)) {
RepositoryInvocationListener invocationListener = getInvocationListener();
RepositoryMethodInvoker invocationMetadata = invocationMetadataCache.get(method);
if (invocationMetadata == null) {
@@ -146,19 +148,13 @@ class QueryExecutorMethodInterceptor implements MethodInterceptor {
invocationMetadataCache.put(method, invocationMetadata);
}
return invocationMetadata.invoke(invocationListener, invocation.getArguments());
return invocationMetadata.invoke(repositoryInformation.getRepositoryInterface(), invocationMulticaster,
invocation.getArguments());
}
return invocation.proceed();
}
private RepositoryInvocationListener getInvocationListener() {
return methodInvocationListeners.isEmpty() ? RepositoryInvocationListener.NoOpRepositoryInvocationListener.INSTANCE
: new RepositoryInvocationListener.RepositoryInvocationMulticaster(
repositoryInformation.getRepositoryInterface(), methodInvocationListeners);
}
/**
* Returns whether we know of a query to execute for the given {@link Method};
*

View File

@@ -32,6 +32,9 @@ import java.util.stream.Stream;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.MethodLookup.InvokedMethod;
import org.springframework.data.repository.core.support.MethodLookup.MethodPredicate;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.NoOpRepositoryInvocationMulticaster;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -56,6 +59,7 @@ import org.springframework.util.ReflectionUtils;
* Composition objects are immutable and thread-safe.
*
* @author Mark Paluch
* @author Christoph Strobl
* @soundtrack Masterboy - Anybody (Fj Gauder Mix)
* @since 2.0
* @see RepositoryFragment
@@ -63,16 +67,50 @@ import org.springframework.util.ReflectionUtils;
public class RepositoryComposition {
private static final BiFunction<Method, Object[], Object[]> PASSTHRU_ARG_CONVERTER = (methodParameter, o) -> o;
private static final RepositoryComposition EMPTY = new RepositoryComposition(RepositoryFragments.empty(),
private static final BiFunction<Method, Object[], Object[]> REACTIVE_ARGS_CONVERTER = (method, args) -> {
if (ReactiveWrappers.isAvailable()) {
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] converted = new Object[args.length];
for (int i = 0; i < args.length; i++) {
Object value = args[i];
Object convertedArg = value;
Class<?> parameterType = parameterTypes.length > i ? parameterTypes[i] : null;
if (value != null && parameterType != null) {
if (!parameterType.isAssignableFrom(value.getClass())
&& ReactiveWrapperConverters.canConvert(value.getClass(), parameterType)) {
convertedArg = ReactiveWrapperConverters.toWrapper(value, parameterType);
}
}
converted[i] = convertedArg;
}
return converted;
}
return args;
};
private static final RepositoryComposition EMPTY = new RepositoryComposition(null, RepositoryFragments.empty(),
MethodLookups.direct(), PASSTHRU_ARG_CONVERTER);
private final Map<Method, Method> methodCache = new ConcurrentReferenceHashMap<>();
private final RepositoryFragments fragments;
private final MethodLookup methodLookup;
private final BiFunction<Method, Object[], Object[]> argumentConverter;
private final @Nullable RepositoryMetadata metadata;
private RepositoryComposition(RepositoryFragments fragments, MethodLookup methodLookup,
BiFunction<Method, Object[], Object[]> argumentConverter) {
private RepositoryComposition(@Nullable RepositoryMetadata metadata, RepositoryFragments fragments,
MethodLookup methodLookup, BiFunction<Method, Object[], Object[]> argumentConverter) {
this.metadata = metadata;
this.fragments = fragments;
this.methodLookup = methodLookup;
this.argumentConverter = argumentConverter;
@@ -87,6 +125,24 @@ public class RepositoryComposition {
return EMPTY;
}
/**
* Create an {@link RepositoryComposition} using the provided {@link RepositoryMetadata} to set {@link MethodLookup
* method lookups} depending in the repository type (reactive/imperative).
*
* @return an empty {@link RepositoryComposition}.
* @since 2.4
*/
public static RepositoryComposition fromMetadata(RepositoryMetadata metadata) {
if (metadata.isReactiveRepository()) {
return new RepositoryComposition(metadata, RepositoryFragments.empty(), MethodLookups.forReactiveTypes(metadata),
REACTIVE_ARGS_CONVERTER);
}
return new RepositoryComposition(metadata, RepositoryFragments.empty(), MethodLookups.direct(),
PASSTHRU_ARG_CONVERTER);
}
/**
* Create a {@link RepositoryComposition} for just a single {@code implementation} with {@link MethodLookups#direct())
* method lookup.
@@ -95,7 +151,7 @@ public class RepositoryComposition {
* @return the {@link RepositoryComposition} for a single {@code implementation}.
*/
public static RepositoryComposition just(Object implementation) {
return new RepositoryComposition(RepositoryFragments.just(implementation), MethodLookups.direct(),
return new RepositoryComposition(null, RepositoryFragments.just(implementation), MethodLookups.direct(),
PASSTHRU_ARG_CONVERTER);
}
@@ -118,7 +174,7 @@ public class RepositoryComposition {
* @return the {@link RepositoryComposition} from {@link RepositoryFragment fragments}.
*/
public static RepositoryComposition of(List<RepositoryFragment<?>> fragments) {
return new RepositoryComposition(RepositoryFragments.from(fragments), MethodLookups.direct(),
return new RepositoryComposition(null, RepositoryFragments.from(fragments), MethodLookups.direct(),
PASSTHRU_ARG_CONVERTER);
}
@@ -130,7 +186,7 @@ public class RepositoryComposition {
* @return the {@link RepositoryComposition} from {@link RepositoryFragments fragments}.
*/
public static RepositoryComposition of(RepositoryFragments fragments) {
return new RepositoryComposition(fragments, MethodLookups.direct(), PASSTHRU_ARG_CONVERTER);
return new RepositoryComposition(null, fragments, MethodLookups.direct(), PASSTHRU_ARG_CONVERTER);
}
/**
@@ -141,7 +197,7 @@ public class RepositoryComposition {
* @return the new {@link RepositoryComposition}.
*/
public RepositoryComposition append(RepositoryFragment<?> fragment) {
return new RepositoryComposition(fragments.append(fragment), methodLookup, argumentConverter);
return new RepositoryComposition(metadata, fragments.append(fragment), methodLookup, argumentConverter);
}
/**
@@ -153,7 +209,7 @@ public class RepositoryComposition {
* @return the new {@link RepositoryComposition}.
*/
public RepositoryComposition append(RepositoryFragments fragments) {
return new RepositoryComposition(this.fragments.append(fragments), methodLookup, argumentConverter);
return new RepositoryComposition(metadata, this.fragments.append(fragments), methodLookup, argumentConverter);
}
/**
@@ -163,7 +219,7 @@ public class RepositoryComposition {
* @return the new {@link RepositoryComposition}.
*/
public RepositoryComposition withArgumentConverter(BiFunction<Method, Object[], Object[]> argumentConverter) {
return new RepositoryComposition(fragments, methodLookup, argumentConverter);
return new RepositoryComposition(metadata, fragments, methodLookup, argumentConverter);
}
/**
@@ -173,7 +229,18 @@ public class RepositoryComposition {
* @return the new {@link RepositoryComposition}.
*/
public RepositoryComposition withMethodLookup(MethodLookup methodLookup) {
return new RepositoryComposition(fragments, methodLookup, argumentConverter);
return new RepositoryComposition(metadata, fragments, methodLookup, argumentConverter);
}
/**
* Create a new {@link RepositoryComposition} retaining current configuration and set {@code metadata}.
*
* @param metadata must not be {@literal null}.
* @return new instance of {@link RepositoryComposition}.
* @since 2.4
*/
public RepositoryComposition withMetadata(RepositoryMetadata metadata) {
return new RepositoryComposition(metadata, fragments, methodLookup, argumentConverter);
}
/**
@@ -194,7 +261,7 @@ public class RepositoryComposition {
* @throws Throwable
*/
public Object invoke(Method method, Object... args) throws Throwable {
return invoke(RepositoryInvocationListener.NoOpRepositoryInvocationListener.INSTANCE, method, args);
return invoke(NoOpRepositoryInvocationMulticaster.INSTANCE, method, args);
}
/**
@@ -205,7 +272,7 @@ public class RepositoryComposition {
* @return
* @throws Throwable
*/
Object invoke(RepositoryInvocationListener listener, Method method, Object[] args) throws Throwable {
Object invoke(RepositoryInvocationMulticaster listener, Method method, Object[] args) throws Throwable {
Method methodToCall = getMethod(method);
@@ -215,7 +282,8 @@ public class RepositoryComposition {
ReflectionUtils.makeAccessible(methodToCall);
return fragments.invoke(listener, method, methodToCall, argumentConverter.apply(methodToCall, args));
return fragments.invoke(metadata != null ? metadata.getRepositoryInterface() : method.getDeclaringClass(), listener,
method, methodToCall, argumentConverter.apply(methodToCall, args));
}
/**
@@ -306,6 +374,7 @@ public class RepositoryComposition {
private final List<RepositoryFragment<?>> fragments;
private RepositoryFragments(List<RepositoryFragment<?>> fragments) {
this.fragments = fragments;
}
@@ -421,8 +490,7 @@ public class RepositoryComposition {
*/
@Nullable
public Object invoke(Method invokedMethod, Method methodToCall, Object[] args) throws Throwable {
return invoke(RepositoryInvocationListener.NoOpRepositoryInvocationListener.INSTANCE, invokedMethod, methodToCall,
args);
return invoke(null, NoOpRepositoryInvocationMulticaster.INSTANCE, invokedMethod, methodToCall, args);
}
/**
@@ -438,8 +506,8 @@ public class RepositoryComposition {
* @throws Throwable
*/
@Nullable
Object invoke(RepositoryInvocationListener listener, Method invokedMethod, Method methodToCall, Object[] args)
throws Throwable {
Object invoke(Class<?> repositoryInterface, RepositoryInvocationMulticaster listener, Method invokedMethod,
Method methodToCall, Object[] args) throws Throwable {
RepositoryFragment<?> fragment = fragmentCache.computeIfAbsent(methodToCall, this::findImplementationFragment);
Optional<?> optional = fragment.getImplementation();
@@ -448,14 +516,16 @@ public class RepositoryComposition {
throw new IllegalArgumentException(String.format("No implementation found for method %s", methodToCall));
}
RepositoryMethodInvoker invocationMetadata = invocationMetadataCache.get(invokedMethod);
RepositoryMethodInvoker repositoryMethodInvoker = invocationMetadataCache.get(invokedMethod);
if (invocationMetadata == null) {
invocationMetadata = RepositoryMethodInvoker.forFragmentMethod(invokedMethod, optional.get(), methodToCall);
invocationMetadataCache.put(invokedMethod, invocationMetadata);
if (repositoryMethodInvoker == null) {
repositoryMethodInvoker = RepositoryMethodInvoker.forFragmentMethod(invokedMethod, optional.get(),
methodToCall);
invocationMetadataCache.put(invokedMethod, repositoryMethodInvoker);
}
return invocationMetadata.invoke(listener, args);
return repositoryMethodInvoker.invoke(repositoryInterface, listener, args);
}
private RepositoryFragment<?> findImplementationFragment(Method key) {

View File

@@ -22,14 +22,12 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
import org.springframework.beans.BeanUtils;
@@ -49,6 +47,8 @@ import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.DefaultRepositoryInvocationMulticaster;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.NoOpRepositoryInvocationMulticaster;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.QueryMethod;
@@ -56,8 +56,6 @@ import org.springframework.data.repository.query.QueryMethodEvaluationContextPro
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.util.ClassUtils;
import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.lang.Nullable;
import org.springframework.transaction.interceptor.TransactionalProxy;
@@ -78,39 +76,8 @@ import org.springframework.util.ObjectUtils;
*/
public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, BeanFactoryAware {
private static final BiFunction<Method, Object[], Object[]> REACTIVE_ARGS_CONVERTER = (method, args) -> {
if (ReactiveWrappers.isAvailable()) {
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] converted = new Object[args.length];
for (int i = 0; i < args.length; i++) {
Object value = args[i];
Object convertedArg = value;
Class<?> parameterType = parameterTypes.length > i ? parameterTypes[i] : null;
if (value != null && parameterType != null) {
if (!parameterType.isAssignableFrom(value.getClass())
&& ReactiveWrapperConverters.canConvert(value.getClass(), parameterType)) {
convertedArg = ReactiveWrapperConverters.toWrapper(value, parameterType);
}
}
converted[i] = convertedArg;
}
return converted;
}
return args;
};
final static GenericConversionService CONVERSION_SERVICE = new DefaultConversionService();
private static final Log logger = LogFactory.getLog(RepositoryFactorySupport.class);
private static final Log logger = LogFactory.getLog(RepositoryFactorySupport.class);
static {
QueryExecutionConverters.registerConvertersIn(CONVERSION_SERVICE);
@@ -260,15 +227,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
* @return
*/
private RepositoryComposition getRepositoryComposition(RepositoryMetadata metadata) {
RepositoryComposition composition = RepositoryComposition.empty();
if (metadata.isReactiveRepository()) {
return composition.withMethodLookup(MethodLookups.forReactiveTypes(metadata))
.withArgumentConverter(REACTIVE_ARGS_CONVERTER);
}
return composition.withMethodLookup(MethodLookups.forRepositoryTypes(metadata));
return RepositoryComposition.fromMetadata(metadata);
}
/**
@@ -349,7 +308,8 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
T repository = (T) result.getProxy(classLoader);
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.format("Finished creation of repository instance for {}.", repositoryInterface.getName()));
logger
.debug(LogMessage.format("Finished creation of repository instance for {}.", repositoryInterface.getName()));
}
return repository;
@@ -545,13 +505,14 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
private final RepositoryInformation information;
private final RepositoryComposition composition;
private final List<RepositoryMethodInvocationListener> methodInvocationListeners;
private final RepositoryInvocationMulticaster invocationMulticaster;
public ImplementationMethodExecutionInterceptor(RepositoryInformation information,
RepositoryComposition composition, List<RepositoryMethodInvocationListener> methodInvocationListeners) {
this.information = information;
this.composition = composition;
this.methodInvocationListeners = methodInvocationListeners;
this.invocationMulticaster = methodInvocationListeners.isEmpty() ? NoOpRepositoryInvocationMulticaster.INSTANCE
: new DefaultRepositoryInvocationMulticaster(methodInvocationListeners);
}
/*
@@ -564,23 +525,15 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
Method method = invocation.getMethod();
Object[] arguments = invocation.getArguments();
RepositoryInvocationListener invocationListener = getInvocationListener();
try {
return composition.invoke(invocationListener, method, arguments);
return composition.invoke(invocationMulticaster, method, arguments);
} catch (Exception e) {
ClassUtils.unwrapReflectionException(e);
}
throw new IllegalStateException("Should not occur!");
}
private RepositoryInvocationListener getInvocationListener() {
return methodInvocationListeners.isEmpty()
? RepositoryInvocationListener.NoOpRepositoryInvocationListener.INSTANCE
: new RepositoryInvocationListener.RepositoryInvocationMulticaster(information.getRepositoryInterface(),
methodInvocationListeners);
}
}
/**

View File

@@ -1,101 +0,0 @@
/*
* Copyright 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.core.support;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented by repository method listeners. Listeners are notified with the called {@link Method},
* arguments and its outcome.
*
* @author Mark Paluch
* @since 2.4
*/
interface RepositoryInvocationListener {
/**
* The repository method invocation to be handled after calling it.
*
* @param method
* @param args
* @param result
* @param exception
*/
void afterInvocation(Method method, Object[] args, @Nullable Object result, @Nullable Throwable exception);
/**
* {@link RepositoryInvocationListener} that does nothing upon invocation.
*
* @author Mark Paluch
*/
enum NoOpRepositoryInvocationListener implements RepositoryInvocationListener {
INSTANCE;
@Override
public void afterInvocation(Method method, Object[] args, @Nullable Object result, @Nullable Throwable exception) {
}
}
/**
* {@link RepositoryInvocationListener} implementation that notifies {@link RepositoryMethodInvocationListener} upon
* {@link #afterInvocation(Method, Object[], Object, Throwable)}.
*
* @author Mark Paluch
*/
class RepositoryInvocationMulticaster implements RepositoryInvocationListener {
private final Class<?> repositoryInterface;
private final List<RepositoryMethodInvocationListener> methodInvocationListeners;
private final long startNs;
RepositoryInvocationMulticaster(Class<?> repositoryInterface,
List<RepositoryMethodInvocationListener> methodInvocationListeners) {
this.repositoryInterface = repositoryInterface;
this.methodInvocationListeners = methodInvocationListeners;
this.startNs = System.nanoTime();
}
@Override
public void afterInvocation(Method method, Object[] args, @Nullable Object result, @Nullable Throwable exception) {
long durationNs = getDuration(System.nanoTime());
RepositoryMethodInvocationListener.Invocation invocation = new RepositoryMethodInvocationListener.Invocation(
durationNs, repositoryInterface, method, result,
exception instanceof InvocationTargetException ? exception.getCause() : exception);
for (RepositoryMethodInvocationListener methodInvocationListener : methodInvocationListeners) {
methodInvocationListener.afterInvocation(invocation);
}
}
private long getDuration(long endNs) {
if (endNs > startNs) {
return endNs - startNs;
}
// end time overflow
return (Long.MAX_VALUE - startNs) + endNs;
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 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.core.support;
import java.lang.reflect.Method;
import java.util.List;
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocation;
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocationResult;
/**
* Interface to be implemented by repository method listeners. Listeners are notified with the called {@link Method},
* arguments and its outcome.
*
* @author Mark Paluch
* @since 2.4
*/
interface RepositoryInvocationMulticaster {
/**
* The repository method invocation to be handled after calling it.
*
* @param method
* @param args
* @param result
*/
void notifyListeners(Method method, Object[] args, RepositoryMethodInvocation result);
/**
* {@link RepositoryInvocationMulticaster} that does nothing upon invocation.
*
* @author Mark Paluch
*/
enum NoOpRepositoryInvocationMulticaster implements RepositoryInvocationMulticaster {
INSTANCE;
@Override
public void notifyListeners(Method method, Object[] args, RepositoryMethodInvocation result) {
}
}
/**
* {@link RepositoryInvocationMulticaster} implementation that notifies {@link RepositoryMethodInvocationListener}
* upon {@link #notifyListeners(Method, Object[], RepositoryMethodInvocationResult)}.
*
* @author Mark Paluch
*/
class DefaultRepositoryInvocationMulticaster implements RepositoryInvocationMulticaster {
private final List<RepositoryMethodInvocationListener> methodInvocationListeners;
DefaultRepositoryInvocationMulticaster(List<RepositoryMethodInvocationListener> methodInvocationListeners) {
this.methodInvocationListeners = methodInvocationListeners;
}
@Override
public void notifyListeners(Method method, Object[] args, RepositoryMethodInvocation result) {
for (RepositoryMethodInvocationListener methodInvocationListener : methodInvocationListeners) {
methodInvocationListener.afterInvocation(result);
}
}
}
}

View File

@@ -16,17 +16,20 @@
package org.springframework.data.repository.core.support;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Interface to be implemented by listeners that want to be notified upon repository method invocation. Listeners are
* notified with an {@link Invocation} object that describes which repository method was invoked along with invocation
* arguments and the call duration.
* notified with an {@link RepositoryMethodInvocation} object that describes which repository method was invoked along
* with invocation arguments and the call duration.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.4
*/
public interface RepositoryMethodInvocationListener {
@@ -34,35 +37,33 @@ public interface RepositoryMethodInvocationListener {
/**
* Handle the invocation event. This method is called after the execution has finished.
*
* @param invocation the invocation to respond to.
* @param repositoryMethodInvocation the invocation to respond to.
*/
void afterInvocation(Invocation invocation);
void afterInvocation(RepositoryMethodInvocation repositoryMethodInvocation);
/**
* Value object capturing the actual invocation.
*/
class Invocation {
class RepositoryMethodInvocation {
private final long durationNs;
private final Class<?> repositoryInterface;
private final Method method;
private final Object result;
private final Throwable exception;
private final RepositoryMethodInvocationResult result;
/**
* @param durationNs the duration in {@link TimeUnit#NANOSECONDS}.
* @param repositoryInterface the repository interface that was used to call {@link Method}.
* @param method the actual method that was called.
* @param result the outcome of the method call. May be {@literal null}.
* @param exception may be {@literal null}.
* @param result the outcome of the invocation. Must not be {@literal null}.
* @param durationNs the duration in {@link TimeUnit#NANOSECONDS}.
*/
public Invocation(long durationNs, Class<?> repositoryInterface, Method method, @Nullable Object result,
@Nullable Throwable exception) {
public RepositoryMethodInvocation(Class<?> repositoryInterface, Method method,
RepositoryMethodInvocationResult result, long durationNs) {
this.durationNs = durationNs;
this.repositoryInterface = repositoryInterface;
this.method = method;
this.result = result;
this.exception = exception;
}
public long getDuration(TimeUnit timeUnit) {
@@ -81,13 +82,29 @@ public interface RepositoryMethodInvocationListener {
}
@Nullable
public Object getResult() {
public RepositoryMethodInvocationResult getResult() {
return result;
}
@Override
public String toString() {
return String.format("Invocation %s.%s(%s): %s ms - %s", repositoryInterface.getSimpleName(), method.getName(),
StringUtils.arrayToCommaDelimitedString(
Arrays.stream(method.getParameterTypes()).map(Class::getSimpleName).toArray()),
getDuration(TimeUnit.MILLISECONDS), result.getState());
}
}
interface RepositoryMethodInvocationResult {
State getState();
@Nullable
public Throwable getException() {
return exception;
Throwable getError();
public enum State {
SUCCESS, ERROR, CANCELED, RUNNING
}
}
}

View File

@@ -18,13 +18,18 @@ package org.springframework.data.repository.core.support;
import kotlin.coroutines.Continuation;
import kotlin.reflect.KFunction;
import kotlinx.coroutines.reactive.AwaitKt;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.stream.Stream;
import org.reactivestreams.Publisher;
import org.springframework.core.KotlinDetector;
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocation;
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocationResult;
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocationResult.State;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.repository.util.ReactiveWrappers;
@@ -37,6 +42,7 @@ import org.springframework.lang.Nullable;
* implementation.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.4
* @see #forFragmentMethod(Method, Object, Method)
* @see #forRepositoryQuery(Method, RepositoryQuery)
@@ -90,7 +96,7 @@ abstract class RepositoryMethodInvoker {
* Return whether the {@link Method declared method} can be adapted by calling {@link Method baseClassMethod}.
*
* @param declaredMethod the declared repository method from the repository interface.
* @param baseMethod the base method to call on fragment {@code instance}.
* @param baseClassMethod the base method to call on fragment {@code instance}.
* @return
*/
public static boolean canInvoke(Method declaredMethod, Method baseClassMethod) {
@@ -101,14 +107,16 @@ abstract class RepositoryMethodInvoker {
/**
* Invoke the repository method and return its value.
*
* @param listener listener to notify about the call outcome.
* @param multicaster listener to notify about the call outcome.
* @param args invocation arguments.
* @return
* @throws Exception
*/
@Nullable
public Object invoke(RepositoryInvocationListener listener, Object[] args) throws Exception {
return shouldAdaptReactiveToSuspended() ? doInvokeReactiveToSuspended(listener, args) : doInvoke(listener, args);
public Object invoke(Class<?> repositoryInterface, RepositoryInvocationMulticaster multicaster, Object[] args)
throws Exception {
return shouldAdaptReactiveToSuspended() ? doInvokeReactiveToSuspended(repositoryInterface, multicaster, args)
: doInvoke(repositoryInterface, multicaster, args);
}
protected boolean shouldAdaptReactiveToSuspended() {
@@ -116,33 +124,38 @@ abstract class RepositoryMethodInvoker {
}
@Nullable
private Object doInvoke(RepositoryInvocationListener listener, Object[] args) throws Exception {
private Object doInvoke(Class<?> repositoryInterface, RepositoryInvocationMulticaster multicaster, Object[] args)
throws Exception {
RepositoryMethodInvocationCaptor invocationResultCaptor = RepositoryMethodInvocationCaptor
.captureInvocationOn(repositoryInterface);
try {
Object result = invokable.invoke(args);
if (result != null && ReactiveWrappers.supports(result.getClass())) {
return ReactiveWrapperConverters.doOnError(
ReactiveWrapperConverters.doOnSuccess(result, () -> listener.afterInvocation(method, args, result, null)),
ex -> listener.afterInvocation(method, args, result, ex));
return inferReactiveInvocationCallbacks(repositoryInterface, multicaster, args, result);
}
if (result instanceof Stream) {
return ((Stream<?>) result).onClose(() -> listener.afterInvocation(method, args, result, null));
return ((Stream<?>) result).onClose(
() -> multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.success())));
}
listener.afterInvocation(method, args, result, null);
multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.success()));
return result;
} catch (Exception e) {
listener.afterInvocation(method, args, null, e);
multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.error(e)));
throw e;
}
}
@Nullable
@SuppressWarnings({ "unchecked", "ConstantConditions" })
private Object doInvokeReactiveToSuspended(RepositoryInvocationListener listener, Object[] args) throws Exception {
private Object doInvokeReactiveToSuspended(Class<?> repositoryInterface, RepositoryInvocationMulticaster multicaster,
Object[] args) throws Exception {
/*
* Kotlin suspended functions are invoked with a synthetic Continuation parameter that keeps track of the Coroutine context.
@@ -151,28 +164,67 @@ abstract class RepositoryMethodInvoker {
*/
Continuation<Object> continuation = (Continuation) args[args.length - 1];
args[args.length - 1] = null;
RepositoryMethodInvocationCaptor invocationResultCaptor = RepositoryMethodInvocationCaptor
.captureInvocationOn(repositoryInterface);
try {
Object result = invokable.invoke(args);
Publisher<?> result = inferReactiveInvocationCallbacks(repositoryInterface, multicaster, args,
invokable.invoke(args));
if (returnsReactiveType) {
listener.afterInvocation(method, args, result, null);
return ReactiveWrapperConverters.toWrapper(result, returnedType);
}
Publisher<?> publisher = result instanceof Publisher ? (Publisher<?>) result
: ReactiveWrapperConverters.toWrapper(result, Publisher.class);
publisher = ReactiveWrapperConverters.doOnError(
ReactiveWrapperConverters.doOnSuccess(publisher, () -> listener.afterInvocation(method, args, result, null)),
ex -> listener.afterInvocation(method, args, result, ex));
return AwaitKt.awaitFirstOrNull(publisher, continuation);
return AwaitKt.awaitFirstOrNull(result, continuation);
} catch (Exception e) {
listener.afterInvocation(method, args, null, e);
multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.error(e)));
throw e;
}
}
private RepositoryMethodInvocation computeInvocationResult(RepositoryMethodInvocationCaptor captured) {
return new RepositoryMethodInvocation(captured.getRepositoryInterface(), method, captured.getCapturedResult(),
captured.getDuration());
}
private Publisher<Object> inferReactiveInvocationCallbacks(Class<?> repositoryInterface,
RepositoryInvocationMulticaster multicaster, Object[] args, Object result) {
if (result instanceof Mono) {
return Mono.usingWhen(
Mono.fromSupplier(() -> RepositoryMethodInvocationCaptor.captureInvocationOn(repositoryInterface)), it -> {
it.trackStart();
return ReactiveWrapperConverters.toWrapper(result, Mono.class);
}, it -> {
multicaster.notifyListeners(method, args, computeInvocationResult(it.success()));
return Mono.empty();
}, (it, e) -> {
multicaster.notifyListeners(method, args, computeInvocationResult(it.error(e)));
return Mono.empty();
}, it -> {
multicaster.notifyListeners(method, args, computeInvocationResult(it.canceled()));
return Mono.empty();
});
}
return Flux.usingWhen(
Mono.fromSupplier(() -> RepositoryMethodInvocationCaptor.captureInvocationOn(repositoryInterface)), it -> {
it.trackStart();
return result instanceof Publisher ? (Publisher<?>) result
: ReactiveWrapperConverters.toWrapper(result, Publisher.class);
}, it -> {
multicaster.notifyListeners(method, args, computeInvocationResult(it.success()));
return Mono.empty();
}, (it, e) -> {
multicaster.notifyListeners(method, args, computeInvocationResult(it.error(e)));
return Mono.empty();
}, it -> {
multicaster.notifyListeners(method, args, computeInvocationResult(it.canceled()));
return Mono.empty();
});
}
interface Invokable {
@Nullable
@@ -301,4 +353,80 @@ abstract class RepositoryMethodInvoker {
}
}
}
private static class RepositoryMethodInvocationCaptor {
private final Class<?> repositoryInterface;
private long startTime;
private @Nullable Long endTime;
private final State state;
private final @Nullable Throwable error;
protected RepositoryMethodInvocationCaptor(Class<?> repositoryInterface, long startTime, Long endTime, State state,
@Nullable Throwable exception) {
this.repositoryInterface = repositoryInterface;
this.startTime = startTime;
this.endTime = endTime;
this.state = state;
this.error = exception instanceof InvocationTargetException ? exception.getCause() : exception;
}
public static RepositoryMethodInvocationCaptor captureInvocationOn(Class<?> repositoryInterface) {
return new RepositoryMethodInvocationCaptor(repositoryInterface, System.nanoTime(), null, State.RUNNING, null);
}
public RepositoryMethodInvocationCaptor error(Throwable exception) {
return new RepositoryMethodInvocationCaptor(repositoryInterface, startTime, System.nanoTime(), State.ERROR,
exception);
}
public RepositoryMethodInvocationCaptor success() {
return new RepositoryMethodInvocationCaptor(repositoryInterface, startTime, System.nanoTime(), State.SUCCESS,
null);
}
public RepositoryMethodInvocationCaptor canceled() {
return new RepositoryMethodInvocationCaptor(repositoryInterface, startTime, System.nanoTime(), State.CANCELED,
null);
}
Class<?> getRepositoryInterface() {
return repositoryInterface;
}
void trackStart() {
startTime = System.nanoTime();
}
public State getState() {
return state;
}
@Nullable
public Throwable getError() {
return error;
}
long getDuration() {
return (endTime != null ? endTime : System.nanoTime()) - startTime;
}
RepositoryMethodInvocationResult getCapturedResult() {
return new RepositoryMethodInvocationResult() {
@Override
public State getState() {
return RepositoryMethodInvocationCaptor.this.getState();
}
@Nullable
@Override
public Throwable getError() {
return RepositoryMethodInvocationCaptor.this.getError();
}
};
}
}
}

View File

@@ -27,13 +27,12 @@ import rx.Single;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.Optional;
import java.util.function.Function;
import javax.annotation.Nonnull;
import org.reactivestreams.Publisher;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.convert.ConversionService;
@@ -59,6 +58,7 @@ import org.springframework.util.ClassUtils;
*
* @author Mark Paluch
* @author Oliver Gierke
* @author Christoph Strobl
* @since 2.0
* @see ReactiveWrappers
* @see ReactiveAdapterRegistry
@@ -183,55 +183,15 @@ public abstract class ReactiveWrapperConverters {
Assert.notNull(reactiveObject, "Reactive source object must not be null!");
Assert.notNull(converter, "Converter must not be null!");
return REACTIVE_WRAPPERS.stream()//
.filter(it -> ClassUtils.isAssignable(it.getWrapperClass(), reactiveObject.getClass()))//
.findFirst()//
return getFirst(reactiveObject)//
.map(it -> (T) it.map(reactiveObject, converter))//
.orElseThrow(() -> new IllegalStateException(String.format("Cannot apply converter to %s", reactiveObject)));
}
/**
* Apply a {@link Runnable} when the reactive type emits a completion signal.
*
* @param reactiveObject must not be {@literal null}.
* @param onSuccess must not be {@literal null}.
* @return
* @since 2.4
*/
@SuppressWarnings("unchecked")
public static <T> T doOnSuccess(Object reactiveObject, Runnable onSuccess) {
Assert.notNull(reactiveObject, "Reactive source object must not be null!");
Assert.notNull(onSuccess, "onSuccess callback must not be null!");
private static Optional<ReactiveTypeWrapper<?>> getFirst(Object reactiveObject) {
return REACTIVE_WRAPPERS.stream()//
.filter(it -> ClassUtils.isAssignable(it.getWrapperClass(), reactiveObject.getClass()))//
.findFirst()//
.map(it -> (T) it.doOnSuccess(reactiveObject, onSuccess))//
.orElseThrow(
() -> new IllegalStateException(String.format("Cannot apply onSuccess callback to %s", reactiveObject)));
}
/**
* Apply a {@link Consumer} when the reactive type emits an error signal.
*
* @param reactiveObject must not be {@literal null}.
* @param onError must not be {@literal null}.
* @return
* @since 2.4
*/
@SuppressWarnings("unchecked")
public static <T> T doOnError(Object reactiveObject, Consumer<? super Throwable> onError) {
Assert.notNull(reactiveObject, "Reactive source object must not be null!");
Assert.notNull(onError, "onError callback must not be null!");
return REACTIVE_WRAPPERS.stream()//
.filter(it -> ClassUtils.isAssignable(it.getWrapperClass(), reactiveObject.getClass()))//
.findFirst()//
.map(it -> (T) it.doOnError(reactiveObject, onError))//
.orElseThrow(
() -> new IllegalStateException(String.format("Cannot apply onError callback to %s", reactiveObject)));
.findFirst();
}
/**
@@ -275,25 +235,6 @@ public abstract class ReactiveWrapperConverters {
*/
Object map(Object wrapper, Function<Object, Object> function);
/**
* Apply a {@link Runnable} when the reactive type emits a completion signal.
*
* @param wrapper the reactive type, must not be {@literal null}.
* @param onSuccess the signal callback, must not be {@literal null}.
* @return the reactive type with {@code onSuccess} attached.
* @since 2.4
*/
Object doOnSuccess(Object wrapper, Runnable onSuccess);
/**
* Apply a {@link Consumer} when the reactive type emits an error signal.
*
* @param wrapper the reactive type, must not be {@literal null}.
* @param onError the error consumer, must not be {@literal null}.
* @return the reactive type with {@code onError} attached.
* @since 2.4
*/
Object doOnError(Object wrapper, Consumer<? super Throwable> onError);
}
/**
@@ -312,16 +253,6 @@ public abstract class ReactiveWrapperConverters {
public Mono<?> map(Object wrapper, Function<Object, Object> function) {
return ((Mono<?>) wrapper).map(function::apply);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return ((Mono<?>) wrapper).doOnSuccess(o -> onSuccess.run());
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return ((Mono<?>) wrapper).doOnError(onError);
}
}
/**
@@ -339,16 +270,6 @@ public abstract class ReactiveWrapperConverters {
public Flux<?> map(Object wrapper, Function<Object, Object> function) {
return ((Flux<?>) wrapper).map(function);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return ((Flux<?>) wrapper).doOnComplete(onSuccess);
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return ((Flux<?>) wrapper).doOnError(onError);
}
}
/**
@@ -368,26 +289,6 @@ public abstract class ReactiveWrapperConverters {
public Flow<?> map(Object wrapper, Function<Object, Object> function) {
return FlowKt.map((Flow<?>) wrapper, (o, continuation) -> function.apply(o));
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return FlowKt.onCompletion((Flow<?>) wrapper, (collector, ex, continuation) -> {
if (ex == null) {
onSuccess.run();
}
return collector;
});
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return FlowKt.onCompletion((Flow<?>) wrapper, (collector, ex, continuation) -> {
if (ex != null) {
onError.accept(ex);
}
return collector;
});
}
}
/**
@@ -415,34 +316,6 @@ public abstract class ReactiveWrapperConverters {
return FluxWrapper.INSTANCE.map(Flux.from((Publisher<?>) wrapper), function);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
if (wrapper instanceof Mono) {
return MonoWrapper.INSTANCE.doOnSuccess(wrapper, onSuccess);
}
if (wrapper instanceof Flux) {
return FluxWrapper.INSTANCE.doOnSuccess(wrapper, onSuccess);
}
return FluxWrapper.INSTANCE.doOnSuccess(Flux.from((Publisher<?>) wrapper), onSuccess);
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
if (wrapper instanceof Mono) {
return MonoWrapper.INSTANCE.doOnError(wrapper, onError);
}
if (wrapper instanceof Flux) {
return FluxWrapper.INSTANCE.doOnError(wrapper, onError);
}
return FluxWrapper.INSTANCE.doOnError(Flux.from((Publisher<?>) wrapper), onError);
}
}
// -------------------------------------------------------------------------
@@ -465,16 +338,6 @@ public abstract class ReactiveWrapperConverters {
public Single<?> map(Object wrapper, Function<Object, Object> function) {
return ((Single<?>) wrapper).map(function::apply);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return ((Single<?>) wrapper).doOnSuccess(o -> onSuccess.run());
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return ((Single<?>) wrapper).doOnError(onError::accept);
}
}
/**
@@ -493,16 +356,6 @@ public abstract class ReactiveWrapperConverters {
public Observable<?> map(Object wrapper, Function<Object, Object> function) {
return ((Observable<?>) wrapper).map(function::apply);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return ((Observable<?>) wrapper).doOnCompleted(onSuccess::run);
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return ((Observable<?>) wrapper).doOnError(onError::accept);
}
}
// -------------------------------------------------------------------------
@@ -525,16 +378,6 @@ public abstract class ReactiveWrapperConverters {
public io.reactivex.Single<?> map(Object wrapper, Function<Object, Object> function) {
return ((io.reactivex.Single<?>) wrapper).map(function::apply);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return ((io.reactivex.Single<?>) wrapper).doOnSuccess(o -> onSuccess.run());
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return ((io.reactivex.Single<?>) wrapper).doOnError(onError::accept);
}
}
/**
@@ -553,16 +396,6 @@ public abstract class ReactiveWrapperConverters {
public io.reactivex.Maybe<?> map(Object wrapper, Function<Object, Object> function) {
return ((io.reactivex.Maybe<?>) wrapper).map(function::apply);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return ((io.reactivex.Maybe<?>) wrapper).doOnSuccess(o -> onSuccess.run());
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return ((io.reactivex.Maybe<?>) wrapper).doOnError(onError::accept);
}
}
/**
@@ -581,16 +414,6 @@ public abstract class ReactiveWrapperConverters {
public io.reactivex.Observable<?> map(Object wrapper, Function<Object, Object> function) {
return ((io.reactivex.Observable<?>) wrapper).map(function::apply);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return ((io.reactivex.Observable<?>) wrapper).doOnComplete(onSuccess::run);
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return ((io.reactivex.Observable<?>) wrapper).doOnError(onError::accept);
}
}
/**
@@ -609,16 +432,6 @@ public abstract class ReactiveWrapperConverters {
public io.reactivex.Flowable<?> map(Object wrapper, Function<Object, Object> function) {
return ((io.reactivex.Flowable<?>) wrapper).map(function::apply);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return ((io.reactivex.Flowable<?>) wrapper).doOnComplete(onSuccess::run);
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return ((io.reactivex.Flowable<?>) wrapper).doOnError(onError::accept);
}
}
// -------------------------------------------------------------------------
@@ -641,16 +454,6 @@ public abstract class ReactiveWrapperConverters {
public io.reactivex.rxjava3.core.Single<?> map(Object wrapper, Function<Object, Object> function) {
return ((io.reactivex.rxjava3.core.Single<?>) wrapper).map(function::apply);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return ((io.reactivex.rxjava3.core.Single<?>) wrapper).doOnSuccess(o -> onSuccess.run());
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return ((io.reactivex.rxjava3.core.Single<?>) wrapper).doOnError(onError::accept);
}
}
/**
@@ -669,16 +472,6 @@ public abstract class ReactiveWrapperConverters {
public io.reactivex.rxjava3.core.Maybe<?> map(Object wrapper, Function<Object, Object> function) {
return ((io.reactivex.rxjava3.core.Maybe<?>) wrapper).map(function::apply);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return ((io.reactivex.rxjava3.core.Maybe<?>) wrapper).doOnSuccess(o -> onSuccess.run());
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return ((io.reactivex.rxjava3.core.Maybe<?>) wrapper).doOnError(onError::accept);
}
}
/**
@@ -697,16 +490,6 @@ public abstract class ReactiveWrapperConverters {
public io.reactivex.rxjava3.core.Observable<?> map(Object wrapper, Function<Object, Object> function) {
return ((io.reactivex.rxjava3.core.Observable<?>) wrapper).map(function::apply);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return ((io.reactivex.rxjava3.core.Observable<?>) wrapper).doOnComplete(onSuccess::run);
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return ((io.reactivex.rxjava3.core.Observable<?>) wrapper).doOnError(onError::accept);
}
}
/**
@@ -725,16 +508,6 @@ public abstract class ReactiveWrapperConverters {
public io.reactivex.rxjava3.core.Flowable<?> map(Object wrapper, Function<Object, Object> function) {
return ((io.reactivex.rxjava3.core.Flowable<?>) wrapper).map(function::apply);
}
@Override
public Object doOnSuccess(Object wrapper, Runnable onSuccess) {
return ((io.reactivex.rxjava3.core.Flowable<?>) wrapper).doOnComplete(onSuccess::run);
}
@Override
public Object doOnError(Object wrapper, Consumer<? super Throwable> onError) {
return ((io.reactivex.rxjava3.core.Flowable<?>) wrapper).doOnError(onError::accept);
}
}
// -------------------------------------------------------------------------
@@ -799,8 +572,7 @@ public abstract class ReactiveWrapperConverters {
/**
* A {@link ConverterFactory} that adapts between reactive types using {@link ReactiveAdapterRegistry}.
*/
private enum ReactiveAdapterConverterFactory
implements ConverterFactory<Object, Object>, ConditionalConverter {
private enum ReactiveAdapterConverterFactory implements ConverterFactory<Object, Object>, ConditionalConverter {
INSTANCE;