/* * Copyright (c) 2010-2016. Axon Framework * * 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.axonframework.commandhandling.gateway; import org.axonframework.commandhandling.CommandBus; import org.axonframework.commandhandling.CommandCallback; import org.axonframework.commandhandling.CommandExecutionException; import org.axonframework.commandhandling.CommandMessage; import org.axonframework.commandhandling.callbacks.FutureCallback; import org.axonframework.common.Assert; import org.axonframework.common.ReflectionUtils; import org.axonframework.common.annotation.AnnotationUtils; import org.axonframework.messaging.MessageDispatchInterceptor; import org.axonframework.messaging.annotation.MetaDataValue; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; import java.lang.reflect.Proxy; import java.util.*; import java.util.concurrent.*; import static java.util.Arrays.asList; import static org.axonframework.commandhandling.GenericCommandMessage.asCommandMessage; /** * Factory that creates Gateway implementations from custom interface definitions. The behavior of the method is defined * by the parameters, declared exceptions and return type of the method. * <p/> * <em>Supported parameter types:</em><ul> <li>The first parameter of the method is considered the payload of the * message. If the first parameter is a Message itself, a new message is created using the payload and metadata of the * message passed as parameter.</li> <li>Parameters that are annotated with {@link MetaDataValue @MetaDataValue} will * cause the parameter values to be added as meta data values to the outgoing message.</li> <li>If the last two * parameters are of type {@link Long long} and {@link TimeUnit}, they are considered to represent the timeout for the * command. The method will block for as long as the command requires to execute, or until the timeout expires.</li> * </ul> * <p/> * <em>Effect of return values</em><ul> <li>{@code void} return types are always allowed. Unless another parameter makes * the method blocking, void methods are non-blocking by default.</li> <li>Declaring a {@link Future} return type will * always result in a non-blocking operation. A future is returned that allows you to retrieve the execution's result at * your own convenience. Note that declared exceptions and timeouts are ignored.</li> <li>Any other return type will * cause the dispatch to block (optionally with timeout) until a result is available</li> </ul> * <p/> * <em>Effect of declared exceptions</em> <ul> <li>Any checked exception declared on the method will cause it to block * (optionally with timeout). If the command results in a declared checked exception, that exception is thrown from the * method.</li> <li>Declaring a {@link TimeoutException} will throw that exception when a configured timeout expires. If * no such exception is declared, but a timeout is configured, the method will return {@code null}.</li> <li>Declaring * an {@link InterruptedException} will throw that exception when a thread blocked while waiting for a response is * interrupted. Not declaring the exception will have the method return {@code null} when a blocked thread is * interrupted. Note that when no InterruptedException is declared, the interrupt flag is set back on the interrupted * thread</li> </ul> * <p/> * <em>Effect of unchecked exceptions</em> <ul> <li>Any unchecked exception thrown during command handling will cause it * to block. If the method is blocking (see below) the unchecked exception will be thrown from the method</li> </ul> * <p/> * Finally, the {@link Timeout @Timeout} annotation can be used to define a timeout on a method. This will always cause * a method invocation to block until a response is available, or the timeout expires. * <p/> * Any method will be blocking if: <ul> <li>It declares a return type other than {@code void} or {@code Future}, or</li> * <li>It declares an exception, or</li> <li>The last two parameters are of type {@link TimeUnit} and {@link Long long}, * or</li> <li>The method is annotated with {@link Timeout @Timeout}</li> </ul> In other cases, the method is * non-blocking and will return immediately after dispatching a command. * <p/> * This factory is thread safe once configured, and so are the gateways it creates. * * @author Allard Buijze * @since 2.0 */ public class CommandGatewayFactory { private final CommandBus commandBus; private final RetryScheduler retryScheduler; private final List<MessageDispatchInterceptor<? super CommandMessage<?>>> dispatchInterceptors; private final List<CommandCallback<?, ?>> commandCallbacks; /** * Initialize the factory sending Commands to the given {@code commandBus}, optionally intercepting them with * given {@code dispatchInterceptors}. * <p/> * Note that the given {@code dispatchInterceptors} are applied only on commands sent through gateways that * have been created using this factory. * * @param commandBus The CommandBus on which to dispatch the Command Messages * @param dispatchInterceptors The interceptors to invoke before dispatching commands to the Command Bus */ @SafeVarargs public CommandGatewayFactory(CommandBus commandBus, MessageDispatchInterceptor<CommandMessage<?>>... dispatchInterceptors) { this(commandBus, null, dispatchInterceptors); } /** * Initialize the factory sending Commands to the given {@code commandBus}, optionally intercepting them with * given {@code dispatchInterceptors}. The given {@code retryScheduler} will reschedule commands for * dispatching if a previous attempt resulted in an exception. * <p/> * Note that the given {@code dispatchInterceptors} are applied only on commands sent through gateways that * have been created using this factory. * * @param commandBus The CommandBus on which to dispatch the Command Messages * @param retryScheduler The scheduler that will decide whether to reschedule commands, may be {@code * null} to report failures without rescheduling * @param messageDispatchInterceptors The interceptors to invoke before dispatching commands to the Command Bus */ @SafeVarargs public CommandGatewayFactory(CommandBus commandBus, RetryScheduler retryScheduler, MessageDispatchInterceptor<CommandMessage<?>>... messageDispatchInterceptors) { this(commandBus, retryScheduler, asList(messageDispatchInterceptors)); } /** * Initialize the factory sending Commands to the given {@code commandBus}, optionally intercepting them with * given {@code dispatchInterceptors}. The given {@code retryScheduler} will reschedule commands for * dispatching if a previous attempt resulted in an exception. * <p/> * Note that the given {@code dispatchInterceptors} are applied only on commands sent through gateways that * have been created using this factory. * * @param commandBus The CommandBus on which to dispatch the Command Messages * @param retryScheduler The scheduler that will decide whether to reschedule commands, may be {@code * null} to report failures without rescheduling * @param messageDispatchInterceptors The interceptors to invoke before dispatching commands to the Command Bus */ public CommandGatewayFactory(CommandBus commandBus, RetryScheduler retryScheduler, List<MessageDispatchInterceptor<CommandMessage<?>>> messageDispatchInterceptors) { Assert.notNull(commandBus, () -> "commandBus may not be null"); this.retryScheduler = retryScheduler; this.commandBus = commandBus; if (messageDispatchInterceptors != null && !messageDispatchInterceptors.isEmpty()) { this.dispatchInterceptors = new CopyOnWriteArrayList<>(messageDispatchInterceptors); } else { this.dispatchInterceptors = new CopyOnWriteArrayList<>(); } this.commandCallbacks = new CopyOnWriteArrayList<>(); } /** * Creates a gateway instance for the given {@code gatewayInterface}. The returned instance is a Proxy that * implements that interface. * * @param gatewayInterface The interface declaring the gateway methods * @param <T> The interface declaring the gateway methods * @return A Proxy implementation implementing the given interface */ @SuppressWarnings("unchecked") public <T> T createGateway(Class<T> gatewayInterface) { Map<Method, InvocationHandler> dispatchers = new HashMap<>(); for (Method gatewayMethod : gatewayInterface.getMethods()) { MetaDataExtractor[] extractors = extractMetaData(gatewayMethod.getParameters()); final Class<?>[] arguments = gatewayMethod.getParameterTypes(); InvocationHandler dispatcher = new DispatchOnInvocationHandler(commandBus, retryScheduler, dispatchInterceptors, extractors, commandCallbacks, true); if (!Arrays.asList(CompletableFuture.class, Future.class, CompletionStage.class) .contains(gatewayMethod.getReturnType())) { if (arguments.length >= 3 && TimeUnit.class.isAssignableFrom(arguments[arguments.length - 1]) && (Long.TYPE.isAssignableFrom(arguments[arguments.length - 2]) || Integer.TYPE.isAssignableFrom(arguments[arguments.length - 2]))) { dispatcher = wrapToReturnWithTimeoutInArguments(dispatcher, arguments.length - 2, arguments.length - 1); } else { Timeout timeout = gatewayMethod.getAnnotation(Timeout.class); timeout = resolveTimeout(gatewayMethod, timeout); if (timeout != null) { dispatcher = wrapToReturnWithFixedTimeout(dispatcher, timeout.value(), timeout.unit()); } else if (!Void.TYPE.equals(gatewayMethod.getReturnType()) || gatewayMethod.getExceptionTypes().length > 0) { dispatcher = wrapToWaitForResult(dispatcher); } else if (commandCallbacks.isEmpty() && !hasCallbackParameters(gatewayMethod)) { // switch to fire-and-forget mode dispatcher = wrapToFireAndForget( new DispatchOnInvocationHandler(commandBus, retryScheduler, dispatchInterceptors, extractors, commandCallbacks, false)); } } Class<?>[] declaredExceptions = gatewayMethod.getExceptionTypes(); if (!contains(declaredExceptions, TimeoutException.class)) { dispatcher = wrapToReturnNullOnTimeout(dispatcher); } if (!contains(declaredExceptions, InterruptedException.class)) { dispatcher = wrapToReturnNullOnInterrupted(dispatcher); } dispatcher = wrapUndeclaredExceptions(dispatcher, declaredExceptions); } dispatchers.put(gatewayMethod, dispatcher); } return gatewayInterface .cast(Proxy.newProxyInstance(gatewayInterface.getClassLoader(), new Class[]{gatewayInterface}, new GatewayInvocationHandler(dispatchers, commandBus, retryScheduler, dispatchInterceptors))); } private Timeout resolveTimeout(Method gatewayMethod, Timeout timeout) { return timeout == null ? gatewayMethod.getDeclaringClass().getAnnotation(Timeout.class) : timeout; } private boolean hasCallbackParameters(Method gatewayMethod) { for (Class<?> parameter : gatewayMethod.getParameterTypes()) { if (CommandCallback.class.isAssignableFrom(parameter)) { return true; } } return false; } /** * Wraps the given {@code delegate} in an InvocationHandler that wraps exceptions not declared on the method * in a {@link org.axonframework.commandhandling.CommandExecutionException}. * * @param delegate The delegate to invoke that potentially throws exceptions * @param declaredExceptions The exceptions declared on the method signature * @param <R> The response type of the command handler * @return an InvocationHandler that wraps undeclared exceptions in a {@code CommandExecutionException} */ protected <R> InvocationHandler<R> wrapUndeclaredExceptions(final InvocationHandler<R> delegate, final Class<?>[] declaredExceptions) { return new WrapNonDeclaredCheckedExceptions<>(delegate, declaredExceptions); } /** * Wrap the given {@code delegate} in an InvocationHandler that returns null when the * {@code delegate} * throws an InterruptedException. * * @param delegate The delegate to invoke, potentially throwing an InterruptedException when invoked * @param <R> The response type of the command handler * @return an InvocationHandler that wraps returns null when an InterruptedException is thrown */ protected <R> InvocationHandler<R> wrapToReturnNullOnInterrupted(final InvocationHandler<R> delegate) { return new NullOnInterrupted<>(delegate); } /** * Wrap the given {@code delegate} in an InvocationHandler that returns null when the * {@code delegate} throws a TimeoutException. * * @param delegate The delegate to invoke, potentially throwing a TimeoutException when invoked * @param <R> The response type of the command handler * @return an InvocationHandler that wraps returns null when a TimeoutException is thrown */ protected <R> InvocationHandler<R> wrapToReturnNullOnTimeout(final InvocationHandler<R> delegate) { return new NullOnTimeout<>(delegate); } /** * Wrap the given {@code delegate} in an InvocationHandler that returns immediately after invoking the * {@code delegate}. * * @param delegate The delegate to invoke, potentially throwing an InterruptedException when invoked * @param <R> The response type of the command handler * @return an InvocationHandler that wraps returns immediately after invoking the delegate */ protected <R> InvocationHandler<R> wrapToFireAndForget(final InvocationHandler<CompletableFuture<R>> delegate) { return new FireAndForget<>(delegate); } /** * Wraps the given {@code delegate} and waits for the result in the Future to become available. No explicit * timeout is provided for the waiting. * * @param delegate The delegate to invoke, returning a Future * @param <R> The result of the command handler * @return the result of the Future, either a return value or an exception */ protected <R> InvocationHandler<R> wrapToWaitForResult(final InvocationHandler<CompletableFuture<R>> delegate) { return new WaitForResult<>(delegate); } /** * Wraps the given {@code delegate} and waits for the result in the Future to become available, with given * {@code timeout} and {@code timeUnit}. * * @param delegate The delegate to invoke, returning a Future * @param timeout The amount of time to wait for the result to become available * @param timeUnit The unit of time to wait * @param <R> The result of the command handler * @return the result of the Future, either a return value or an exception */ protected <R> InvocationHandler<R> wrapToReturnWithFixedTimeout(InvocationHandler<CompletableFuture<R>> delegate, long timeout, TimeUnit timeUnit) { return new WaitForResultWithFixedTimeout<>(delegate, timeout, timeUnit); } /** * Wraps the given {@code delegate} and waits for the result in the Future to become available using given * indices to resolve the parameters that provide the timeout to use. * * @param delegate The delegate to invoke, returning a Future * @param timeoutIndex The index of the argument providing the timeout * @param timeUnitIndex The index of the argument providing the time unit * @param <R> The result of the command handler * @return the result of the Future, either a return value or an exception */ protected <R> InvocationHandler<R> wrapToReturnWithTimeoutInArguments( final InvocationHandler<CompletableFuture<R>> delegate, int timeoutIndex, int timeUnitIndex) { return new WaitForResultWithTimeoutInArguments<>(delegate, timeoutIndex, timeUnitIndex); } private boolean contains(Class<?>[] declaredExceptions, Class<?> exceptionClass) { for (Class<?> declaredException : declaredExceptions) { if (declaredException.isAssignableFrom(exceptionClass)) { return true; } } return false; } /** * Registers the {@code callback}, which is invoked for each sent command, unless Axon is able to detect that * the result of the command does not match the type accepted by the callback. * <p/> * Axon will check the signature of the onSuccess() method and only invoke the callback if the actual result of the * command is an instance of that type. If Axon is unable to detect the type, the callback is always invoked, * potentially causing {@link java.lang.ClassCastException}. * * @param callback The callback to register * @param <R> The type of return value the callback is interested in * @return this instance for further configuration */ public <C, R> CommandGatewayFactory registerCommandCallback(CommandCallback<C, R> callback) { this.commandCallbacks.add(new TypeSafeCallbackWrapper<>(callback)); return this; } /** * Registers the given {@code dispatchInterceptor} which is invoked for each Command dispatched through the * Command Gateways created by this factory. * * @param dispatchInterceptor The interceptor to register. * @return this instance for further configuration */ public CommandGatewayFactory registerDispatchInterceptor( MessageDispatchInterceptor<CommandMessage<?>> dispatchInterceptor) { this.dispatchInterceptors.add(dispatchInterceptor); return this; } private MetaDataExtractor[] extractMetaData(Parameter[] parameters) { List<MetaDataExtractor> extractors = new ArrayList<>(); for (int i = 0; i < parameters.length; i++) { if (org.axonframework.messaging.MetaData.class.isAssignableFrom(parameters[i].getType())) { extractors.add(new MetaDataExtractor(i, null)); } else { Optional<Map<String, Object>> metaDataAnnotation = AnnotationUtils.findAnnotationAttributes(parameters[i], MetaDataValue.class); if (metaDataAnnotation.isPresent()) { extractors.add(new MetaDataExtractor(i, (String) metaDataAnnotation.get().get("metaDataValue"))); } } } return extractors.toArray(new MetaDataExtractor[extractors.size()]); } /** * Interface towards the mechanism that handles a method call on a gateway interface method. * * @param <R> The return type of the method invocation */ public interface InvocationHandler<R> { /** * Handle the invocation of the given {@code invokedMethod}, invoked on given {@code proxy} with * given * {@code args}. * * @param proxy The proxy on which the method was invoked * @param invokedMethod The method being invoked * @param args The arguments of the invocation * @return the return value of the invocation * @throws Exception any exceptions that occurred while processing the invocation */ R invoke(Object proxy, Method invokedMethod, Object[] args) throws Exception; } private static class GatewayInvocationHandler extends AbstractCommandGateway implements java.lang.reflect .InvocationHandler { private final Map<Method, InvocationHandler> dispatchers; public GatewayInvocationHandler(Map<Method, InvocationHandler> dispatchers, CommandBus commandBus, RetryScheduler retryScheduler, List<MessageDispatchInterceptor<? super CommandMessage<?>>> dispatchInterceptors) { super(commandBus, retryScheduler, dispatchInterceptors); this.dispatchers = new HashMap<>(dispatchers); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Exception { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { final InvocationHandler invocationHandler = dispatchers.get(method); return invocationHandler.invoke(proxy, method, args); } } } private static class DispatchOnInvocationHandler<C, R> extends AbstractCommandGateway implements InvocationHandler<CompletableFuture<R>> { private final MetaDataExtractor[] metaDataExtractors; private final List<CommandCallback<? super C, ? super R>> commandCallbacks; private final boolean forceCallbacks; protected DispatchOnInvocationHandler(CommandBus commandBus, RetryScheduler retryScheduler, List<MessageDispatchInterceptor<? super CommandMessage<?>>> messageDispatchInterceptors, MetaDataExtractor[] metaDataExtractors, // NOSONAR List<CommandCallback<? super C, ? super R>> commandCallbacks, boolean forceCallbacks) { super(commandBus, retryScheduler, messageDispatchInterceptors); this.metaDataExtractors = metaDataExtractors; // NOSONAR this.commandCallbacks = commandCallbacks; this.forceCallbacks = forceCallbacks; } @SuppressWarnings("unchecked") @Override public CompletableFuture<R> invoke(Object proxy, Method invokedMethod, Object[] args) { Object command = args[0]; if (metaDataExtractors.length != 0) { Map<String, Object> metaDataValues = new HashMap<>(); for (MetaDataExtractor extractor : metaDataExtractors) { extractor.addMetaData(args, metaDataValues); } if (!metaDataValues.isEmpty()) { command = asCommandMessage(command).withMetaData(metaDataValues); } } if (forceCallbacks || !commandCallbacks.isEmpty()) { List<CommandCallback<? super C, ? super R>> callbacks = new LinkedList<>(); FutureCallback<C, R> future = new FutureCallback<>(); callbacks.add(future); for (Object arg : args) { if (arg instanceof CommandCallback) { final CommandCallback<C, R> callback = (CommandCallback<C, R>) arg; callbacks.add(callback); } } callbacks.addAll(commandCallbacks); send(command, new CompositeCallback(callbacks)); return future; } else { sendAndForget(command); return null; } } } private static class CompositeCallback<C, R> implements CommandCallback<C, R> { private final List<CommandCallback<? super C, ? super R>> callbacks; @SuppressWarnings("unchecked") public CompositeCallback(List<CommandCallback<? super C, ? super R>> callbacks) { this.callbacks = new ArrayList<>(callbacks); } @Override public void onSuccess(CommandMessage<? extends C> commandMessage, R result) { for (CommandCallback<? super C, ? super R> callback : callbacks) { callback.onSuccess(commandMessage, result); } } @Override public void onFailure(CommandMessage<? extends C> commandMessage, Throwable cause) { for (CommandCallback<? super C, ? super R> callback : callbacks) { callback.onFailure(commandMessage, cause); } } } private static final class WrapNonDeclaredCheckedExceptions<R> implements InvocationHandler<R> { private final Class<?>[] declaredExceptions; private final InvocationHandler<R> delegate; private WrapNonDeclaredCheckedExceptions(InvocationHandler<R> delegate, Class<?>[] declaredExceptions) { this.delegate = delegate; this.declaredExceptions = declaredExceptions; // NOSONAR } @Override public R invoke(Object proxy, Method invokedMethod, Object[] args) throws Exception { try { return delegate.invoke(proxy, invokedMethod, args); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } for (Class<?> exception : declaredExceptions) { if (exception.isInstance(cause)) { throw cause instanceof Exception ? (Exception) cause : e; } } throw new CommandExecutionException( "Command execution resulted in a checked exception that was " + "not declared on the gateway", cause); } } } private static class NullOnTimeout<R> implements InvocationHandler<R> { private final InvocationHandler<R> delegate; private NullOnTimeout(InvocationHandler<R> delegate) { this.delegate = delegate; } @Override public R invoke(Object proxy, Method invokedMethod, Object[] args) throws Exception { try { return delegate.invoke(proxy, invokedMethod, args); } catch (TimeoutException timeout) { return null; } } } private static class NullOnInterrupted<R> implements InvocationHandler<R> { private final InvocationHandler<R> delegate; private NullOnInterrupted(InvocationHandler<R> delegate) { this.delegate = delegate; } @Override public R invoke(Object proxy, Method invokedMethod, Object[] args) throws Exception { try { return delegate.invoke(proxy, invokedMethod, args); } catch (InterruptedException timeout) { Thread.currentThread().interrupt(); return null; } } } private static class WaitForResultWithFixedTimeout<R> implements InvocationHandler<R> { private final InvocationHandler<CompletableFuture<R>> delegate; private final long timeout; private final TimeUnit timeUnit; private WaitForResultWithFixedTimeout(InvocationHandler<CompletableFuture<R>> delegate, long timeout, TimeUnit timeUnit) { this.delegate = delegate; this.timeout = timeout; this.timeUnit = timeUnit; } @Override public R invoke(Object proxy, Method invokedMethod, Object[] args) throws Exception { return delegate.invoke(proxy, invokedMethod, args).get(timeout, timeUnit); } } private static class WaitForResultWithTimeoutInArguments<R> implements InvocationHandler<R> { private final InvocationHandler<CompletableFuture<R>> delegate; private final int timeoutIndex; private final int timeUnitIndex; private WaitForResultWithTimeoutInArguments(InvocationHandler<CompletableFuture<R>> delegate, int timeoutIndex, int timeUnitIndex) { this.delegate = delegate; this.timeoutIndex = timeoutIndex; this.timeUnitIndex = timeUnitIndex; } @Override public R invoke(Object proxy, Method invokedMethod, Object[] args) throws Exception { return delegate.invoke(proxy, invokedMethod, args) .get(toLong(args[timeoutIndex]), (TimeUnit) args[timeUnitIndex]); } private long toLong(Object arg) { if (int.class.isInstance(arg) || Integer.class.isInstance(arg)) { return Long.valueOf((Integer) arg); } return (Long) arg; } } private static class WaitForResult<R> implements InvocationHandler<R> { private final InvocationHandler<CompletableFuture<R>> delegate; private WaitForResult(InvocationHandler<CompletableFuture<R>> delegate) { this.delegate = delegate; } @Override public R invoke(Object proxy, Method invokedMethod, Object[] args) throws Exception { return delegate.invoke(proxy, invokedMethod, args).get(); } } private static class FireAndForget<R> implements InvocationHandler<R> { private final InvocationHandler<CompletableFuture<R>> delegate; private FireAndForget(InvocationHandler<CompletableFuture<R>> delegate) { this.delegate = delegate; } @Override public R invoke(Object proxy, Method invokedMethod, Object[] args) throws Exception { delegate.invoke(proxy, invokedMethod, args); return null; } } private static class MetaDataExtractor { private final int argumentIndex; private final String metaDataKey; private MetaDataExtractor(int argumentIndex, String metaDataKey) { this.argumentIndex = argumentIndex; this.metaDataKey = metaDataKey; } @SuppressWarnings("unchecked") public void addMetaData(Object[] args, Map<String, Object> metaData) { final Object parameterValue = args[argumentIndex]; if (metaDataKey == null) { if (parameterValue != null) { metaData.putAll((Map<? extends String, ?>) parameterValue); } } else { metaData.put(metaDataKey, parameterValue); } } } private static class TypeSafeCallbackWrapper<C, R> implements CommandCallback<C, Object> { private final CommandCallback<C, R> delegate; private final Class<R> parameterType; @SuppressWarnings("unchecked") public TypeSafeCallbackWrapper(CommandCallback<C, R> delegate) { this.delegate = delegate; Class discoveredParameterType = Object.class; for (Method m : ReflectionUtils.methodsOf(delegate.getClass())) { if (m.getGenericParameterTypes().length == 2 && m.getGenericParameterTypes()[1] != Object.class && "onSuccess".equals(m.getName()) && Modifier.isPublic(m.getModifiers())) { discoveredParameterType = m.getParameterTypes()[1]; if (discoveredParameterType != Object.class) { break; } } } parameterType = discoveredParameterType; } @Override public void onSuccess(CommandMessage<? extends C> commandMessage, Object result) { if (parameterType.isInstance(result) || (!parameterType.isPrimitive() && result == null)) { delegate.onSuccess(commandMessage, parameterType.cast(result)); } } @Override public void onFailure(CommandMessage<? extends C> commandMessage, Throwable cause) { delegate.onFailure(commandMessage, cause); } } }