/* * Copyright (c) 2010-2015. 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.messaging.unitofwork; import org.axonframework.common.Assert; import org.axonframework.messaging.Message; import org.axonframework.messaging.unitofwork.UnitOfWork.Phase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayDeque; import java.util.Deque; import java.util.EnumMap; import java.util.LinkedList; import java.util.function.Consumer; import java.util.function.Function; /** * Maintains the context around the processing of a single Message. This class notifies handlers when the Unit of Work * processing the Message transitions to a new {@link Phase}. * * @author Rene de Waele * @since 3.0 */ public class MessageProcessingContext<T extends Message<?>> { private static final Logger LOGGER = LoggerFactory.getLogger(MessageProcessingContext.class); @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private static final Deque EMPTY = new LinkedList<>(); private final EnumMap<Phase, Deque<Consumer<UnitOfWork<T>>>> handlers = new EnumMap<>(Phase.class); private T message; private ExecutionResult executionResult; /** * Creates a new processing context for the given {@code message}. * * @param message The Message that is to be processed. */ public MessageProcessingContext(T message) { this.message = message; } /** * Invoke the handlers in this collection attached to the given {@code phase}. * * @param unitOfWork The Unit of Work that is changing its phase * @param phase The phase for which attached handlers should be invoked */ @SuppressWarnings("unchecked") public void notifyHandlers(UnitOfWork<T> unitOfWork, Phase phase) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Notifying handlers for phase {}", phase.toString()); } Deque<Consumer<UnitOfWork<T>>> l = handlers.getOrDefault(phase, EMPTY); while (!l.isEmpty()) { l.poll().accept(unitOfWork); } } /** * Adds a handler to the collection. Note that the order in which you register the handlers determines the order * in which they will be handled during the various stages of a unit of work. * * @param phase The phase of the unit of work to attach the handler to * @param handler The handler to invoke in the given phase */ public void addHandler(Phase phase, Consumer<UnitOfWork<T>> handler) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Adding handler {} for phase {}", handler.getClass().getName(), phase.toString()); } final Deque<Consumer<UnitOfWork<T>>> consumers = handlers.computeIfAbsent(phase, p -> new ArrayDeque<>()); if (phase.isReverseCallbackOrder()) { consumers.addFirst(handler); } else { consumers.add(handler); } } /** * Set the execution result of processing the current {@link #getMessage() Message}. In case this context has a * previously set ExecutionResult, setting a new result is only allowed if the new result is an exception result. * <p/> * In case the previously set result is also an exception result, the exception in the new execution result is * added to the original exception as a suppressed exception. * * @param executionResult the ExecutionResult of the currently handled Message */ @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public void setExecutionResult(ExecutionResult executionResult) { Assert.state(this.executionResult == null || executionResult.isExceptionResult(), () -> String.format("Cannot change execution result [%s] to [%s] for message [%s].", message, this.executionResult, executionResult)); if (this.executionResult != null && this.executionResult.isExceptionResult()) { this.executionResult.getExceptionResult().addSuppressed(executionResult.getExceptionResult()); } else { this.executionResult = executionResult; } } /** * Get the Message that is being processed in this context. * * @return the Message that is being processed */ public T getMessage() { return message; } /** * Get the result of processing the {@link #getMessage() Message}. If the Message has not been processed yet this * method returns {@code null}. * * @return The result of processing the Message, or {@code null} if the Message hasn't been processed */ public ExecutionResult getExecutionResult() { return executionResult; } /** * Transform the Message being processed using the given operator. * * @param transformOperator The transform operator to apply to the stored message */ public void transformMessage(Function<T, ? extends Message<?>> transformOperator) { message = (T) transformOperator.apply(message); } /** * Reset the processing context. This clears the execution result and map with registered handlers, and replaces * the current Message with the given {@code message}. * * @param message The new message that is being processed */ public void reset(T message) { this.message = message; handlers.clear(); executionResult = null; } }