Package com.codebullets.sagalib.processing

Source Code of com.codebullets.sagalib.processing.SagaFactory$ProviderLoader

package com.codebullets.sagalib.processing;

import com.codebullets.sagalib.Saga;
import com.codebullets.sagalib.SagaState;
import com.codebullets.sagalib.messages.Timeout;
import com.codebullets.sagalib.startup.MessageHandler;
import com.codebullets.sagalib.startup.SagaAnalyzer;
import com.codebullets.sagalib.startup.SagaHandlersMap;
import com.codebullets.sagalib.storage.StateStorage;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Provider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;

/**
* Responsible to create new instances of sagas.
*/
public class SagaFactory {
    private static final Logger LOG = LoggerFactory.getLogger(SagaFactory.class);
    private final Multimap<Class, Class<? extends Saga>> messagesToContinueSaga = HashMultimap.create();
    private final Multimap<Class, Class<? extends Saga>> messagesStartingSagas = HashMultimap.create();
    private final LoadingCache<Class<? extends Saga>, Provider<? extends Saga>> providers;
    private KeyExtractor keyExtractor;
    private StateStorage stateStorage;

    /**
     * Generates a new instance of SagaFactory.
     */
    @Inject
    public SagaFactory(final SagaAnalyzer sagaAnalyzer, final SagaProviderFactory providerFactory, final KeyExtractor keyExtractor,
                       final StateStorage stateStorage) {
        this.keyExtractor = keyExtractor;
        this.stateStorage = stateStorage;
        // Create providers when needed. Cache providers for later use.
        providers = CacheBuilder.newBuilder().build(new ProviderLoader(providerFactory));

        // scan for sagas and their messages being handled
        Map<Class<? extends Saga>, SagaHandlersMap> handlersMap = sagaAnalyzer.scanHandledMessageTypes();
        initializeMessageMappings(handlersMap);
    }

    /**
     * Creates new instances based on the message type provided.
     * @return Returns a saga instance.
     */
    public Collection<Saga> create(final Object message) {
        Collection<Saga> sagaInstances = new ArrayList<>();

        if (message instanceof Timeout) {
            // timeout is special. Has only one specific saga state and
            // saga id is already known
            Timeout timeout = (Timeout) message;
            Saga saga = createSagaForTimeoutHandling(timeout);
            if (saga != null) {
                sagaInstances.add(saga);
            }
        } else {
            // create and start a new saga if message has been flagged as such
            Collection<Class<? extends Saga>> startingSagaTypes = messagesStartingSagas.get(message.getClass());
            for (Class<? extends Saga> sagaType : startingSagaTypes) {
                sagaInstances.add(startNewSaga(sagaType));
            }

            // Search for existing saga states and attach them to created instances.
            Collection <Class<? extends Saga>> existingSagaTypes = messagesToContinueSaga.get(message.getClass());
            for (Class<? extends Saga> sagaType : existingSagaTypes) {
                Collection<Saga> sagas = continueSagas(sagaType, message);
                sagaInstances.addAll(sagas);
            }
        }

        return sagaInstances;
    }

    /**
     * Search for saga state based on id directly and create instance with attached state.
     */
    private Saga createSagaForTimeoutHandling(final Timeout timeout) {
        Saga saga = null;

        // timeout does not need key extraction
        SagaState state = stateStorage.load(timeout.getSagaId());
        if (state != null) {
            saga = continueSaga(state.getType(), state);
        } else {
            LOG.warn("No open saga state found. Timeout = {}", timeout);
        }

        return saga;
    }

    /**
     * Search for existing saga states and attach them to saga instances.
     */
    private Collection<Saga> continueSagas(final Class<? extends Saga> sagaToContinue, final Object message) {
        Collection<Saga> sagas = new ArrayList<>();

        String key = keyExtractor.findSagaInstanceKey(sagaToContinue, message);
        if (key != null) {
            Collection<? extends SagaState> sagaStates = stateStorage.load(sagaToContinue.getName(), key);
            for (SagaState sagaState : sagaStates) {
                Saga saga = continueSaga(sagaToContinue, sagaState);
                if (saga != null) {
                    sagas.add(saga);
                }
            }
        } else {
            LOG.error("Can not determine saga instance key from message {}", message);
        }

        return sagas;
    }

    /**
     * Creates a new saga instance and attaches the existing saga state.
     */
    private Saga continueSaga(final Class<? extends Saga> sagaToContinue, final SagaState existingSate) {
        Saga saga;

        try {
            saga = providers.get(sagaToContinue).get();
            saga.setState(existingSate);
        } catch (Exception ex) {
            saga = null;
            LOG.error("Unable to create new instance of saga type {}.", sagaToContinue, ex);
        }

        return saga;
    }

    /**
     * Create a new saga instance based on fully qualified name and the existing saga state.
     */
    private Saga continueSaga(final String sagaToContinue, final SagaState existingState) {
        Saga saga = null;

        try {
            Class clazz = Class.forName(sagaToContinue);
            saga = continueSaga(clazz, existingState);
        } catch (Exception ex) {
            LOG.error("Error creating instance for saga type string {}", sagaToContinue);
        }

        return saga;
    }

    /**
     * Starts a new saga by creating an instance and attaching a new saga state.
     */
    private Saga startNewSaga(final Class<? extends Saga> sagaToStart) {
        Saga createdSaga = null;

        try {
            Provider<? extends Saga> sagaProvider = providers.get(sagaToStart);
            createdSaga = sagaProvider.get();
            createdSaga.createNewState();

            SagaState newState = createdSaga.state();
            newState.setSagaId(UUID.randomUUID().toString());
            newState.setType(sagaToStart.getName());
        } catch (Exception ex) {
            LOG.error("Unable to create new instance of saga type {}.", sagaToStart, ex);
        }

        return createdSaga;
    }

    /**
     * Populate internal map to translate between incoming message event type and saga type.
     */
    private void initializeMessageMappings(final Map<Class<? extends Saga>, SagaHandlersMap> handlersMap) {
        for (Map.Entry<Class<? extends Saga>, SagaHandlersMap> entry : handlersMap.entrySet()) {
            Class<? extends Saga> sagaClass = entry.getKey();

            Collection<MessageHandler> sagaHandlers = entry.getValue().messageHandlers();
            for (MessageHandler handler : sagaHandlers) {

                // remember all message types where a completely new saga needs to be started.
                if (handler.getStartsSaga()) {
                    messagesStartingSagas.put(handler.getMessageType(), sagaClass);
                } else {
                    messagesToContinueSaga.put(handler.getMessageType(), sagaClass);
                }
            }
        }
    }

    /**
     * Creates a new provider on demand.
     */
    private final static class ProviderLoader extends CacheLoader<Class<? extends Saga>, Provider<? extends Saga>> {
        private final SagaProviderFactory providerFactory;

        private ProviderLoader(final SagaProviderFactory providerFactory) {
            this.providerFactory = providerFactory;
        }

        @Override
        public Provider<? extends Saga> load(final Class<? extends Saga> key) throws Exception {
            return providerFactory.createProvider(key);
        }
    }
}
TOP

Related Classes of com.codebullets.sagalib.processing.SagaFactory$ProviderLoader

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.