/* 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.activiti.engine.test.bpmn.event.timer; import java.io.ByteArrayInputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.concurrent.Callable; import org.activiti.engine.impl.cmd.CancelJobsCmd; import org.activiti.engine.impl.interceptor.CommandExecutor; import org.activiti.engine.impl.test.PluggableActivitiTestCase; import org.activiti.engine.impl.util.IoUtil; import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.runtime.Job; import org.activiti.engine.runtime.JobQuery; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.runtime.ProcessInstanceQuery; import org.activiti.engine.test.Deployment; /** * @author Joram Barrez */ public class StartTimerEventTest extends PluggableActivitiTestCase { @Deployment public void testDurationStartTimerEvent() throws Exception { // Set the clock fixed Date startTime = new Date(); // After process start, there should be timer created JobQuery jobQuery = managementService.createJobQuery(); assertEquals(1, jobQuery.count()); // After setting the clock to time '50minutes and 5 seconds', the second timer should fire processEngineConfiguration.getClock().setCurrentTime(new Date(startTime.getTime() + ((50 * 60 * 1000) + 5000))); waitForJobExecutorToProcessAllJobs(5000L, 25L); List<ProcessInstance> pi = runtimeService.createProcessInstanceQuery().processDefinitionKey("startTimerEventExample") .list(); assertEquals(1, pi.size()); assertEquals(0, jobQuery.count()); } @Deployment public void testFixedDateStartTimerEvent() throws Exception { // After process start, there should be timer created JobQuery jobQuery = managementService.createJobQuery(); assertEquals(1, jobQuery.count()); processEngineConfiguration.getClock().setCurrentTime(new SimpleDateFormat("dd/MM/yyyy hh:mm:ss").parse("15/11/2036 11:12:30")); waitForJobExecutorToProcessAllJobs(5000L, 25L); List<ProcessInstance> pi = runtimeService.createProcessInstanceQuery().processDefinitionKey("startTimerEventExample").list(); assertEquals(1, pi.size()); assertEquals(0, jobQuery.count()); } // FIXME: This test likes to run in an endless loop when invoking the waitForJobExecutorOnCondition method @Deployment public void testCycleDateStartTimerEvent() throws Exception { processEngineConfiguration.getClock().setCurrentTime(new Date()); // After process start, there should be timer created JobQuery jobQuery = managementService.createJobQuery(); assertEquals(1, jobQuery.count()); final ProcessInstanceQuery piq = runtimeService.createProcessInstanceQuery().processDefinitionKey("startTimerEventExample"); moveByMinutes(5); waitForJobExecutorOnCondition(10000, 500, new Callable<Boolean>() { public Boolean call() throws Exception { return 1 == piq.count(); } }); assertEquals(1, jobQuery.count()); moveByMinutes(5); waitForJobExecutorOnCondition(10000, 500, new Callable<Boolean>() { public Boolean call() throws Exception { return 2 == piq.count(); } }); assertEquals(1, jobQuery.count()); //have to manually delete pending timer cleanDB(); } private void moveByMinutes(int minutes) throws Exception { processEngineConfiguration.getClock().setCurrentTime(new Date(processEngineConfiguration.getClock().getCurrentTime().getTime() + ((minutes * 60 * 1000) + 5000))); } @Deployment public void testCycleWithLimitStartTimerEvent() throws Exception { processEngineConfiguration.getClock().setCurrentTime(new Date()); // After process start, there should be timer created JobQuery jobQuery = managementService.createJobQuery(); assertEquals(1, jobQuery.count()); moveByMinutes(6); managementService.executeJob(managementService.createJobQuery().singleResult().getId()); assertEquals(1, jobQuery.count()); moveByMinutes(6); managementService.executeJob(managementService.createJobQuery().singleResult().getId()); assertEquals(0, jobQuery.count()); } @Deployment public void testExpressionStartTimerEvent() throws Exception { // ACT-1415: fixed start-date is an expression JobQuery jobQuery = managementService.createJobQuery(); assertEquals(1, jobQuery.count()); processEngineConfiguration.getClock().setCurrentTime(new SimpleDateFormat("dd/MM/yyyy hh:mm:ss").parse("15/11/2036 11:12:30")); waitForJobExecutorToProcessAllJobs(5000L, 25L); List<ProcessInstance> pi = runtimeService.createProcessInstanceQuery().processDefinitionKey("startTimerEventExample") .list(); assertEquals(1, pi.size()); assertEquals(0, jobQuery.count()); } @Deployment public void testVersionUpgradeShouldCancelJobs() throws Exception { processEngineConfiguration.getClock().setCurrentTime(new Date()); // After process start, there should be timer created JobQuery jobQuery = managementService.createJobQuery(); assertEquals(1, jobQuery.count()); //we deploy new process version, with some small change String process = new String(IoUtil.readInputStream(getClass().getResourceAsStream("StartTimerEventTest.testVersionUpgradeShouldCancelJobs.bpmn20.xml"), "")).replaceAll("beforeChange","changed"); String id = repositoryService.createDeployment().addInputStream("StartTimerEventTest.testVersionUpgradeShouldCancelJobs.bpmn20.xml", new ByteArrayInputStream(process.getBytes())).deploy().getId(); assertEquals(1, jobQuery.count()); moveByMinutes(5); waitForJobExecutorOnCondition(10000, 500, new Callable<Boolean>() { public Boolean call() throws Exception { //we check that correct version was started ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processDefinitionKey("startTimerEventExample").singleResult(); if(processInstance != null) { String pi = processInstance.getProcessInstanceId(); return "changed".equals(runtimeService.getActiveActivityIds(pi).get(0)); }else { return false; } } }); assertEquals(1, jobQuery.count()); cleanDB(); repositoryService.deleteDeployment(id, true); } @Deployment public void testTimerShouldNotBeRecreatedOnDeploymentCacheReboot() { // Just to be sure, I added this test. Sounds like something that could easily happen // when the order of deploy/parsing is altered. // After process start, there should be timer created JobQuery jobQuery = managementService.createJobQuery(); assertEquals(1, jobQuery.count()); // Reset deployment cache processEngineConfiguration.getProcessDefinitionCache().clear(); // Start one instance of the process definition, this will trigger a cache reload runtimeService.startProcessInstanceByKey("startTimer"); // No new jobs should have been created assertEquals(1, jobQuery.count()); } // Test for ACT-1533 public void testTimerShouldNotBeRemovedWhenUndeployingOldVersion() throws Exception { // Deploy test process String processXml = new String(IoUtil.readInputStream(getClass().getResourceAsStream("StartTimerEventTest.testTimerShouldNotBeRemovedWhenUndeployingOldVersion.bpmn20.xml"), "")); String firstDeploymentId = repositoryService.createDeployment().addInputStream("StartTimerEventTest.testVersionUpgradeShouldCancelJobs.bpmn20.xml", new ByteArrayInputStream(processXml.getBytes())).deploy().getId(); // After process start, there should be timer created JobQuery jobQuery = managementService.createJobQuery(); assertEquals(1, jobQuery.count()); //we deploy new process version, with some small change String processChanged = processXml.replaceAll("beforeChange","changed"); String secondDeploymentId = repositoryService.createDeployment().addInputStream("StartTimerEventTest.testVersionUpgradeShouldCancelJobs.bpmn20.xml", new ByteArrayInputStream(processChanged.getBytes())).deploy().getId(); assertEquals(1, jobQuery.count()); // Remove the first deployment repositoryService.deleteDeployment(firstDeploymentId, true); // The removal of an old version should not affect timer deletion // ACT-1533: this was a bug, and the timer was deleted! assertEquals(1, jobQuery.count()); // Cleanup cleanDB(); repositoryService.deleteDeployment(secondDeploymentId, true); } public void testOldJobsDeletedOnRedeploy() { for (int i=0; i<3; i++) { repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/timer/StartTimerEventTest.testOldJobsDeletedOnRedeploy.bpmn20.xml") .deploy(); assertEquals(i+1, repositoryService.createDeploymentQuery().count()); assertEquals(i+1, repositoryService.createProcessDefinitionQuery().count()); assertEquals(1, managementService.createJobQuery().count()); } // Cleanup for (ProcessDefinition processDefinition : repositoryService.createProcessDefinitionQuery().processDefinitionKey("timer").orderByProcessDefinitionVersion().desc().list()) { repositoryService.deleteDeployment(processDefinition.getDeploymentId(), true); } assertEquals(0, managementService.createJobQuery().count()); } public void testTimersRecreatedOnDeploymentDelete() { // v1 has timer // v2 has no timer // v3 has no timer // v4 has no timer // Deploy v1 String deployment1 = repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/timer/StartTimerEventTest.testTimersRecreatedOnDeploymentDelete_v1.bpmn20.xml") .deploy().getId(); assertEquals(1, repositoryService.createDeploymentQuery().count()); assertEquals(1, repositoryService.createProcessDefinitionQuery().count()); assertEquals(1, managementService.createJobQuery().count()); // Deploy v2: no timer -> previous should be deleted String deployment2 = repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/timer/StartTimerEventTest.testTimersRecreatedOnDeploymentDelete_v2.bpmn20.xml") .deploy().getId(); assertEquals(2, repositoryService.createDeploymentQuery().count()); assertEquals(2, repositoryService.createProcessDefinitionQuery().count()); assertEquals(0, managementService.createJobQuery().count()); // Deploy v3: no timer String deployment3 = repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/timer/StartTimerEventTest.testTimersRecreatedOnDeploymentDelete_v3.bpmn20.xml") .deploy().getId(); assertEquals(3, repositoryService.createDeploymentQuery().count()); assertEquals(3, repositoryService.createProcessDefinitionQuery().count()); assertEquals(0, managementService.createJobQuery().count()); // Deploy v4: no timer String deployment4 = repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/timer/StartTimerEventTest.testTimersRecreatedOnDeploymentDelete_v4.bpmn20.xml") .deploy().getId(); assertEquals(4, repositoryService.createDeploymentQuery().count()); assertEquals(4, repositoryService.createProcessDefinitionQuery().count()); assertEquals(1, managementService.createJobQuery().count()); // Delete v4 -> V3 active. No timer active anymore (v3 doesn't have a timer) repositoryService.deleteDeployment(deployment4, true); assertEquals(3, repositoryService.createDeploymentQuery().count()); assertEquals(3, repositoryService.createProcessDefinitionQuery().count()); assertEquals(0, managementService.createJobQuery().count()); // Delete v2 --> V3 still active, nothing changed there repositoryService.deleteDeployment(deployment2, true); assertEquals(2, repositoryService.createDeploymentQuery().count()); assertEquals(2, repositoryService.createProcessDefinitionQuery().count()); assertEquals(0, managementService.createJobQuery().count()); // v3 is still active // Delete v3 -> fallback to v1 repositoryService.deleteDeployment(deployment3, true); assertEquals(1, repositoryService.createDeploymentQuery().count()); assertEquals(1, repositoryService.createProcessDefinitionQuery().count()); assertEquals(1, managementService.createJobQuery().count()); // Cleanup for (ProcessDefinition processDefinition : repositoryService.createProcessDefinitionQuery().processDefinitionKey("timer").orderByProcessDefinitionVersion().desc().list()) { repositoryService.deleteDeployment(processDefinition.getDeploymentId(), true); } assertEquals(0, managementService.createJobQuery().count()); } // Same test as above, but now with tenants public void testTimersRecreatedOnDeploymentDeleteWithTenantId() { // Deploy 4 versions without tenantId for (int i=1; i<=4; i++) { repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/timer/StartTimerEventTest.testTimersRecreatedOnDeploymentDelete_v" + i + ".bpmn20.xml") .deploy(); } String testTenant = "Activiti-tenant"; // Deploy v1 String deployment1 = repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/timer/StartTimerEventTest.testTimersRecreatedOnDeploymentDelete_v1.bpmn20.xml") .tenantId(testTenant) .deploy().getId(); assertEquals(1, repositoryService.createDeploymentQuery().deploymentTenantId(testTenant).count()); assertEquals(1, repositoryService.createProcessDefinitionQuery().processDefinitionTenantId(testTenant).count()); assertEquals(1, managementService.createJobQuery().jobTenantId(testTenant).count()); // Deploy v2: no timer -> previous should be deleted String deployment2 = repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/timer/StartTimerEventTest.testTimersRecreatedOnDeploymentDelete_v2.bpmn20.xml") .tenantId(testTenant) .deploy().getId(); assertEquals(2, repositoryService.createDeploymentQuery().deploymentTenantId(testTenant).count()); assertEquals(2, repositoryService.createProcessDefinitionQuery().processDefinitionTenantId(testTenant).count()); assertEquals(0, managementService.createJobQuery().jobTenantId(testTenant).count()); // Deploy v3: no timer String deployment3 = repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/timer/StartTimerEventTest.testTimersRecreatedOnDeploymentDelete_v3.bpmn20.xml") .tenantId(testTenant) .deploy().getId(); assertEquals(3, repositoryService.createDeploymentQuery().deploymentTenantId(testTenant).count()); assertEquals(3, repositoryService.createProcessDefinitionQuery().processDefinitionTenantId(testTenant).count()); assertEquals(0, managementService.createJobQuery().jobTenantId(testTenant).count()); // Deploy v4: no timer String deployment4 = repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/timer/StartTimerEventTest.testTimersRecreatedOnDeploymentDelete_v4.bpmn20.xml") .tenantId(testTenant) .deploy().getId(); assertEquals(4, repositoryService.createDeploymentQuery().deploymentTenantId(testTenant).count()); assertEquals(4, repositoryService.createProcessDefinitionQuery().processDefinitionTenantId(testTenant).count()); assertEquals(1, managementService.createJobQuery().jobTenantId(testTenant).count()); // Delete v4 -> V3 active. No timer active anymore (v3 doesn't have a timer) repositoryService.deleteDeployment(deployment4, true); assertEquals(3, repositoryService.createDeploymentQuery().deploymentTenantId(testTenant).count()); assertEquals(3, repositoryService.createProcessDefinitionQuery().processDefinitionTenantId(testTenant).count()); assertEquals(0, managementService.createJobQuery().jobTenantId(testTenant).count()); // Delete v2 --> V3 still active, nothing changed there repositoryService.deleteDeployment(deployment2, true); assertEquals(2, repositoryService.createDeploymentQuery().deploymentTenantId(testTenant).count()); assertEquals(2, repositoryService.createProcessDefinitionQuery().processDefinitionTenantId(testTenant).count()); assertEquals(0, managementService.createJobQuery().jobTenantId(testTenant).count()); // Delete v3 -> fallback to v1 repositoryService.deleteDeployment(deployment3, true); assertEquals(1, repositoryService.createDeploymentQuery().deploymentTenantId(testTenant).count()); assertEquals(1, repositoryService.createProcessDefinitionQuery().processDefinitionTenantId(testTenant).count()); assertEquals(1, managementService.createJobQuery().jobTenantId(testTenant).count()); // Cleanup for (ProcessDefinition processDefinition : repositoryService.createProcessDefinitionQuery().processDefinitionKey("timer").orderByProcessDefinitionVersion().desc().list()) { repositoryService.deleteDeployment(processDefinition.getDeploymentId(), true); } assertEquals(0, managementService.createJobQuery().count()); } // Can't use @Deployment, we need to control the clock very strict to have a good test public void testMultipleStartEvents() { // Human time (GMT): Tue, 10 May 2016 18:50:01 GMT Date startTime = new Date(1462906201000L); processEngineConfiguration.getClock().setCurrentTime(startTime); String deploymentId = repositoryService.createDeployment() .addClasspathResource("org/activiti/engine/test/bpmn/event/timer/StartTimerEventTest.testMultipleStartEvents.bpmn20.xml") .deploy().getId(); // After deployment, should have 4 jobs for the 4 timer events assertEquals(4, managementService.createJobQuery().count()); assertEquals(0, managementService.createJobQuery().executable().count()); // Path A : triggered at start + 10 seconds (18:50:11) (R2) // Path B: triggered at start + 5 seconds (18:50:06) (R3) // Path C: triggered at start + 15 seconds (18:50:16) (R1) // path D: triggerd at 18:50:20 (Cron) // Moving 7 seconds (18:50:08) should trigger one timer (the second start timer in the process diagram) Date newDate = new Date(startTime.getTime() + (7 * 1000)); processEngineConfiguration.getClock().setCurrentTime(newDate); List<Job> executableTimers = managementService.createJobQuery().executable().list(); assertEquals(1,executableTimers.size()); executeJobs(executableTimers); validateTaskCounts(0, 1, 0, 0); assertEquals(4, managementService.createJobQuery().count()); assertEquals(0, managementService.createJobQuery().executable().count()); // New situation: // Path A : triggered at start + 10 seconds (18:50:11) (R2) // Path B: triggered at start + 2*5 seconds (18:50:11) (R2 - was R3) [CHANGED] // Path C: triggered at start + 15 seconds (18:50:16) (R1) // path D: triggerd at 18:50:20 (Cron) // Moving 4 seconds (18:50:12) should trigger both path A and B newDate = new Date(newDate.getTime() + (4 * 1000)); processEngineConfiguration.getClock().setCurrentTime(newDate); executableTimers = managementService.createJobQuery().executable().list(); assertEquals(2,executableTimers.size()); executeJobs(executableTimers); validateTaskCounts(1, 2, 0, 0); assertEquals(4, managementService.createJobQuery().count()); assertEquals(0, managementService.createJobQuery().executable().count()); // New situation: // Path A : triggered at start + 2*10 seconds (18:50:21) (R1 - was R2) [CHANGED] // Path B: triggered at start + 3*5 seconds (18:50:16) (R1 - was R2) [CHANGED] // Path C: triggered at start + 15 seconds (18:50:16) (R1) // path D: triggerd at 18:50:20 (Cron) // Moving 6 seconds (18:50:18) should trigger B and C newDate = new Date(newDate.getTime() + (6 * 1000)); processEngineConfiguration.getClock().setCurrentTime(newDate); executableTimers = managementService.createJobQuery().executable().list(); assertEquals(2,executableTimers.size()); executeJobs(executableTimers); validateTaskCounts(1, 3, 1, 0); assertEquals(2, managementService.createJobQuery().count()); assertEquals(0, managementService.createJobQuery().executable().count()); // New situation: // Path A : triggered at start + 2*10 seconds (18:50:21) (R1 - was R2) [CHANGED] // Path B: all repeats used up // Path C: all repeats used up // path D: triggerd at 18:50:20 (Cron) // Moving 10 seconds (18:50:28) should trigger A and D newDate = new Date(newDate.getTime() + (6 * 1000)); processEngineConfiguration.getClock().setCurrentTime(newDate); executableTimers = managementService.createJobQuery().executable().list(); assertEquals(2,executableTimers.size()); executeJobs(executableTimers); validateTaskCounts(2, 3, 1, 1); assertEquals(1, managementService.createJobQuery().count()); assertEquals(0, managementService.createJobQuery().executable().count()); // New situation: // Path A : all repeats used up // Path B: all repeats used up // Path C: all repeats used up // path D: triggerd at 18:50:40 (Cron) // Clean up repositoryService.deleteDeployment(deploymentId, true); } private void validateTaskCounts(long taskACount, long taskBCount, long taskCCount, long taskDCount) { assertEquals("task A counts are incorrect", taskACount, taskService.createTaskQuery().taskName("Task A").count()); assertEquals("task B counts are incorrect", taskBCount, taskService.createTaskQuery().taskName("Task B").count()); assertEquals("task C counts are incorrect", taskCCount, taskService.createTaskQuery().taskName("Task C").count()); assertEquals("task D counts are incorrect", taskDCount, taskService.createTaskQuery().taskName("Task D").count()); } private void executeJobs(List<Job> jobs) { for (Job job : jobs) { managementService.executeJob(job.getId()); } } private void cleanDB() { String jobId = managementService.createJobQuery().singleResult().getId(); CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor(); commandExecutor.execute(new CancelJobsCmd(jobId)); } }