org.finra.herd.service.activiti.HerdCommandInvoker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of herd-service Show documentation
Show all versions of herd-service Show documentation
This project contains the business service code. This is a classic service tier where business logic is defined along with it's associated
transaction management configuration.
/*
* Copyright 2015 herd contributors
*
* 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.finra.herd.service.activiti;
import java.lang.reflect.Field;
import org.activiti.engine.impl.cmd.ExecuteAsyncJobCmd;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandConfig;
import org.activiti.engine.impl.interceptor.CommandInvoker;
import org.activiti.engine.impl.persistence.entity.JobEntity;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
/**
* A custom herd Activiti command invoker that extends the default standard Activiti command invoker so we can handle exceptions being thrown in a custom way.
*/
@Component
public class HerdCommandInvoker extends CommandInvoker
{
private static final Logger LOGGER = LoggerFactory.getLogger(HerdCommandInvoker.class);
private static final String CANNOT_CREATE_TRANSACTION_EXCEPTION = "CannotCreateTransactionException";
@Override
public T execute(CommandConfig config, Command command)
{
LOGGER.debug("command=\"{}\"", command.getClass().getName());
try
{
// Perform the normal execution.
return super.execute(config, command);
}
catch (Exception e)
{
LOGGER.warn(String.format("HerdCommandInvoker caught an exception."), e);
/*
* If the exception is unable to create transaction due to JDBC connection issue,
* throw out the exception, then let the Activiti engine take care of retries
*/
if (StringUtils.containsIgnoreCase(ExceptionUtils.getMessage(e), CANNOT_CREATE_TRANSACTION_EXCEPTION))
{
LOGGER.warn(String.format("HerdCommandInvoker caught a CannotCreateTransactionException."), e);
throw e;
}
/*
* Attempt to handle exception based on the command.
* If we bubble the exception up here, the transaction will be rolled back and all variables which were not committed will not be persisted.
* The problem with swallowing the exception, however, is that the exception message is not persisted automatically. To get around it, we must save
* the exception message and stacktrace into a JobEntity which is associated with the current execution.
*/
if (command instanceof ExecuteAsyncJobCmd)
{
/*
* ExecuteAsyncJobCmd is executed when a task is asynchronous.
* Save the exception information in the command's JobEntity
*/
ExecuteAsyncJobCmd executeAsyncJobCmd = (ExecuteAsyncJobCmd) command;
JobEntity jobEntity = getJobEntity(executeAsyncJobCmd);
jobEntity.setExceptionMessage(ExceptionUtils.getMessage(e));
jobEntity.setExceptionStacktrace(ExceptionUtils.getStackTrace(e));
return null;
}
else
{
/*
* We do not know how to handle any other commands, so just bubble it up and let Activiti's default mechanism kick in.
*/
throw e;
}
}
}
/**
* Gets the JobEntity from the given ExecuteAsyncJobCmd.
*
* @param executeAsyncJobCmd The ExecuteAsyncJobCmd
*
* @return The JobEntity
*/
private JobEntity getJobEntity(ExecuteAsyncJobCmd executeAsyncJobCmd)
{
/*
* Unfortunately, ExecuteAsyncJobCmd does not provide an accessible method to get the JobEntity stored within it.
* We use reflection to force the value out of the object.
* Also, we cannot simply get the entity and update it. We must retrieve it through the entity manager so it registers in Activiti's persistent object
* cache. This way when the transaction commits, Activiti is aware of any changes in the JobEntity and persists them correctly.
*/
try
{
Field field = ExecuteAsyncJobCmd.class.getDeclaredField("job");
ReflectionUtils.makeAccessible(field);
String jobId = ((JobEntity) ReflectionUtils.getField(field, executeAsyncJobCmd)).getId();
return Context.getCommandContext().getJobEntityManager().findJobById(jobId);
}
catch (NoSuchFieldException | SecurityException e)
{
/*
* This exception should not happen.
*/
throw new IllegalStateException(e);
}
}
}