All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.finra.herd.service.activiti.task.BaseJavaDelegate Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 0.160.0
Show newest version
/*
* 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.task;

import java.util.Collections;
import java.util.stream.Collectors;

import org.activiti.engine.delegate.BpmnError;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
import org.activiti.engine.repository.ProcessDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;

import org.finra.herd.core.HerdStringUtils;
import org.finra.herd.dao.JobDefinitionDao;
import org.finra.herd.dao.helper.HerdStringHelper;
import org.finra.herd.dao.helper.JsonHelper;
import org.finra.herd.dao.helper.XmlHelper;
import org.finra.herd.model.ObjectNotFoundException;
import org.finra.herd.model.dto.ApplicationUser;
import org.finra.herd.model.dto.JobDefinitionAlternateKeyDto;
import org.finra.herd.model.dto.SecurityUserWrapper;
import org.finra.herd.model.jpa.JobDefinitionEntity;
import org.finra.herd.service.ActivitiService;
import org.finra.herd.service.activiti.ActivitiHelper;
import org.finra.herd.service.activiti.ActivitiRuntimeHelper;
import org.finra.herd.service.helper.ConfigurationDaoHelper;
import org.finra.herd.service.helper.HerdErrorInformationExceptionHandler;
import org.finra.herd.service.helper.JobDefinitionDaoHelper;
import org.finra.herd.service.helper.JobDefinitionHelper;
import org.finra.herd.service.helper.UserNamespaceAuthorizationHelper;

/**
 * This class handles the core flow for our Activiti "JavaDelegate" tasks and calls back sub-classes for the actual task implementation. All of our custom tasks
 * should extend this class.
 * 

* WARNING: When Java Delegates make service calls, those service calls should all take place within a new transaction to ensure Activiti can set workflow * variables upon errors and have those workflow variables committed to the database. If they don't take place within a new transaction, it is possible that the * calling code could roll back the entire transaction and the workflow variables wouldn't get updated. Service methods can occur within a new transaction by * annotating the service method with "@Transactional(propagation = Propagation.REQUIRES_NEW)". Note that JUnit invocations of those same services don't require * their own transaction since we usually want JUnits to roll back all their data. For those situations, we can provide an alternate service implementation that * extends the normal service implementation and simply doesn't annotate the service method with the "requires new" annotation. */ public abstract class BaseJavaDelegate implements JavaDelegate { private static final Logger LOGGER = LoggerFactory.getLogger(BaseJavaDelegate.class); // MDC property key. It can be referenced in a log4j.xml configuration. private static final String ACTIVITI_PROCESS_INSTANCE_ID_KEY = "activitiProcessInstanceId"; private static final String USER_ID_KEY = "uid"; private static final String ACTIVITI_LOG_MESSAGE_PREFIX = "HerdTimingLog timingSource=Activiti"; @Autowired protected ActivitiService activitiService; @Autowired protected ConfigurationDaoHelper configurationDaoHelper; @Autowired protected HerdStringHelper daoHelper; @Autowired protected HerdStringHelper herdStringHelper; @Autowired protected JobDefinitionDao jobDefinitionDao; @Autowired protected JobDefinitionDaoHelper jobDefinitionDaoHelper; @Autowired protected JobDefinitionHelper jobDefinitionHelper; @Autowired protected JsonHelper jsonHelper; @Autowired protected UserNamespaceAuthorizationHelper userNamespaceAuthorizationHelper; @Autowired protected XmlHelper xmlHelper; /** * Variable that is set in workflow for the json response. */ public static final String VARIABLE_JSON_RESPONSE = "jsonResponse"; // A variable we use to know whether this class (i.e. sub-classes) have had Spring initialized (e.g. auto-wiring) since we need to do it manually // given that the delegate tasks are created by Activiti as non-Spring beans. The HerdDelegateInterceptor performs the initialization. private boolean springInitialized; @Autowired protected ActivitiHelper activitiHelper; @Autowired protected ActivitiRuntimeHelper activitiRuntimeHelper; @Autowired @Qualifier("herdErrorInformationExceptionHandler") // This is to ensure we get the base class bean rather than any classes that extend it. private HerdErrorInformationExceptionHandler errorInformationExceptionHandler; /** * The execution implementation. Sub-classes should override this method for their specific task implementation. * * @param execution the delegation execution. * * @throws Exception when a problem is encountered. A BpmnError should be thrown when there is a problem that should be handled by the workflow. All other * errors will be considered system errors that will be logged. */ public abstract void executeImpl(DelegateExecution execution) throws Exception; /** * This is what Activiti will call to execute this task. Sub-classes should override the executeImpl method to supply the actual implementation. * * @param execution the execution information. * * @throws Exception if any errors were encountered. */ @Override public final void execute(DelegateExecution execution) throws Exception { long taskBeginTimeMillis = 0; boolean taskSuccessFlag = false; try { // Set the task begin time taskBeginTimeMillis = System.currentTimeMillis(); // Need to clear the security context here since the current thread may have been reused, // which may might have left over its security context. If we do not clear the security // context, any subsequent calls may be restricted by the permissions given // to the previous thread's security context. SecurityContextHolder.clearContext(); // Check if method is not allowed. configurationDaoHelper.checkNotAllowedMethod(this.getClass().getCanonicalName()); // Set the security context per last updater of the current process instance's job definition. ApplicationUser applicationUser = getApplicationUser(execution); setSecurityContext(applicationUser); // Set the MDC property for the Activiti process instance ID and user ID. MDC.put(ACTIVITI_PROCESS_INSTANCE_ID_KEY, "activitiProcessInstanceId=" + execution.getProcessInstanceId()); MDC.put(USER_ID_KEY, "userId=" + (applicationUser.getUserId() == null ? "" : applicationUser.getUserId())); // Log all input variables from the execution (before the execution starts). logInputParameters(execution); // Perform the execution implementation handled in the sub-class. executeImpl(execution); // Set a success status as a workflow variable. activitiRuntimeHelper.setTaskSuccessInWorkflow(execution); // Set the flag to true since there is no exception thrown taskSuccessFlag = true; } catch (Exception ex) { handleException(execution, ex); } finally { // Log the task execution time logTaskExecutionTime(taskBeginTimeMillis, taskSuccessFlag); // Remove the MDC property to ensure they don't accidentally get used by anybody else. MDC.remove(ACTIVITI_PROCESS_INSTANCE_ID_KEY); MDC.remove(USER_ID_KEY); // Clear up the security context. SecurityContextHolder.clearContext(); } } /** * Logs the Activiti task execution time * * @param taskBeginTimeMillis the task begin time in millisecond * @param taskSuccessFlag the success flag for the task */ protected void logTaskExecutionTime(long taskBeginTimeMillis, boolean taskSuccessFlag) { StringBuilder message = new StringBuilder(); // Append the log message prefix. message.append(ACTIVITI_LOG_MESSAGE_PREFIX); // Append the Activiti task name message.append(" task=" + this.getClass().getName()); // Append the task success flag message.append(" success=").append(taskSuccessFlag); // Append response time message.append(" responseTimeMillis=").append(System.currentTimeMillis() - taskBeginTimeMillis); LOGGER.info(message.toString()); } /** * Sets the security context per last updater of the current process instance's job definition. * * @param applicationUser the application user */ protected void setSecurityContext(ApplicationUser applicationUser) { userNamespaceAuthorizationHelper.buildNamespaceAuthorizations(applicationUser); SecurityContextHolder.getContext().setAuthentication(new PreAuthenticatedAuthenticationToken( new SecurityUserWrapper(applicationUser.getUserId(), "", true, true, true, true, Collections.emptyList(), applicationUser), null)); } /** * Retrieves application user per last updater of the current process instance's job definition. * * @param execution the delegate execution * * @return the application user */ protected ApplicationUser getApplicationUser(DelegateExecution execution) { String processDefinitionId = execution.getProcessDefinitionId(); // Get process definition by process definition ID from Activiti. ProcessDefinition processDefinition = activitiService.getProcessDefinitionById(processDefinitionId); // Validate that we retrieved the process definition from Activiti. if (processDefinition == null) { throw new ObjectNotFoundException(String.format("Failed to find Activiti process definition for processDefinitionId=\"%s\".", processDefinitionId)); } // Retrieve the process definition key. String processDefinitionKey = processDefinition.getKey(); // Get the job definition key. JobDefinitionAlternateKeyDto jobDefinitionKey = jobDefinitionHelper.getJobDefinitionKey(processDefinitionKey); // Get the job definition from the Herd repository and validate that it exists. JobDefinitionEntity jobDefinitionEntity = jobDefinitionDaoHelper.getJobDefinitionEntity(jobDefinitionKey.getNamespace(), jobDefinitionKey.getJobName()); // Set the security context per last updater of the job definition. String updatedByUserId = jobDefinitionEntity.getUpdatedBy(); ApplicationUser applicationUser = new ApplicationUser(getClass()); applicationUser.setUserId(updatedByUserId); return applicationUser; } /** * Handles any exception thrown by an Activiti task. * * @param execution The execution which identifies the task. * @param exception The exception that has been thrown * * @throws Exception Some exceptions may choose to bubble up the exception */ protected void handleException(DelegateExecution execution, Exception exception) throws Exception { // Set the error status and stack trace as workflow variables. activitiRuntimeHelper.setTaskErrorInWorkflow(execution, exception.getMessage(), exception); // Continue throwing the original exception and let workflow handle it with a Boundary event handler. if (exception instanceof BpmnError) { throw exception; } // Log the error if the exception should be reported. if (errorInformationExceptionHandler.isReportableError(exception)) { LOGGER.error("{} Unexpected error occurred during task. activitiTaskName=\"{}\"", activitiHelper.getProcessIdentifyingInformation(execution), getClass().getSimpleName(), exception); } } /** * Sets a JSON response object as a workflow variable. * * @param responseObject the JSON object. * @param execution the delegate execution. * * @throws Exception if any problems were encountered. */ public void setJsonResponseAsWorkflowVariable(Object responseObject, DelegateExecution execution) throws Exception { String jsonResponse = jsonHelper.objectToJson(responseObject); setTaskWorkflowVariable(execution, VARIABLE_JSON_RESPONSE, jsonResponse); } public void setJsonResponseAsWorkflowVariable(Object responseObject, String executionId, String activitiId) throws Exception { String jsonResponse = jsonHelper.objectToJson(responseObject); setTaskWorkflowVariable(executionId, activitiId, VARIABLE_JSON_RESPONSE, jsonResponse); } public boolean isSpringInitialized() { return springInitialized; } public void setSpringInitialized(boolean springInitialized) { this.springInitialized = springInitialized; } /** * Sets the workflow variable with task id prefixed. * * @param execution the delegate execution. * @param variableName the variable name * @param variableValue the variable value */ protected void setTaskWorkflowVariable(DelegateExecution execution, String variableName, Object variableValue) { activitiRuntimeHelper.setTaskWorkflowVariable(execution, variableName, variableValue); } protected void setTaskWorkflowVariable(String executionId, String activitiId, String variableName, Object variableValue) { activitiRuntimeHelper.setTaskWorkflowVariable(executionId, activitiId, variableName, variableValue); } /** * Converts the request string to xsd object. * * @param contentType the content type "xml" or "json" * @param requestString the request string * @param xsdClass the xsd class of the object to convert to * @param the type of the returned object. * * @return the request object. */ @SuppressWarnings({"unchecked", "rawtypes"}) protected T getRequestObject(String contentType, String requestString, Class xsdClass) { T request; if (contentType.equalsIgnoreCase("xml")) { try { request = (T) xmlHelper.unmarshallXmlToObject(xsdClass, requestString); } catch (Exception ex) { throw new IllegalArgumentException("\"" + xsdClass.getSimpleName() + "\" must be valid xml string.", ex); } } else if (contentType.equalsIgnoreCase("json")) { try { request = (T) jsonHelper.unmarshallJsonToObject(xsdClass, requestString); } catch (Exception ex) { throw new IllegalArgumentException("\"" + xsdClass.getSimpleName() + "\" must be valid json string.", ex); } } else { throw new IllegalArgumentException("\"ContentType\" must be a valid value of either \"xml\" or \"json\"."); } return request; } /** * Loops through all process variables and logs them. * * @param execution the execution information */ protected void logInputParameters(DelegateExecution execution) { String loggingText = execution.getVariables().entrySet().stream().map(entry -> entry.getKey() + "=" + jsonHelper.objectToJson(entry.getValue())) .collect(Collectors.joining(" ")); LOGGER.info("{} Input parameters for {}: {}", activitiHelper.getProcessIdentifyingInformation(execution), this.getClass().getName(), HerdStringUtils.sanitizeLogText(loggingText)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy