/* * 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.eventhandling.saga; import org.axonframework.common.Assert; import org.axonframework.common.IdentifierFactory; import org.axonframework.eventhandling.EventHandlerInvoker; import org.axonframework.eventhandling.EventMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashSet; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; import static java.lang.String.format; /** * Abstract implementation of the SagaManager interface that provides basic functionality required by most SagaManager * implementations. Provides support for Saga lifecycle management and asynchronous handling of events. * * @author Allard Buijze * @since 0.7 */ public abstract class AbstractSagaManager<T> implements EventHandlerInvoker { private static final Logger logger = LoggerFactory.getLogger(AbstractSagaManager.class); private final SagaRepository<T> sagaRepository; private final Class<T> sagaType; private volatile boolean suppressExceptions = true; private final Supplier<T> sagaFactory; /** * Initializes the SagaManager with the given {@code sagaRepository}. * * @param sagaType The type of Saga Managed by this instance * @param sagaRepository The repository providing the saga instances. * @param sagaFactory The factory responsible for creating new Saga instances */ protected AbstractSagaManager(Class<T> sagaType, SagaRepository<T> sagaRepository, Supplier<T> sagaFactory) { this.sagaType = sagaType; this.sagaFactory = sagaFactory; Assert.notNull(sagaRepository, () -> "sagaRepository may not be null"); this.sagaRepository = sagaRepository; } @Override public Object handle(EventMessage<?> event) throws Exception { Set<AssociationValue> associationValues = extractAssociationValues(event); Set<Saga<T>> sagas = associationValues.stream().flatMap(associationValue -> sagaRepository.find(associationValue).stream()) .map(sagaRepository::load).filter(s -> s != null).filter(Saga::isActive) .collect(Collectors.toCollection(HashSet<Saga<T>>::new)); boolean sagaOfTypeInvoked = false; for (Saga<T> saga : sagas) { if (doInvokeSaga(event, saga)) { sagaOfTypeInvoked = true; } } SagaInitializationPolicy initializationPolicy = getSagaCreationPolicy(event); if (initializationPolicy.getCreationPolicy() == SagaCreationPolicy.ALWAYS || (!sagaOfTypeInvoked && initializationPolicy.getCreationPolicy() == SagaCreationPolicy.IF_NONE_FOUND)) { startNewSaga(event, initializationPolicy.getInitialAssociationValue()); } return null; } private void startNewSaga(EventMessage event, AssociationValue associationValue) { Saga<T> newSaga = sagaRepository.createInstance(IdentifierFactory.getInstance().generateIdentifier(), sagaFactory); newSaga.getAssociationValues().add(associationValue); doInvokeSaga(event, newSaga); } /** * Returns the Saga Initialization Policy for a Saga of the given {@code sagaType} and {@code event}. This * policy provides the conditions to create new Saga instance, as well as the initial association of that saga. * * @param event The Event that is being dispatched to Saga instances * @return the initialization policy for the Saga */ protected abstract SagaInitializationPolicy getSagaCreationPolicy(EventMessage<?> event); /** * Extracts the AssociationValues from the given {@code event} as relevant for a Saga of given * {@code sagaType}. A single event may be associated with multiple values. * * @param event The event containing the association information * @return the AssociationValues indicating which Sagas should handle given event */ protected abstract Set<AssociationValue> extractAssociationValues(EventMessage<?> event); private boolean doInvokeSaga(EventMessage event, Saga<T> saga) { try { return saga.handle(event); } catch (Exception e) { if (suppressExceptions) { logger.error(format("An exception occurred while a Saga [%s] was handling an Event [%s]:", saga.getClass().getSimpleName(), event.getPayloadType().getSimpleName()), e); return true; } else { throw e; } } } /** * Sets whether or not to suppress any exceptions that are cause by invoking Sagas. When suppressed, exceptions are * logged. Defaults to {@code true}. * * @param suppressExceptions whether or not to suppress exceptions from Sagas. */ public void setSuppressExceptions(boolean suppressExceptions) { this.suppressExceptions = suppressExceptions; } /** * Returns the class of Saga managed by this SagaManager * * @return the managed saga type */ public Class<T> getSagaType() { return sagaType; } }