class HerdCommandInvoker extends CommandInvoker {

    @Override
    public <T> T execute(CommandConfig config, Command<T> 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);

            /*
             * 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);

                /*
                 * 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. Job id %s, Process instance id %s, Retries %d.",
                        jobEntity.getId(), jobEntity.getProcessInstanceId(), jobEntity.getRetries()), e);
                    throw e;
                }

                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;
            }
        }
    }

}