Package org.axonframework.saga.annotation

Source Code of org.axonframework.saga.annotation.AsyncAnnotatedSagaManager

/*
* 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.saga.annotation;

import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.EventTranslator;
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import org.axonframework.common.Assert;
import org.axonframework.common.AxonConfigurationException;
import org.axonframework.common.Subscribable;
import org.axonframework.common.annotation.ClasspathParameterResolverFactory;
import org.axonframework.common.annotation.ParameterResolverFactory;
import org.axonframework.correlation.CorrelationDataProvider;
import org.axonframework.correlation.MultiCorrelationDataProvider;
import org.axonframework.correlation.SimpleCorrelationDataProvider;
import org.axonframework.domain.EventMessage;
import org.axonframework.eventhandling.EventBus;
import org.axonframework.eventhandling.EventProcessingMonitor;
import org.axonframework.eventhandling.EventProcessingMonitorCollection;
import org.axonframework.eventhandling.EventProcessingMonitorSupport;
import org.axonframework.saga.GenericSagaFactory;
import org.axonframework.saga.SagaCreationPolicy;
import org.axonframework.saga.SagaFactory;
import org.axonframework.saga.SagaManager;
import org.axonframework.saga.SagaRepository;
import org.axonframework.saga.repository.inmemory.InMemorySagaRepository;
import org.axonframework.unitofwork.DefaultUnitOfWorkFactory;
import org.axonframework.unitofwork.TransactionManager;
import org.axonframework.unitofwork.UnitOfWorkFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
* A SagaManager implementation that processes Sagas asynchronously. Incoming events are placed on a queue and
* processed by a given number of processors. Modified saga state is persisted in batches to the repository, to
* minimize communication overhead with back-ends.
* <p/>
* This SagaManager implementation guarantees a "happens before" type processing for each Saga. That means that the
* behavior of asynchronously processed events is exactly identical as the behavior if the events were processed
* completely sequentially.
*
* @author Allard Buijze
* @since 2.0
*/
public class AsyncAnnotatedSagaManager implements SagaManager, Subscribable, EventProcessingMonitorSupport {

    private static final WaitStrategy DEFAULT_WAIT_STRATEGY = new BlockingWaitStrategy();
    private WaitStrategy waitStrategy = DEFAULT_WAIT_STRATEGY;
    private static final int DEFAULT_BUFFER_SIZE = 512;
    private int bufferSize = DEFAULT_BUFFER_SIZE;
    private static final int DEFAULT_PROCESSOR_COUNT = 1;
    private int processorCount = DEFAULT_PROCESSOR_COUNT;
    private final EventBus eventBus;
    private final Class<? extends AbstractAnnotatedSaga>[] sagaTypes;
    private final ParameterResolverFactory parameterResolverFactory;
    private final SagaManagerStatus sagaManagerStatus = new SagaManagerStatus();
    private final EventProcessingMonitorCollection processingMonitors = new EventProcessingMonitorCollection();
    private volatile Disruptor<AsyncSagaProcessingEvent> disruptor;
    private boolean shutdownExecutorOnStop = true;
    private Executor executor = Executors.newCachedThreadPool();
    private SagaRepository sagaRepository = new InMemorySagaRepository();
    private volatile SagaFactory sagaFactory = new GenericSagaFactory();
    private UnitOfWorkFactory unitOfWorkFactory = new DefaultUnitOfWorkFactory();
    private long startTimeout = 5000;
    private CorrelationDataProvider<? super EventMessage> correlationDataProvider = new SimpleCorrelationDataProvider();

    /**
     * Initializes an Asynchronous Saga Manager using default values for the given <code>sagaTypes</code> to listen to
     * events on the given <code>eventBus</code>.
     * <p/>
     * After initialization, the SagaManager must be explicitly started using the {@link #start()} method.
     *
     * @param eventBus  The Event Bus from which the Saga Manager will process events
     * @param sagaTypes The types of Saga this saga manager will process incoming events for
     * @deprecated use {@link #AsyncAnnotatedSagaManager(Class[])} and register with the event bus using {@link
     * EventBus#subscribe(org.axonframework.eventhandling.EventListener)}
     */
    @Deprecated
    public AsyncAnnotatedSagaManager(EventBus eventBus, Class<? extends AbstractAnnotatedSaga>... sagaTypes) {
        Assert.notNull(eventBus, "eventBus may not be null");
        this.eventBus = eventBus;
        this.sagaTypes = Arrays.copyOf(sagaTypes, sagaTypes.length);
        this.parameterResolverFactory = ClasspathParameterResolverFactory.forClass(
                sagaTypes.length == 0 ? AsyncAnnotatedSagaManager.class : sagaTypes[0]);
    }

    /**
     * Initializes an Asynchronous Saga Manager using default values for the given <code>sagaTypes</code>.
     * <p/>
     * After initialization, the SagaManager must be explicitly started using the {@link #start()} method.
     *
     * @param sagaTypes The types of Saga this saga manager will process incoming events for
     */
    public AsyncAnnotatedSagaManager(Class<? extends AbstractAnnotatedSaga>... sagaTypes) {
        this(ClasspathParameterResolverFactory.forClass(
                sagaTypes.length == 0 ? AsyncAnnotatedSagaManager.class : sagaTypes[0]),
             sagaTypes);
    }

    /**
     * Initializes an Asynchronous Saga Manager using default values for the given <code>sagaTypes</code>.
     * <p/>
     * After initialization, the SagaManager must be explicitly started using the {@link #start()} method.
     *
     * @param parameterResolverFactory The parameter resolver factory to resolve parameters of annotated handlers
     * @param sagaTypes                The types of Saga this saga manager will process incoming events for
     */
    public AsyncAnnotatedSagaManager(ParameterResolverFactory parameterResolverFactory,
                                     Class<? extends AbstractAnnotatedSaga>... sagaTypes) {
        this.parameterResolverFactory = parameterResolverFactory;
        this.eventBus = null;
        this.sagaTypes = Arrays.copyOf(sagaTypes, sagaTypes.length);
    }

    /**
     * Starts the Saga Manager by starting the processor threads and subscribes it with the <code>eventBus</code>. If
     * the saga manager is already started, it is only re-subscribed to the event bus.
     */
    public synchronized void start() {
        if (disruptor == null) {
            sagaManagerStatus.setStatus(true);
            disruptor = new Disruptor<AsyncSagaProcessingEvent>(new AsyncSagaProcessingEvent.Factory(),
                                                                bufferSize,
                                                                new ValidatingExecutor(executor, startTimeout),
                                                                ProducerType.MULTI,
                                                                waitStrategy);
            disruptor.handleExceptionsWith(new LoggingExceptionHandler());
            disruptor.handleEventsWith(AsyncSagaEventProcessor.createInstances(sagaRepository, parameterResolverFactory,
                                                                               unitOfWorkFactory, processorCount,
                                                                               disruptor.getRingBuffer(),
                                                                               sagaManagerStatus,
                                                                               correlationDataProvider))
                     .then(new MonitorNotifier(processingMonitors));
            disruptor.start();
        }
        subscribe();
    }

    /**
     * Unsubscribes this Saga Manager from the event bus and stops accepting new events. The method is blocked until
     * all scheduled events have been processed. Note that any manually provided Executors using ({@link
     * #setExecutor(java.util.concurrent.Executor)} are not shut down.
     * <p/>
     * If the Saga Manager was already stopped, nothing happens.
     */
    public synchronized void stop() {
        sagaManagerStatus.setStatus(false);
        unsubscribe();
        if (disruptor != null) {
            disruptor.shutdown();
            if (shutdownExecutorOnStop && executor instanceof ExecutorService) {
                ((ExecutorService) executor).shutdown();
            }
        }
        disruptor = null;
    }

    @Override
    public void unsubscribe() {
        if (eventBus != null) {
            eventBus.unsubscribe(this);
        }
    }

    @Override
    public void subscribe() {
        if (eventBus != null) {
            eventBus.subscribe(this);
        }
    }

    @SuppressWarnings({"unchecked"})
    @Override
    public void handle(final EventMessage event) {
        if (disruptor != null) {
            for (final Class<? extends AbstractAnnotatedSaga> sagaType : sagaTypes) {
                SagaMethodMessageHandlerInspector inspector =
                        SagaMethodMessageHandlerInspector.getInstance(sagaType, parameterResolverFactory);
                final List<SagaMethodMessageHandler> handlers = inspector.getMessageHandlers(event);
                if (!handlers.isEmpty()) {
                    AbstractAnnotatedSaga newSagaInstance = null;
                    for (SagaMethodMessageHandler handler : handlers) {
                        if (newSagaInstance == null && handler.getCreationPolicy() != SagaCreationPolicy.NONE) {
                            newSagaInstance = (AbstractAnnotatedSaga) sagaFactory.createSaga(inspector
                                                                                                     .getSagaType());
                        }
                    }
                    disruptor.publishEvent(new SagaProcessingEventTranslator(event, inspector, handlers,
                                                                             newSagaInstance));
                }
            }
        }
    }

    @Override
    public Class<?> getTargetType() {
        return sagaTypes.length > 0 ? sagaTypes[0] : Void.TYPE;
    }

    @Override
    public void subscribeEventProcessingMonitor(EventProcessingMonitor monitor) {
        processingMonitors.subscribeEventProcessingMonitor(monitor);
    }

    @Override
    public void unsubscribeEventProcessingMonitor(EventProcessingMonitor monitor) {
        processingMonitors.unsubscribeEventProcessingMonitor(monitor);
    }

    /**
     * Sets the executor that provides the threads for the processors. Note that you must ensure that this executor
     * is capable of delivering <em>all</em> of the required threads at once. If that is not the case, the Saga Manager
     * might hang while waiting for the executor to provide them. Must be set <em>before</em> the SagaManager is
     * started.
     * <p/>
     * By default, a thread is created for each processor.
     *
     * @param executor the executor that provides the threads for the processors
     * @see #setProcessorCount(int)
     */
    public synchronized void setExecutor(Executor executor) {
        Assert.state(disruptor == null, "Cannot set executor after SagaManager has started");
        this.shutdownExecutorOnStop = false;
        this.executor = executor;
    }

    /**
     * Sets the saga repository to store and load Sagas from. Must be set <em>before</em> the SagaManager is
     * started.
     * <p/>
     * Defaults to an in-memory repository.
     *
     * @param sagaRepository the saga repository to store and load Sagas from
     */
    public synchronized void setSagaRepository(SagaRepository sagaRepository) {
        Assert.state(disruptor == null, "Cannot set sagaRepository when SagaManager has started");
        this.sagaRepository = sagaRepository;
    }

    /**
     * Sets the SagaFactory responsible for creating new Saga instances when required. Must be set <em>before</em> the
     * SagaManager is started.
     * <p/>
     * Defaults to a {@link GenericSagaFactory} instance.
     *
     * @param sagaFactory the SagaFactory responsible for creating new Saga instances
     */
    public synchronized void setSagaFactory(SagaFactory sagaFactory) {
        Assert.state(disruptor == null, "Cannot set sagaFactory when SagaManager has started");
        this.sagaFactory = sagaFactory;
    }

    /**
     * Sets the TransactionManager used to manage any transactions required by the underlying storage mechanism. Note
     * that batch sizes set by this transaction manager are ignored. Must be set <em>before</em> the
     * SagaManager is started.
     * <p/>
     * By default, no transactions are managed.
     *
     * @param transactionManager the TransactionManager used to manage any transactions required by the underlying
     *                           storage mechanism.
     */
    public synchronized void setTransactionManager(TransactionManager transactionManager) {
        Assert.state(disruptor == null, "Cannot set transactionManager when SagaManager has started");
        this.unitOfWorkFactory = new DefaultUnitOfWorkFactory(transactionManager);
    }

    /**
     * Sets the number of processors (threads) to process events with. Ensure that the given {@link
     * #setExecutor(java.util.concurrent.Executor) executor} is capable of processing this amount of concurrent tasks.
     * Must be set <em>before</em> the SagaManager is started.
     * <p/>
     * Defaults to 1.
     *
     * @param processorCount the number of processors (threads) to process events with
     */
    public synchronized void setProcessorCount(int processorCount) {
        Assert.state(disruptor == null, "Cannot set processorCount when SagaManager has started");
        this.processorCount = processorCount;
    }

    /**
     * Sets the amount of time (in milliseconds) the AsyncSagaManager will wait for the async processors to be assigned
     * a thread from the executor. This is used to ensure that the executor provides a thread for the processors,
     * instead of queueing them. This typically occurs when using a thread pool with a core pool size smaller than the
     * processorCount.
     * <p/>
     * Must be set before calling {@link #start()}. Defaults to 5000 (5 seconds).
     *
     * @param startTimeout the number of millis to wait for the processor to have been assigned a thread. Defaults to
     *                     5000 (5 seconds).
     */
    public synchronized void setStartTimeout(long startTimeout) {
        this.startTimeout = startTimeout;
    }

    /**
     * Sets the size of the processing buffer. This is equal to the amount of events that may awaiting for processing
     * before the input is blocked. Must be set <em>before</em> the SagaManager is started.
     * <p/>
     * Note that this value <em>must</em> be a power of 2.
     * <p/>
     * Defaults to 512.
     *
     * @param bufferSize The size of the processing buffer. Must be a power of 2.
     */
    public synchronized void setBufferSize(int bufferSize) {
        Assert.isTrue(Integer.bitCount(bufferSize) == 1, "The buffer size must be a power of 2");
        Assert.state(disruptor == null, "Cannot set bufferSize when SagaManager has started");
        this.bufferSize = bufferSize;
    }

    /**
     * Sets the WaitStrategy to use when event processors need to wait for incoming events.
     * <p/>
     * Defaults to a BlockingWaitStrategy.
     *
     * @param waitStrategy the WaitStrategy to use when event processors need to wait for incoming events
     */
    public synchronized void setWaitStrategy(WaitStrategy waitStrategy) {
        Assert.state(disruptor == null, "Cannot set waitStrategy when SagaManager has started");
        this.waitStrategy = waitStrategy;
    }

    public synchronized void setCorrelationDataProvider(
            CorrelationDataProvider<? super EventMessage> correlationDataProvider) {
        this.correlationDataProvider = correlationDataProvider;
    }

    public synchronized void setCorrelationDataProviders(
            List<? extends CorrelationDataProvider<? super EventMessage>> correlationDataProviders) {
        this.correlationDataProvider = new MultiCorrelationDataProvider<EventMessage>(correlationDataProviders);
    }

    private static final class SagaProcessingEventTranslator implements EventTranslator<AsyncSagaProcessingEvent> {

        private final EventMessage event;
        private final SagaMethodMessageHandlerInspector annotationInspector;
        private final List<SagaMethodMessageHandler> handlers;
        private final AbstractAnnotatedSaga newSagaInstance;

        private SagaProcessingEventTranslator(EventMessage event, SagaMethodMessageHandlerInspector annotationInspector,
                                              List<SagaMethodMessageHandler> handlers,
                                              AbstractAnnotatedSaga newSagaInstance) {
            this.event = event;
            this.annotationInspector = annotationInspector;
            this.handlers = handlers;
            this.newSagaInstance = newSagaInstance;
        }

        @SuppressWarnings({"unchecked"})
        @Override
        public void translateTo(AsyncSagaProcessingEvent entry, long sequence) {
            entry.reset(event, annotationInspector.getSagaType(), handlers, newSagaInstance);
        }
    }

    private static final class LoggingExceptionHandler implements ExceptionHandler {

        private static final Logger logger = LoggerFactory.getLogger(LoggingExceptionHandler.class);

        @Override
        public void handleEventException(Throwable ex, long sequence, Object event) {
            logger.warn("A fatal exception occurred while processing an Event for a Saga. "
                                + "Processing will continue with the next Event", ex);
        }

        @Override
        public void handleOnStartException(Throwable ex) {
            logger.warn("An exception occurred while starting the AsyncAnnotatedSagaManager.", ex);
        }

        @Override
        public void handleOnShutdownException(Throwable ex) {
            logger.warn("An exception occurred while shutting down the AsyncAnnotatedSagaManager.", ex);
        }
    }

    /**
     * Exposes the running state of the SagaManager.
     */
    static class SagaManagerStatus {

        private volatile boolean isRunning;

        private void setStatus(boolean running) {
            isRunning = running;
        }

        /**
         * Indicates whether the SagaManager that provided this instance is (still) running.
         *
         * @return <code>true</code> if the SagaManager is running, otherwise <code>false</code>
         */
        public boolean isRunning() {
            return isRunning;
        }
    }

    /**
     * Executor wrapper that checks whether a given task is executed immediately.
     */
    private static class ValidatingExecutor implements Executor {

        private final Executor delegate;
        private final long timeoutMillis;

        /**
         * Initialize the Executor that delegates to the given <code>executor</code> and wait for at most
         * <code>timeoutMillis</code> for tasks to actually start.
         *
         * @param executor      The executor expected to provide a thread for execution
         * @param timeoutMillis The maximum amount of time to wait for the thread to pick up the task
         */
        public ValidatingExecutor(Executor executor, long timeoutMillis) {
            this.delegate = executor;
            this.timeoutMillis = timeoutMillis;
        }

        @Override
        public void execute(Runnable command) {
            final StartDetectingRunnable task = new StartDetectingRunnable(command);
            delegate.execute(task);
            try {
                if (!task.awaitStarted(timeoutMillis, TimeUnit.MILLISECONDS)) {
                    throw new AxonConfigurationException("It seems that the given Executor is not providing a thread "
                                                                 + "for the AsyncSagaManager. Ensure that the "
                                                                 + "corePoolSize is larger than the processor count.");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    /**
     * Runnable implementation that exposes the fact it has started executing.
     */
    private static class StartDetectingRunnable implements Runnable {

        private final Runnable delegate;
        private final CountDownLatch cdl = new CountDownLatch(1);

        /**
         * @param command The actual runnable to execute
         */
        public StartDetectingRunnable(Runnable command) {
            this.delegate = command;
        }

        @Override
        public void run() {
            cdl.countDown();
            delegate.run();
        }

        /**
         * Indicates whether the task has started, potentially waiting for the given <code>timeout</code>.
         *
         * @param timeout The amount of time to wait for the process to start
         * @param unit    The unit of time
         * @return whether the task has started or not
         *
         * @throws InterruptedException when the thread receives an interrupt while waiting for the start notification
         */
        public boolean awaitStarted(long timeout, TimeUnit unit) throws InterruptedException {
            return cdl.await(timeout, unit);
        }
    }

    private static class MonitorNotifier implements EventHandler<AsyncSagaProcessingEvent> {

        private final EventProcessingMonitor monitor;
        private final List<EventMessage> processedMessages = new ArrayList<EventMessage>();

        public MonitorNotifier(EventProcessingMonitor monitor) {
            this.monitor = monitor;
        }

        @Override
        public void onEvent(AsyncSagaProcessingEvent event, long sequence, boolean endOfBatch) throws Exception {
            processedMessages.add(event.getPublishedEvent());
            if (endOfBatch) {
                monitor.onEventProcessingCompleted(processedMessages);
                processedMessages.clear();
            }
        }
    }
}
TOP

Related Classes of org.axonframework.saga.annotation.AsyncAnnotatedSagaManager

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.