content: /*
   * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
   *
   * 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 static org.assertj.core.api.Assertions.assertThat;
  
  import java.util.Collections;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.List;
  import org.activiti.engine.impl.test.PluggableActivitiTestCase;
  import org.activiti.engine.runtime.Execution;
  import org.activiti.engine.runtime.Job;
  import org.activiti.engine.runtime.ProcessInstance;
  import org.activiti.engine.runtime.TimerJobQuery;
  import org.activiti.engine.task.Task;
  import org.activiti.engine.task.TaskQuery;
  import org.activiti.engine.test.Deployment;
  
  /**
   */
  public class BoundaryTimerNonInterruptingEventTest extends PluggableActivitiTestCase {
  
      @Deployment
      public void testMultipleTimersOnUserTask() {
          // Set the clock fixed
          Date startTime = new Date();
  
          // After process start, there should be 3 timers created
          ProcessInstance pi = runtimeService.startProcessInstanceByKey("nonInterruptingTimersOnUserTask");
          Task task1 = taskService.createTaskQuery().singleResult();
          assertThat(task1.getName()).isEqualTo("First Task");
  
          TimerJobQuery jobQuery = managementService.createTimerJobQuery().processInstanceId(pi.getId());
          List<Job> jobs = jobQuery.list();
          assertThat(jobs).hasSize(2);
  
          // After setting the clock to time '1 hour and 5 seconds', the first timer should fire
          processEngineConfiguration.getClock().setCurrentTime(new Date(startTime.getTime() + ((60 * 60 * 1000) + 5000)));
          Job job = managementService.createTimerJobQuery().executable().singleResult();
          assertThat(job).isNotNull();
          managementService.moveTimerToExecutableJob(job.getId());
          managementService.executeJob(job.getId());
  
          // we still have one timer more to fire
          assertThat(jobQuery.count()).isEqualTo(1L);
  
          // and we are still in the first state, but in the second state as well!
          assertThat(taskService.createTaskQuery().count()).isEqualTo(2L);
          List<Task> taskList = taskService.createTaskQuery().orderByTaskName().desc().list();
          assertThat(taskList.get(0).getName()).isEqualTo("First Task");
          assertThat(taskList.get(1).getName()).isEqualTo("Escalation Task 1");
  
          // complete the task and end the forked execution
          taskService.complete(taskList.get(1).getId());
  
          // but we still have the original executions
          assertThat(taskService.createTaskQuery().count()).isEqualTo(1L);
          assertThat(taskService.createTaskQuery().singleResult().getName()).isEqualTo("First Task");
  
          // After setting the clock to time '2 hour and 5 seconds', the second timer should fire
          processEngineConfiguration
              .getClock()
              .setCurrentTime(new Date(startTime.getTime() + ((2 * 60 * 60 * 1000) + 5000)));
          waitForJobExecutorToProcessAllJobs(5000L, 25L);
  
          // no more timers to fire
          assertThat(jobQuery.count()).isEqualTo(0L);
  
          // and we are still in the first state, but in the next escalation state as well
          assertThat(taskService.createTaskQuery().count()).isEqualTo(2L);
          taskList = taskService.createTaskQuery().orderByTaskName().desc().list();
          assertThat(taskList.get(0).getName()).isEqualTo("First Task");
          assertThat(taskList.get(1).getName()).isEqualTo("Escalation Task 2");
  
          // This time we end the main task
          taskService.complete(taskList.get(0).getId());
  
          // but we still have the escalation task
          assertThat(taskService.createTaskQuery().count()).isEqualTo(1L);
          Task escalationTask = taskService.createTaskQuery().singleResult();
          assertThat(escalationTask.getName()).isEqualTo("Escalation Task 2");
  
          taskService.complete(escalationTask.getId());
  
          // now we are really done :-)
          assertProcessEnded(pi.getId());
      }
  
      @Deployment
      public void testJoin() {
          // Set the clock fixed
          Date startTime = new Date();
  
          // After process start, there should be 3 timers created
          ProcessInstance pi = runtimeService.startProcessInstanceByKey("testJoin");
          Task task1 = taskService.createTaskQuery().singleResult();
          assertThat(task1.getName()).isEqualTo("Main Task");
  
          TimerJobQuery jobQuery = managementService.createTimerJobQuery().processInstanceId(pi.getId());
          List<Job> jobs = jobQuery.list();
          assertThat(jobs).hasSize(1);
  
          // After setting the clock to time '1 hour and 5 seconds', the first timer should fire
          processEngineConfiguration.getClock().setCurrentTime(new Date(startTime.getTime() + ((60 * 60 * 1000) + 5000)));
          waitForJobExecutorToProcessAllJobs(5000L, 25L);
  
          // timer has fired
          assertThat(jobQuery.count()).isEqualTo(0L);
  
          // we now have both tasks
          assertThat(taskService.createTaskQuery().count()).isEqualTo(2L);
  
          // end the first
          taskService.complete(task1.getId());
  
          // we now have one task left
          assertThat(taskService.createTaskQuery().count()).isEqualTo(1L);
          Task task2 = taskService.createTaskQuery().singleResult();
          assertThat(task2.getName()).isEqualTo("Escalation Task");
  
          // complete the task, the parallel gateway should fire
          taskService.complete(task2.getId());
  
          // and the process has ended
          assertProcessEnded(pi.getId());
      }
  
      @Deployment
      public void testTimerOnConcurrentTasks() {
          String procId = runtimeService.startProcessInstanceByKey("nonInterruptingOnConcurrentTasks").getId();
          assertThat(taskService.createTaskQuery().count()).isEqualTo(2);
  
          Job timer = managementService.createTimerJobQuery().singleResult();
          managementService.moveTimerToExecutableJob(timer.getId());
          managementService.executeJob(timer.getId());
          assertThat(taskService.createTaskQuery().count()).isEqualTo(3);
  
          // Complete task that was reached by non interrupting timer
          Task task = taskService.createTaskQuery().taskDefinitionKey("timerFiredTask").singleResult();
          taskService.complete(task.getId());
          assertThat(taskService.createTaskQuery().count()).isEqualTo(2);
  
          // Complete other tasks
          for (Task t : taskService.createTaskQuery().list()) {
              taskService.complete(t.getId());
          }
          assertProcessEnded(procId);
      }
  
      // Difference with previous test: now the join will be reached first
      @Deployment(
          resources = {
              "org/activiti/engine/test/bpmn/event/timer/BoundaryTimerNonInterruptingEventTest.testTimerOnConcurrentTasks.bpmn20.xml",
          }
      )
      public void testTimerOnConcurrentTasks2() {
          String procId = runtimeService.startProcessInstanceByKey("nonInterruptingOnConcurrentTasks").getId();
          assertThat(taskService.createTaskQuery().count()).isEqualTo(2);
  
          Job timer = managementService.createTimerJobQuery().singleResult();
          managementService.moveTimerToExecutableJob(timer.getId());
          managementService.executeJob(timer.getId());
          assertThat(taskService.createTaskQuery().count()).isEqualTo(3);
  
          // Complete 2 tasks that will trigger the join
          Task task = taskService.createTaskQuery().taskDefinitionKey("firstTask").singleResult();
          taskService.complete(task.getId());
          task = taskService.createTaskQuery().taskDefinitionKey("secondTask").singleResult();
          taskService.complete(task.getId());
          assertThat(taskService.createTaskQuery().count()).isEqualTo(1);
  
          // Finally, complete the task that was created due to the timer
          task = taskService.createTaskQuery().taskDefinitionKey("timerFiredTask").singleResult();
          taskService.complete(task.getId());
  
          assertProcessEnded(procId);
      }
  
      @Deployment
      public void testTimerWithCycle() throws Exception {
          String processInstanceId = runtimeService.startProcessInstanceByKey("nonInterruptingCycle").getId();
  
          List<Job> jobs = managementService.createTimerJobQuery().processInstanceId(processInstanceId).list();
          assertThat(jobs).hasSize(1);
  
          // boundary events
          waitForJobExecutorToProcessAllJobs(2000, 100);
  
          // a new job must be prepared because there are indefinite number of repeats 1 hour interval");
          assertThat(managementService.createTimerJobQuery().processInstanceId(processInstanceId).count()).isEqualTo(1);
  
          moveByMinutes(60);
          waitForJobExecutorToProcessAllJobs(2000, 100);
  
          // a new job must be prepared because there are indefinite number of repeats 1 hour interval");
          assertThat(managementService.createTimerJobQuery().processInstanceId(processInstanceId).count()).isEqualTo(1);
  
          Task task = taskService.createTaskQuery().taskDefinitionKey("task").singleResult();
          taskService.complete(task.getId());
  
          moveByMinutes(60);
          try {
              waitForJobExecutorToProcessAllJobs(2000, 100);
          } catch (Exception ex) {
              fail("No more jobs since the user completed the task");
          }
      }
  
      @Deployment
      /**
       * see https://activiti.atlassian.net/browse/ACT-1173
       */
      public void testTimerOnEmbeddedSubprocess() {
          String id = runtimeService.startProcessInstanceByKey("nonInterruptingTimerOnEmbeddedSubprocess").getId();
  
          TaskQuery tq = taskService.createTaskQuery().taskAssignee("kermit");
  
          assertThat(tq.count()).isEqualTo(1);
  
          // Simulate timer
          Job timer = managementService.createTimerJobQuery().singleResult();
          managementService.moveTimerToExecutableJob(timer.getId());
          managementService.executeJob(timer.getId());
  
          tq = taskService.createTaskQuery().taskAssignee("kermit");
  
          assertThat(tq.count()).isEqualTo(2);
  
          List<Task> tasks = tq.list();
  
          taskService.complete(tasks.get(0).getId());
          taskService.complete(tasks.get(1).getId());
  
          assertProcessEnded(id);
      }
  
      @Deployment
      /**
       * see https://activiti.atlassian.net/browse/ACT-1106
       */
      public void testReceiveTaskWithBoundaryTimer() {
          // Set the clock fixed
          HashMap<String, Object> variables = new HashMap<String, Object>();
          variables.put("timeCycle", "R/PT1H");
  
          // After process start, there should be a timer created
          ProcessInstance pi = runtimeService.startProcessInstanceByKey("nonInterruptingCycle", variables);
  
          TimerJobQuery jobQuery = managementService.createTimerJobQuery().processInstanceId(pi.getId());
          List<Job> jobs = jobQuery.list();
          assertThat(jobs).hasSize(1);
  
          // The Execution Query should work normally and find executions in state "task"
          List<Execution> executions = runtimeService.createExecutionQuery().activityId("task").list();
          assertThat(executions).hasSize(1);
          List<String> activeActivityIds = runtimeService.getActiveActivityIds(executions.get(0).getId());
          assertThat(activeActivityIds).hasSize(2);
          Collections.sort(activeActivityIds);
          assertThat(activeActivityIds.get(0)).isEqualTo("task");
          assertThat(activeActivityIds.get(1)).isEqualTo("timer");
  
          runtimeService.trigger(executions.get(0).getId());
  
          // // After setting the clock to time '1 hour and 5 seconds', the second
          // timer should fire
          // processEngineConfiguration.getClock().setCurrentTime(new
          // Date(startTime.getTime() + ((60 * 60 * 1000) + 5000)));
          // waitForJobExecutorToProcessAllJobs(5000L, 25L);
          // assertThat(jobQuery.count()).isEqualTo(0L);
  
          // which means the process has ended
          assertProcessEnded(pi.getId());
      }
  
      @Deployment
      public void testTimerOnConcurrentSubprocess() {
          String procId = runtimeService.startProcessInstanceByKey("testTimerOnConcurrentSubprocess").getId();
          assertThat(taskService.createTaskQuery().count()).isEqualTo(4);
  
          Job timer = managementService.createTimerJobQuery().singleResult();
          managementService.moveTimerToExecutableJob(timer.getId());
          managementService.executeJob(timer.getId());
          assertThat(taskService.createTaskQuery().count()).isEqualTo(5);
  
          // Complete 4 tasks that will trigger the join
          Task task = taskService.createTaskQuery().taskDefinitionKey("sub1task1").singleResult();
          taskService.complete(task.getId());
          task = taskService.createTaskQuery().taskDefinitionKey("sub1task2").singleResult();
          taskService.complete(task.getId());
          task = taskService.createTaskQuery().taskDefinitionKey("sub2task1").singleResult();
          taskService.complete(task.getId());
          task = taskService.createTaskQuery().taskDefinitionKey("sub2task2").singleResult();
          taskService.complete(task.getId());
          assertThat(taskService.createTaskQuery().count()).isEqualTo(1);
  
          // Finally, complete the task that was created due to the timer
          task = taskService.createTaskQuery().taskDefinitionKey("timerFiredTask").singleResult();
          taskService.complete(task.getId());
  
          assertProcessEnded(procId);
      }
  
      @Deployment(
          resources = "org/activiti/engine/test/bpmn/event/timer/BoundaryTimerNonInterruptingEventTest.testTimerOnConcurrentSubprocess.bpmn20.xml"
      )
      public void testTimerOnConcurrentSubprocess2() {
          String procId = runtimeService.startProcessInstanceByKey("testTimerOnConcurrentSubprocess").getId();
          assertThat(taskService.createTaskQuery().count()).isEqualTo(4);
  
          Job timer = managementService.createTimerJobQuery().singleResult();
          managementService.moveTimerToExecutableJob(timer.getId());
          managementService.executeJob(timer.getId());
          assertThat(taskService.createTaskQuery().count()).isEqualTo(5);
  
          Task task = taskService.createTaskQuery().taskDefinitionKey("sub1task1").singleResult();
          taskService.complete(task.getId());
          task = taskService.createTaskQuery().taskDefinitionKey("sub1task2").singleResult();
          taskService.complete(task.getId());
  
          // complete the task that was created due to the timer
          task = taskService.createTaskQuery().taskDefinitionKey("timerFiredTask").singleResult();
          taskService.complete(task.getId());
  
          task = taskService.createTaskQuery().taskDefinitionKey("sub2task1").singleResult();
          taskService.complete(task.getId());
          task = taskService.createTaskQuery().taskDefinitionKey("sub2task2").singleResult();
          taskService.complete(task.getId());
          assertThat(taskService.createTaskQuery().count()).isEqualTo(0);
  
          assertProcessEnded(procId);
      }
  
      private void moveByMinutes(int minutes) throws Exception {
          processEngineConfiguration
              .getClock()
              .setCurrentTime(
                  new Date(processEngineConfiguration.getClock().getCurrentTime().getTime() + ((minutes * 60 * 1000)))
              );
      }
  }
  