/*
* 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 java.util.*;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Unit of Work implementation that is able to process a batch of Messages instead of just a single Message.
*
* @author Rene de Waele
* @since 3.0
*/
public class BatchingUnitOfWork<T extends Message<?>> extends AbstractUnitOfWork<T> {
private final List<MessageProcessingContext<T>> processingContexts;
private MessageProcessingContext<T> processingContext;
/**
* Initializes a BatchingUnitOfWork for processing the given batch of {@code messages}.
*
* @param messages batch of messages to process
*/
@SafeVarargs
public BatchingUnitOfWork(T... messages) {
this(Arrays.asList(messages));
}
/**
* Initializes a BatchingUnitOfWork for processing the given list of {@code messages}.
*
* @param messages batch of messages to process
*/
public BatchingUnitOfWork(List<T> messages) {
Assert.isFalse(messages.isEmpty(), () -> "The list of Messages to process is empty");
processingContexts = messages.stream().map(MessageProcessingContext::new).collect(Collectors.toList());
processingContext = processingContexts.get(0);
}
/**
* {@inheritDoc}
* <p>
* <p/>
* This implementation executes the given {@code task} for each of its messages. The return value is the
* result of the last executed task.
*/
@Override
public <R> R executeWithResult(Callable<R> task, RollbackConfiguration rollbackConfiguration) throws Exception {
if (phase() == Phase.NOT_STARTED) {
start();
}
Assert.state(phase() == Phase.STARTED,
() -> String.format("The UnitOfWork has an incompatible phase: %s", phase()));
R result = null;
Exception exception = null;
for (MessageProcessingContext<T> processingContext : processingContexts) {
this.processingContext = processingContext;
try {
result = task.call();
} catch (Exception e) {
if (rollbackConfiguration.rollBackOn(e)) {
rollback(e);
throw e;
}
setExecutionResult(new ExecutionResult(e));
if (exception != null) {
exception.addSuppressed(e);
} else {
exception = e;
}
continue;
}
setExecutionResult(new ExecutionResult(result));
}
commit();
if (exception != null) {
throw exception;
}
return result;
}
/**
* Returns a Map of {@link ExecutionResult} per Message. If the Unit of Work has not been given a task
* to execute, the ExecutionResult is {@code null} for each Message.
*
* @return a Map of ExecutionResult per Message processed by this Unit of Work
*/
public Map<Message<?>, ExecutionResult> getExecutionResults() {
return processingContexts.stream().collect(
Collectors.toMap(MessageProcessingContext::getMessage, MessageProcessingContext::getExecutionResult));
}
@Override
public T getMessage() {
return processingContext.getMessage();
}
@Override
public UnitOfWork<T> transformMessage(Function<T, ? extends Message<?>> transformOperator) {
processingContext.transformMessage(transformOperator);
return this;
}
@Override
public ExecutionResult getExecutionResult() {
return processingContext.getExecutionResult();
}
@Override
protected void notifyHandlers(Phase phase) {
Iterator<MessageProcessingContext<T>> iterator =
phase.isReverseCallbackOrder() ? new LinkedList<>(processingContexts).descendingIterator() :
processingContexts.iterator();
iterator.forEachRemaining(context -> (processingContext = context).notifyHandlers(this, phase));
}
@Override
protected void setRollbackCause(Throwable cause) {
processingContexts.forEach(context -> context.setExecutionResult(new ExecutionResult(cause)));
}
@Override
protected void addHandler(Phase phase, Consumer<UnitOfWork<T>> handler) {
processingContext.addHandler(phase, handler);
}
@Override
protected void setExecutionResult(ExecutionResult executionResult) {
processingContext.setExecutionResult(executionResult);
}
/**
* Get the batch of messages that is being processed (or has been processed) by this unit of work.
*
* @return the message batch
*/
public List<? extends Message<?>> getMessages() {
return processingContexts.stream().map(MessageProcessingContext::getMessage).collect(Collectors.toList());
}
/**
* Checks if the given {@code message} is the last of the batch being processed in this unit of work.
*
* @param message the message to check for
* @return {@code true} if the message is the last of this batch, {@code false} otherwise
*/
public boolean isLastMessage(Message<?> message) {
return processingContexts.get(processingContexts.size() - 1).getMessage().equals(message);
}
}