Package org.axonframework.commandhandling.gateway

Source Code of org.axonframework.commandhandling.gateway.GatewayProxyFactory$TypeSafeCallbackWrapper

/*
* Copyright (c) 2010-2014. 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.CommandDispatchInterceptor;
import org.axonframework.commandhandling.CommandExecutionException;
import org.axonframework.commandhandling.callbacks.FutureCallback;
import org.axonframework.common.Assert;
import org.axonframework.common.CollectionUtils;
import org.axonframework.common.ReflectionUtils;
import org.axonframework.common.annotation.MetaData;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

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 MetaData @MetaData} are 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</code> 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</code>.</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</code> 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/>
* 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</code> or <code>Future</code>, 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 GatewayProxyFactory {

    private final CommandBus commandBus;
    private final RetryScheduler retryScheduler;
    private final List<CommandDispatchInterceptor> dispatchInterceptors;
    private final List<CommandCallback<?>> commandCallbacks;

    /**
     * Initialize the factory sending Commands to the given <code>commandBus</code>, optionally intercepting them with
     * given <code>dispatchInterceptors</code>.
     * <p/>
     * Note that the given <code>dispatchInterceptors</code> 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
     */
    public GatewayProxyFactory(CommandBus commandBus, CommandDispatchInterceptor... dispatchInterceptors) {
        this(commandBus, null, dispatchInterceptors);
    }

    /**
     * Initialize the factory sending Commands to the given <code>commandBus</code>, optionally intercepting them with
     * given <code>dispatchInterceptors</code>. The given <code>retryScheduler</code> will reschedule commands for
     * dispatching if a previous attempt resulted in an exception.
     * <p/>
     * Note that the given <code>dispatchInterceptors</code> 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</code> to report failures without rescheduling
     * @param commandDispatchInterceptors The interceptors to invoke before dispatching commands to the Command Bus
     */
    public GatewayProxyFactory(CommandBus commandBus, RetryScheduler retryScheduler,
                               CommandDispatchInterceptor... commandDispatchInterceptors) {
        this(commandBus, retryScheduler, asList(commandDispatchInterceptors));
    }

    /**
     * Initialize the factory sending Commands to the given <code>commandBus</code>, optionally intercepting them with
     * given <code>dispatchInterceptors</code>. The given <code>retryScheduler</code> will reschedule commands for
     * dispatching if a previous attempt resulted in an exception.
     * <p/>
     * Note that the given <code>dispatchInterceptors</code> 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</code> to report failures without rescheduling
     * @param commandDispatchInterceptors The interceptors to invoke before dispatching commands to the Command Bus
     */
    public GatewayProxyFactory(CommandBus commandBus, RetryScheduler retryScheduler,
                               List<CommandDispatchInterceptor> commandDispatchInterceptors) {
        Assert.notNull(commandBus, "commandBus may not be null");
        this.retryScheduler = retryScheduler;
        this.commandBus = commandBus;
        if (commandDispatchInterceptors != null && !commandDispatchInterceptors.isEmpty()) {
            this.dispatchInterceptors = new CopyOnWriteArrayList<CommandDispatchInterceptor>(commandDispatchInterceptors);
        } else {
            this.dispatchInterceptors = new CopyOnWriteArrayList<CommandDispatchInterceptor>();
        }
        this.commandCallbacks = new CopyOnWriteArrayList<CommandCallback<?>>();
    }

    /**
     * Creates a gateway instance for the given <code>gatewayInterface</code>. 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<Method, InvocationHandler>();
        for (Method gatewayMethod : gatewayInterface.getMethods()) {
            MetaDataExtractor[] extractors = extractMetaData(gatewayMethod.getParameterTypes(),
                                                             gatewayMethod.getParameterAnnotations());

            final Class<?>[] arguments = gatewayMethod.getParameterTypes();

            InvocationHandler dispatcher = new DispatchOnInvocationHandler(commandBus, retryScheduler,
                                                                           dispatchInterceptors, extractors,
                                                                           commandCallbacks, true);
            if (!Future.class.equals(gatewayMethod.getReturnType())) {
                // no wrapping
                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);
                    if (timeout == null) {
                        timeout = gatewayMethod.getDeclaringClass().getAnnotation(Timeout.class);
                    }
                    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 boolean hasCallbackParameters(Method gatewayMethod) {
        for (Class<?> parameter : gatewayMethod.getParameterTypes()) {
            if (CommandCallback.class.isAssignableFrom(parameter)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Wraps the given <code>delegate</code> 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</code>
     */
    protected <R> InvocationHandler<R> wrapUndeclaredExceptions(final InvocationHandler<R> delegate,
                                                                final Class<?>[] declaredExceptions) {
        return new WrapNonDeclaredCheckedExceptions<R>(delegate, declaredExceptions);
    }

    /**
     * Wrap the given <code>delegate</code> in an InvocationHandler that returns null when the
     * <code>delegate</code>
     * 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<R>(delegate);
    }

    /**
     * Wrap the given <code>delegate</code> in an InvocationHandler that returns null when the
     * <code>delegate</code> 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<R>(delegate);
    }

    /**
     * Wrap the given <code>delegate</code> in an InvocationHandler that returns immediately after invoking the
     * <code>delegate</code>.
     *
     * @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<Future<R>> delegate) {
        return new FireAndForget<R>(delegate);
    }

    /**
     * Wraps the given <code>delegate</code> 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<Future<R>> delegate) {
        return new WaitForResult<R>(delegate);
    }

    /**
     * Wraps the given <code>delegate</code> and waits for the result in the Future to become available, with given
     * <code>timeout</code> and <code>timeUnit</code>.
     *
     * @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<Future<R>> delegate,
                                                                    long timeout, TimeUnit timeUnit) {
        return new WaitForResultWithFixedTimeout<R>(delegate, timeout, timeUnit);
    }

    /**
     * Wraps the given <code>delegate</code> 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<Future<R>> delegate, int timeoutIndex, int timeUnitIndex) {
        return new WaitForResultWithTimeoutInArguments<R>(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</code>, 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 <R> GatewayProxyFactory registerCommandCallback(CommandCallback<R> callback) {
        this.commandCallbacks.add(new TypeSafeCallbackWrapper<R>(callback));
        return this;
    }

    /**
     * Registers the given <code>dispatchInterceptor</code> 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 GatewayProxyFactory registerDispatchInterceptor(CommandDispatchInterceptor dispatchInterceptor) {
        this.dispatchInterceptors.add(dispatchInterceptor);
        return this;
    }

    private MetaDataExtractor[] extractMetaData(Class<?>[] parameterTypes, Annotation[][] parameterAnnotations) {
        List<MetaDataExtractor> extractors = new ArrayList<MetaDataExtractor>();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            if (org.axonframework.domain.MetaData.class.isAssignableFrom(parameterTypes[i])) {
                extractors.add(new MetaDataExtractor(i, null));
            } else {
                Annotation[] annotations = parameterAnnotations[i];
                final MetaData metaDataAnnotation = CollectionUtils.getAnnotation(annotations, MetaData.class);
                if (metaDataAnnotation != null) {
                    extractors.add(new MetaDataExtractor(i, metaDataAnnotation.value()));
                }
            }
        }
        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</code>, invoked on given <code>proxy</code> with
         * given
         * <code>args</code>.
         *
         * @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 Throwable any exceptions that occurred while processing the invocation
         */
        R invoke(Object proxy, Method invokedMethod, Object[] args) throws Throwable;
    }

    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<CommandDispatchInterceptor> dispatchInterceptors) {
            super(commandBus, retryScheduler, dispatchInterceptors);
            this.dispatchers = new HashMap<Method, InvocationHandler>(dispatchers);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            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<R> extends AbstractCommandGateway
            implements InvocationHandler<Future<R>> {

        private final MetaDataExtractor[] metaDataExtractors;
        private final List<CommandCallback<? super R>> commandCallbacks;
        private final boolean forceCallbacks;

        protected DispatchOnInvocationHandler(CommandBus commandBus, RetryScheduler retryScheduler,
                                              List<CommandDispatchInterceptor> commandDispatchInterceptors,
                                              MetaDataExtractor[] metaDataExtractors, // NOSONAR
                                              List<CommandCallback<? super R>> commandCallbacks,
                                              boolean forceCallbacks) {
            super(commandBus, retryScheduler, commandDispatchInterceptors);
            this.metaDataExtractors = metaDataExtractors; // NOSONAR
            this.commandCallbacks = commandCallbacks;
            this.forceCallbacks = forceCallbacks;
        }

        @SuppressWarnings("unchecked")
        @Override
        public Future<R> invoke(Object proxy, Method invokedMethod, Object[] args) {
            Object command = args[0];
            if (metaDataExtractors.length != 0) {
                Map<String, Object> metaDataValues = new HashMap<String, Object>();
                for (MetaDataExtractor extractor : metaDataExtractors) {
                    extractor.addMetaData(args, metaDataValues);
                }
                if (!metaDataValues.isEmpty()) {
                    command = asCommandMessage(command).withMetaData(metaDataValues);
                }
            }
            if (forceCallbacks || !commandCallbacks.isEmpty()) {
                List<CommandCallback<? super R>> callbacks = new LinkedList<CommandCallback<? super R>>();
                FutureCallback<R> future = new FutureCallback<R>();
                callbacks.add(future);
                for (Object arg : args) {
                    if (arg instanceof CommandCallback) {
                        final CommandCallback<R> callback = (CommandCallback<R>) arg;
                        callbacks.add(callback);
                    }
                }
                callbacks.addAll(commandCallbacks);
                send(command, new CompositeCallback(callbacks));
                return future;
            } else {
                sendAndForget(command);
                return null;
            }
        }
    }

    private static class CompositeCallback<R> implements CommandCallback<R> {

        private final List<CommandCallback<? super R>> callbacks;

        @SuppressWarnings("unchecked")
        public CompositeCallback(List<CommandCallback<? super R>> callbacks) {
            this.callbacks = new ArrayList<CommandCallback<? super R>>(callbacks);
        }

        @Override
        public void onSuccess(R result) {
            for (CommandCallback<? super R> callback : callbacks) {
                callback.onSuccess(result);
            }
        }

        @Override
        public void onFailure(Throwable cause) {
            for (CommandCallback callback : callbacks) {
                callback.onFailure(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 Throwable {
            try {
                return delegate.invoke(proxy, invokedMethod, args);
            } catch (ExecutionException e) {
                Throwable cause = e.getCause();
                for (Class<?> exception : declaredExceptions) {
                    if (exception.isInstance(cause)) {
                        throw cause;
                    }
                }
                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 Throwable {
            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 Throwable {
            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<Future<R>> delegate;
        private final long timeout;
        private final TimeUnit timeUnit;

        private WaitForResultWithFixedTimeout(InvocationHandler<Future<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 Throwable {
            return delegate.invoke(proxy, invokedMethod, args).get(timeout, timeUnit);
        }
    }

    private static class WaitForResultWithTimeoutInArguments<R> implements InvocationHandler<R> {

        private final InvocationHandler<Future<R>> delegate;
        private final int timeoutIndex;
        private final int timeUnitIndex;

        private WaitForResultWithTimeoutInArguments(InvocationHandler<Future<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 Throwable {
            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<Future<R>> delegate;

        private WaitForResult(InvocationHandler<Future<R>> delegate) {
            this.delegate = delegate;
        }

        @Override
        public R invoke(Object proxy, Method invokedMethod, Object[] args) throws Throwable {
            return delegate.invoke(proxy, invokedMethod, args).get();
        }
    }

    private static class FireAndForget<R> implements InvocationHandler<R> {

        private final InvocationHandler<Future<R>> delegate;

        private FireAndForget(InvocationHandler<Future<R>> delegate) {
            this.delegate = delegate;
        }

        @Override
        public R invoke(Object proxy, Method invokedMethod, Object[] args) throws Throwable {
            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<R> implements CommandCallback<Object> {

        private final CommandCallback<R> delegate;
        private final Class<R> parameterType;

        @SuppressWarnings("unchecked")
        public TypeSafeCallbackWrapper(CommandCallback<R> delegate) {
            this.delegate = delegate;
            Class discoveredParameterType = Object.class;
            for (Method m : ReflectionUtils.methodsOf(delegate.getClass())) {
                if (m.getGenericParameterTypes().length == 1
                        && m.getGenericParameterTypes()[0] != Object.class
                        && "onSuccess".equals(m.getName())
                        && Modifier.isPublic(m.getModifiers())) {
                    discoveredParameterType = m.getParameterTypes()[0];
                    if (discoveredParameterType != Object.class) {
                        break;
                    }
                }
            }
            parameterType = discoveredParameterType;
        }

        @Override
        public void onSuccess(Object result) {
            if (parameterType.isInstance(result)) {
                delegate.onSuccess(parameterType.cast(result));
            }
        }

        @Override
        public void onFailure(Throwable cause) {
            delegate.onFailure(cause);
        }
    }
}
TOP

Related Classes of org.axonframework.commandhandling.gateway.GatewayProxyFactory$TypeSafeCallbackWrapper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.