/*
* Copyright 2013 Stefan Domnanovits
*
* 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 com.codebullets.sagalib.processing;
import com.codebullets.sagalib.context.ExecutionContext;
import com.codebullets.sagalib.Saga;
import com.codebullets.sagalib.context.NeedContext;
import com.codebullets.sagalib.storage.StateStorage;
import com.codebullets.sagalib.timeout.TimeoutManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Provider;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Perform execution of saga message handling. This class is the execution
* root unit when handling messages as part of an execution strategy.
*/
public class SagaExecutionTask implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(SagaExecutionTask.class);
private final SagaFactory sagaFactory;
private final HandlerInvoker invoker;
private final StateStorage storage;
private final TimeoutManager timeoutManager;
private final Object message;
private final Provider<ExecutionContext> contextProvider;
/**
* Generates a new instance of SagaExecutionTask.
*/
public SagaExecutionTask(final SagaFactory sagaFactory, final HandlerInvoker invoker, final StateStorage storage, final TimeoutManager timeoutManager,
final Object message, final Provider<ExecutionContext> contextProvider) {
this.sagaFactory = sagaFactory;
this.invoker = invoker;
this.storage = storage;
this.timeoutManager = timeoutManager;
this.message = message;
this.contextProvider = contextProvider;
}
/**
* Performs synchronous saga handling of the message provided in ctor.
*
* @throws InvocationTargetException Thrown when invocation of the handler method fails.
* @throws IllegalAccessException Thrown when access to the handler method fails.
*/
public void handle() throws InvocationTargetException, IllegalAccessException {
checkNotNull(message, "Message to handle must not be null.");
handleSagaMessage(message);
}
/**
* Perform handling of a single message.
*/
private void handleSagaMessage(final Object invokeParam) throws InvocationTargetException, IllegalAccessException {
Collection<SagaInstanceDescription> sagaDescriptions = sagaFactory.create(invokeParam);
if (sagaDescriptions.isEmpty()) {
LOG.warn("No saga found to handle message. {}", invokeParam);
} else {
ExecutionContext context = contextProvider.get();
for (SagaInstanceDescription sagaDescription : sagaDescriptions) {
Saga saga = sagaDescription.getSaga();
setSagaExecutionContext(saga, context);
invoker.invoke(saga, invokeParam);
updateStateStorage(sagaDescription);
if (context.dispatchingStopped()) {
LOG.debug("Handler dispatching stopped after invoking saga {}.", sagaDescription.getSaga().getClass().getSimpleName());
break;
}
}
}
}
private void setSagaExecutionContext(final Saga saga, final ExecutionContext context) {
if (saga instanceof NeedContext) {
((NeedContext) saga).setExecutionContext(context);
}
}
/**
* Updates the state storage depending on whether the saga is completed or keeps on running.
*/
private void updateStateStorage(final SagaInstanceDescription description) {
Saga saga = description.getSaga();
// if saga has finished delete existing state and possible timeouts
// if saga has just been created state has never been save and there
// is no need to delete it.
if (saga.isFinished() && !description.isStarting()) {
storage.delete(saga.state().getSagaId());
timeoutManager.cancelTimeouts(saga.state().getSagaId());
} else if (!saga.isFinished()) {
storage.save(saga.state());
}
}
/**
* Similar to {@link #handle()} but intended for execution on any thread.<p/>
* May throw a runtime exception in case something went wrong invoking the target saga message handler.
*/
@Override
public void run() {
try {
handle();
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}