/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FieldExtension;
import org.activiti.bpmn.model.ImplementationType;
import org.activiti.bpmn.model.ServiceTask;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.jobexecutor.TimerExecuteNestedActivityJobHandler;
import org.activiti.engine.runtime.ProcessInstance;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.finra.herd.model.ObjectNotFoundException;
import org.finra.herd.model.api.xml.Job;
import org.finra.herd.model.api.xml.JobDefinition;
import org.finra.herd.model.api.xml.Parameter;
import org.finra.herd.model.jpa.JobDefinitionEntity;
import org.finra.herd.service.activiti.task.BaseJavaDelegate;
import org.finra.herd.service.activiti.task.HerdActivitiServiceTaskTest;
import org.finra.herd.service.activiti.task.MockJavaDelegate;
/**
* Tests the ActivitiDelegateTest class.
*/
public class ActivitiDelegateTest extends HerdActivitiServiceTaskTest
{
/**
* This method tests the timer execution in a workflow.
*/
@Test
public void testTimerJob() throws Exception
{
// Create and start the workflow.
jobDefinitionServiceTestHelper.createJobDefinition(ACTIVITI_XML_HERD_TIMER_WITH_CLASSPATH);
Job job = jobService.createAndStartJob(jobServiceTestHelper.createJobCreateRequest(TEST_ACTIVITI_NAMESPACE_CD, TEST_ACTIVITI_JOB_NAME));
assertNotNull(job);
// This workflow would normally automatically start a timer which would eventually complete the workflow, however, since
// this test method is running within our own transaction, the timer would never go off since it is run in a different thread which is outside of
// our transaction which didn't commit yet. As a result, we need to manually run the timer job to simulate what would happen if the timer
// went off by itself.
org.activiti.engine.runtime.Job timer = activitiManagementService.createJobQuery().processInstanceId(job.getId()).timers().singleResult();
if (timer != null)
{
activitiManagementService.executeJob(timer.getId());
}
}
/**
* This is an alternative way of running the timer test above which doesn't go through the herd job infrastructure, but rather ensures that Activiti can run
* the timer job successfully. This is accomplished by invoking Activiti API's directly and not running within our own transaction.
*
* @throws Exception
*/
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testTimerJobLowLevelActiviti() throws Exception
{
try
{// Read workflow XML from classpath and deploy it.
activitiRepositoryService.createDeployment().addClasspathResource(ACTIVITI_XML_HERD_TIMER).deploy();
// Set workflow variables for no other reason than to make sure it doesn't cause any problems.
Map<String, Object> variables = new HashMap<>();
variables.put("key1", "value1");
// Execute the workflow.
ProcessInstance processInstance = activitiRuntimeService.startProcessInstanceByKey("testNamespace.testHerdWorkflow", variables);
// Wait a reasonable amount of time for the process to finish.
waitUntilAllProcessCompleted();
// Get the history for the process and ensure the workflow completed.
HistoricProcessInstance historicProcessInstance =
activitiHistoryService.createHistoricProcessInstanceQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
Assert.assertNotNull(historicProcessInstance);
Assert.assertNotNull(historicProcessInstance.getEndTime());
}
finally
{
deleteActivitiDeployments();
}
}
/**
* This method tests the scenario when an error that is not workflow related is throws while workflow is executing an Async type task like Timer. This error
* is logged as ERROR.
*/
@Test(expected = ActivitiException.class)
public void testActivitiReportableError() throws Exception
{
BpmnModel bpmnModel = getBpmnModelForXmlResource(ACTIVITI_XML_HERD_TIMER_WITH_CLASSPATH);
ServiceTask serviceTask = (ServiceTask) bpmnModel.getProcesses().get(0).getFlowElement("servicetask1");
FieldExtension exceptionField = new FieldExtension();
exceptionField.setFieldName("exceptionToThrow");
exceptionField.setExpression("${exceptionToThrow}");
serviceTask.getFieldExtensions().add(exceptionField);
jobDefinitionServiceTestHelper.createJobDefinitionForActivitiXml(getActivitiXmlFromBpmnModel(bpmnModel));
List<Parameter> parameters = new ArrayList<>();
Parameter parameter = new Parameter("exceptionToThrow", MockJavaDelegate.EXCEPTION_BPMN_ERROR);
parameters.add(parameter);
Job job = jobService.createAndStartJob(jobServiceTestHelper.createJobCreateRequest(TEST_ACTIVITI_NAMESPACE_CD, TEST_ACTIVITI_JOB_NAME, parameters));
org.activiti.engine.runtime.Job timer = activitiManagementService.createJobQuery().processInstanceId(job.getId()).timers().singleResult();
if (timer != null)
{
executeWithoutLogging(Arrays.asList(ActivitiRuntimeHelper.class, TimerExecuteNestedActivityJobHandler.class), () -> {
activitiManagementService.executeJob(timer.getId());
});
}
}
/**
* This method tests the scenario when an workflow related error is throws while workflow is executing an Async type task like Timer. This error is logged
* as WARN.
*/
@Test(expected = ActivitiException.class)
public void testActivitiUnReportableError() throws Exception
{
BpmnModel bpmnModel = getBpmnModelForXmlResource(ACTIVITI_XML_HERD_TIMER_WITH_CLASSPATH);
ServiceTask serviceTask = (ServiceTask) bpmnModel.getProcesses().get(0).getFlowElement("servicetask1");
serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION);
serviceTask.setImplementation("${BeanNotAvailable}");
jobDefinitionServiceTestHelper.createJobDefinitionForActivitiXml(getActivitiXmlFromBpmnModel(bpmnModel));
Job job = jobService.createAndStartJob(jobServiceTestHelper.createJobCreateRequest(TEST_ACTIVITI_NAMESPACE_CD, TEST_ACTIVITI_JOB_NAME, null));
org.activiti.engine.runtime.Job timer = activitiManagementService.createJobQuery().processInstanceId(job.getId()).timers().singleResult();
if (timer != null)
{
executeWithoutLogging(TimerExecuteNestedActivityJobHandler.class, () -> {
activitiManagementService.executeJob(timer.getId());
});
}
}
/**
* This method tests the scenario where a java delegate is not populated again with spring beans.
*/
@Test
public void testDelegateSpringBeansNotPopulatedAgain() throws Exception
{
BpmnModel bpmnModel = getBpmnModelForXmlResource(ACTIVITI_XML_HERD_WORKFLOW);
ServiceTask serviceTask = (ServiceTask) bpmnModel.getProcesses().get(0).getFlowElement("servicetask1");
serviceTask.setImplementation(MockJavaDelegate.class.getCanonicalName());
serviceTask.getFieldExtensions().clear();
// Define the job definition
jobDefinitionServiceTestHelper.createJobDefinitionForActivitiXml(getActivitiXmlFromBpmnModel(bpmnModel));
// Executing the job twice so that the same JavaDelegate object is used and spring beans are not wired again.
jobService.createAndStartJob(jobServiceTestHelper.createJobCreateRequest(TEST_ACTIVITI_NAMESPACE_CD, TEST_ACTIVITI_JOB_NAME));
jobService.createAndStartJob(jobServiceTestHelper.createJobCreateRequest(TEST_ACTIVITI_NAMESPACE_CD, TEST_ACTIVITI_JOB_NAME));
}
/**
* This method tests the scenario where a RuntimeException occurs but not thrown by java delegate.
*/
@Test
public void testDelegateRuntimeError() throws Exception
{
BpmnModel bpmnModel = getBpmnModelForXmlResource(ACTIVITI_XML_HERD_WORKFLOW);
ServiceTask serviceTask = (ServiceTask) bpmnModel.getProcesses().get(0).getFlowElement("servicetask1");
serviceTask.setImplementation(MockJavaDelegate.class.getCanonicalName());
FieldExtension exceptionField = new FieldExtension();
exceptionField.setFieldName("exceptionToThrow");
exceptionField.setExpression("${exceptionToThrow}");
serviceTask.getFieldExtensions().clear();
serviceTask.getFieldExtensions().add(exceptionField);
List<Parameter> parameters = new ArrayList<>();
Parameter parameter = new Parameter("exceptionToThrow", MockJavaDelegate.EXCEPTION_RUNTIME);
parameters.add(parameter);
executeWithoutLogging(Arrays.asList(ActivitiRuntimeHelper.class, BaseJavaDelegate.class), () -> {
jobServiceTestHelper.createJobFromActivitiXml(getActivitiXmlFromBpmnModel(bpmnModel), parameters);
});
}
/**
* This method tests the scenario where a process definition is not defined.
*/
@Test(expected = ObjectNotFoundException.class)
public void testCreateProcessCommandProcessNotDefined() throws Exception
{
// Create the job with Activiti.
jobService.createAndStartJob(jobServiceTestHelper.createJobCreateRequest(TEST_ACTIVITI_NAMESPACE_CD, "test_process_not_defined"));
}
/**
* This method tests the scenario where a process definition is suspended in Activiti.
*/
@Test(expected = IllegalArgumentException.class)
public void testCreateProcessCommandProcessSuspended() throws Exception
{
// Define the job definition
JobDefinition jobDefinition = jobDefinitionServiceTestHelper.createJobDefinition(ACTIVITI_XML_HERD_WORKFLOW);
JobDefinitionEntity jobDefinitionEntity = jobDefinitionDao.getJobDefinitionByAltKey(jobDefinition.getNamespace(), jobDefinition.getJobName());
// Suspend the job definition with activiti api
activitiRepositoryService.suspendProcessDefinitionById(jobDefinitionEntity.getActivitiId());
jobService.createAndStartJob(jobServiceTestHelper.createJobCreateRequest(TEST_ACTIVITI_NAMESPACE_CD, TEST_ACTIVITI_JOB_NAME));
}
/**
* This method tests when wrong class name is used in service task, the process instance is created.
*/
@Test
public void testDelegateWrongClass() throws Exception
{
BpmnModel bpmnModel = getBpmnModelForXmlResource(ACTIVITI_XML_HERD_WORKFLOW);
ServiceTask serviceTask = (ServiceTask) bpmnModel.getProcesses().get(0).getFlowElement("servicetask1");
serviceTask.setImplementation("ClassDoesNotExist");
serviceTask.getFieldExtensions().clear();
// Run a job with Activiti XML that will start cluster.
try
{
jobServiceTestHelper.createJobFromActivitiXml(getActivitiXmlFromBpmnModel(bpmnModel), null);
fail();
}
catch (Exception e)
{
assertEquals(ActivitiException.class, e.getClass());
assertEquals("couldn't instantiate class ClassDoesNotExist", e.getMessage());
}
}
}