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.api.event;
  
  import static org.assertj.core.api.Assertions.assertThat;
  
  import java.util.ArrayList;
  import java.util.List;
  import org.activiti.engine.delegate.event.ActivitiActivityEvent;
  import org.activiti.engine.delegate.event.ActivitiCancelledEvent;
  import org.activiti.engine.delegate.event.ActivitiEntityEvent;
  import org.activiti.engine.delegate.event.ActivitiEvent;
  import org.activiti.engine.delegate.event.ActivitiEventListener;
  import org.activiti.engine.delegate.event.ActivitiEventType;
  import org.activiti.engine.event.EventLogEntry;
  import org.activiti.engine.impl.event.logger.EventLogger;
  import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
  import org.activiti.engine.impl.persistence.entity.TaskEntity;
  import org.activiti.engine.impl.test.PluggableActivitiTestCase;
  import org.activiti.engine.runtime.Execution;
  import org.activiti.engine.runtime.ProcessInstance;
  import org.activiti.engine.test.Deployment;
  
  public class CancelCallActivityByMessageTest extends PluggableActivitiTestCase {
  
      private CallActivityByMessageEventListener listener;
  
      protected EventLogger databaseEventLogger;
  
      @Deployment(
          resources = {
              "org/activiti/engine/test/api/event/CancelCallActivityByMessageTest.testActivityMessageBoundaryEventsOnCallActivity.bpmn20.xml",
              "org/activiti/engine/test/api/event/CancelCallActivityByMessageTest.testActivityMessageBoundaryEventsExternalSubProcess.bpmn20.xml",
          }
      )
      public void testCancelCallActivityByMessage() throws Exception {
          CallActivityByMessageEventListener myEventListener = new CallActivityByMessageEventListener();
          processEngineConfiguration.getEventDispatcher().addEventListener(myEventListener);
  
          ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("messageEventOnCallActivity");
          assertThat(processInstance).isNotNull();
  
          Execution executionWithMessageEvent = runtimeService
              .createExecutionQuery()
              .activityId("cancelBoundaryMessageEvent")
              .singleResult();
          assertThat(executionWithMessageEvent).isNotNull();
          Execution callActivityExecution = runtimeService
              .createExecutionQuery()
              .activityId("callActivity1")
              .singleResult();
          assertThat(callActivityExecution).isNotNull();
  
          runtimeService.messageEventReceived("cancel", executionWithMessageEvent.getId());
  
          ActivitiEntityEvent entityEvent = (ActivitiEntityEvent) myEventListener.getEventsReceived().get(0);
          assertThat(entityEvent.getType()).isEqualTo(ActivitiEventType.ENTITY_CREATED);
          ExecutionEntity executionEntity = (ExecutionEntity) entityEvent.getEntity();
          // this is process so parent null
          assertThat(executionEntity.getParentId()).isNull();
          String processExecutionId = executionEntity.getId();
  
          // this is callActivity
          entityEvent = (ActivitiEntityEvent) myEventListener.getEventsReceived().get(1);
          assertThat(entityEvent.getType()).isEqualTo(ActivitiEventType.ENTITY_CREATED);
          executionEntity = (ExecutionEntity) entityEvent.getEntity();
          assertThat(executionEntity.getParentId()).isNotNull();
          assertThat(executionEntity.getParentId()).isEqualTo(processExecutionId);
  
          ActivitiEvent activitiEvent = (ActivitiEvent) myEventListener.getEventsReceived().get(2);
          assertThat(activitiEvent.getType()).isEqualTo(ActivitiEventType.PROCESS_STARTED);
  
          ActivitiActivityEvent activityEvent = (ActivitiActivityEvent) myEventListener.getEventsReceived().get(3);
          assertThat(activityEvent.getType()).isEqualTo(ActivitiEventType.ACTIVITY_STARTED);
          assertThat(activityEvent.getActivityType()).isEqualTo("startEvent");
  
          activityEvent = (ActivitiActivityEvent) myEventListener.getEventsReceived().get(4);
          assertThat(activityEvent.getType()).isEqualTo(ActivitiEventType.ACTIVITY_COMPLETED);
          assertThat(activityEvent.getActivityType()).isEqualTo("startEvent");
  
          entityEvent = (ActivitiEntityEvent) myEventListener.getEventsReceived().get(5);
          assertThat(entityEvent.getType()).isEqualTo(ActivitiEventType.ENTITY_CREATED);
          executionEntity = (ExecutionEntity) entityEvent.getEntity();
          assertThat(executionEntity.getActivityId()).isEqualTo("cancelBoundaryMessageEvent");
          String boundaryExecutionId = executionEntity.getId();
  
          activityEvent = (ActivitiActivityEvent) myEventListener.getEventsReceived().get(6);
          assertThat(activityEvent.getType()).isEqualTo(ActivitiEventType.ACTIVITY_STARTED);
          assertThat(activityEvent.getActivityId()).isEqualTo("callActivity1");
  
          // this is external subprocess. Workflow uses the ENTITY_CREATED event to determine when to send our event.
          entityEvent = (ActivitiEntityEvent) myEventListener.getEventsReceived().get(7);
          assertThat(entityEvent.getType()).isEqualTo(ActivitiEventType.ENTITY_CREATED);
          executionEntity = (ExecutionEntity) entityEvent.getEntity();
          assertThat(executionEntity.getParentId()).isNull();
          assertThat(executionEntity.getProcessInstanceId()).isEqualTo(executionEntity.getId());
  
          // this is the task within the external subprocess
          entityEvent = (ActivitiEntityEvent) myEventListener.getEventsReceived().get(8);
          assertThat(entityEvent.getType()).isEqualTo(ActivitiEventType.ENTITY_CREATED);
          executionEntity = (ExecutionEntity) entityEvent.getEntity();
          assertThat(executionEntity.getActivityId()).isEqualTo("calledSubprocessTask");
  
          // start event in external subprocess
          activitiEvent = (ActivitiEvent) myEventListener.getEventsReceived().get(9);
          assertThat(activitiEvent.getType()).isEqualTo(ActivitiEventType.PROCESS_STARTED);
  
          activityEvent = (ActivitiActivityEvent) myEventListener.getEventsReceived().get(10);
          assertThat(activityEvent.getType()).isEqualTo(ActivitiEventType.ACTIVITY_STARTED);
          assertThat(activityEvent.getActivityType()).isEqualTo("startEvent");
          assertThat(activityEvent.getActivityId()).isEqualTo("startevent2");
  
          // start event in external subprocess
          activityEvent = (ActivitiActivityEvent) myEventListener.getEventsReceived().get(11);
          assertThat(activityEvent.getType()).isEqualTo(ActivitiEventType.ACTIVITY_COMPLETED);
          assertThat(activityEvent.getActivityType()).isEqualTo("startEvent");
          assertThat(activityEvent.getActivityId()).isEqualTo("startevent2");
  
          // this is user task within external subprocess
          activityEvent = (ActivitiActivityEvent) myEventListener.getEventsReceived().get(12);
          assertThat(activityEvent.getType()).isEqualTo(ActivitiEventType.ACTIVITY_STARTED);
          assertThat(activityEvent.getActivityId()).isEqualTo("calledSubprocessTask");
          assertThat(activityEvent.getActivityType()).isEqualTo("userTask");
  
          entityEvent = (ActivitiEntityEvent) myEventListener.getEventsReceived().get(13);
          assertThat(entityEvent.getType()).isEqualTo(ActivitiEventType.TASK_CREATED);
          TaskEntity taskEntity = (TaskEntity) entityEvent.getEntity();
          assertThat(taskEntity.getName()).isEqualTo("Sample User Task2 in External");
          String externalTaskExecutionId = taskEntity.getExecutionId();
  
          // activityId is the call activity and the execution is the boundary event as we have seen before
          // We get this event in workflow but we ignore the activityType of "callActivity"
          activityEvent = (ActivitiActivityEvent) myEventListener.getEventsReceived().get(14);
          assertThat(activityEvent)
              .extracting(
                  ActivitiActivityEvent::getType,
                  ActivitiActivityEvent::getActivityType,
                  ActivitiEvent::getExecutionId,
                  ActivitiActivityEvent::getActivityName
              )
              .containsExactly(
                  ActivitiEventType.ACTIVITY_CANCELLED,
                  "userTask",
                  externalTaskExecutionId,
                  "Sample User Task2 in External"
              );
  
          assertThat(((ActivitiCancelledEvent) activityEvent).getCause()).isEqualTo(
              "boundary event (cancelBoundaryMessageEvent)"
          );
  
          ActivitiEntityEvent taskCancelledEvent = (ActivitiEntityEvent) myEventListener.getEventsReceived().get(15);
          assertThat(taskCancelledEvent.getType()).isEqualTo(ActivitiEventType.ENTITY_DELETED);
          assertThat(((TaskEntity) taskCancelledEvent.getEntity()).getName()).isEqualTo(taskEntity.getName());
  
          ActivitiCancelledEvent processCancelledEvent = (ActivitiCancelledEvent) myEventListener
              .getEventsReceived()
              .get(16);
          assertThat(processCancelledEvent.getType()).isEqualTo(ActivitiEventType.PROCESS_CANCELLED);
          assertThat(processCancelledEvent.getExecutionId()).isEqualTo(processCancelledEvent.getProcessInstanceId());
  
          activityEvent = (ActivitiActivityEvent) myEventListener.getEventsReceived().get(17);
          assertThat(activityEvent)
              .extracting(
                  ActivitiActivityEvent::getType,
                  ActivitiActivityEvent::getActivityType,
                  ActivitiEvent::getExecutionId,
                  ActivitiActivityEvent::getActivityName
              )
              .containsExactly(
                  ActivitiEventType.ACTIVITY_CANCELLED,
                  "callActivity",
                  callActivityExecution.getId(),
                  "Call activity"
              );
          assertThat(((ActivitiCancelledEvent) activityEvent).getCause()).isEqualTo(
              "boundary event (cancelBoundaryMessageEvent)"
          );
  
          activityEvent = (ActivitiActivityEvent) myEventListener.getEventsReceived().get(18);
          assertThat(activityEvent.getType()).isEqualTo(ActivitiEventType.ACTIVITY_COMPLETED);
          assertThat(activityEvent.getActivityType()).isEqualTo("boundaryEvent");
          assertThat(activityEvent.getActivityId()).isEqualTo("cancelBoundaryMessageEvent");
  
          // task in the main definition
          activityEvent = (ActivitiActivityEvent) myEventListener.getEventsReceived().get(19);
          assertThat(activityEvent.getType()).isEqualTo(ActivitiEventType.ACTIVITY_STARTED);
          assertThat(activityEvent.getActivityId()).isEqualTo("usertask1");
          assertThat(activityEvent.getActivityType()).isEqualTo("userTask");
  
          entityEvent = (ActivitiEntityEvent) myEventListener.getEventsReceived().get(20);
          assertThat(entityEvent.getType()).isEqualTo(ActivitiEventType.TASK_CREATED);
          taskEntity = (TaskEntity) entityEvent.getEntity();
          assertThat(taskEntity.getName()).isEqualTo("Sample User Task1");
  
          assertThat(myEventListener.getEventsReceived()).hasSize(21);
      }
  
      @Override
      protected void setUp() throws Exception {
          super.setUp();
  
          // Database event logger setup
          databaseEventLogger = new EventLogger(
              processEngineConfiguration.getClock(),
              processEngineConfiguration.getObjectMapper()
          );
          runtimeService.addEventListener(databaseEventLogger);
      }
  
      @Override
      protected void tearDown() throws Exception {
          if (listener != null) {
              listener.clearEventsReceived();
              processEngineConfiguration.getEventDispatcher().removeEventListener(listener);
          }
  
          // Remove entries
          for (EventLogEntry eventLogEntry : managementService.getEventLogEntries(null, null)) {
              managementService.deleteEventLogEntry(eventLogEntry.getLogNumber());
          }
  
          // Database event logger teardown
          runtimeService.removeEventListener(databaseEventLogger);
          super.tearDown();
      }
  
      @Override
      protected void initializeServices() {
          super.initializeServices();
          listener = new CallActivityByMessageEventListener();
          processEngineConfiguration.getEventDispatcher().addEventListener(listener);
      }
  
      class CallActivityByMessageEventListener implements ActivitiEventListener {
  
          private List<ActivitiEvent> eventsReceived;
  
          public CallActivityByMessageEventListener() {
              eventsReceived = new ArrayList<ActivitiEvent>();
          }
  
          public List<ActivitiEvent> getEventsReceived() {
              return eventsReceived;
          }
  
          public void clearEventsReceived() {
              eventsReceived.clear();
          }
  
          @Override
          public void onEvent(ActivitiEvent event) {
              switch (event.getType()) {
                  case ENTITY_CREATED:
                      ActivitiEntityEvent entityEvent = (ActivitiEntityEvent) event;
                      if (entityEvent.getEntity() instanceof ExecutionEntity) {
                          eventsReceived.add(event);
                      }
                      break;
                  case ENTITY_DELETED:
                      if (
                          event instanceof ActivitiEntityEvent &&
                          ((ActivitiEntityEvent) event).getEntity() instanceof TaskEntity
                      ) {
                          eventsReceived.add(event);
                      }
                      break;
                  case ACTIVITY_STARTED:
                  case ACTIVITY_COMPLETED:
                  case ACTIVITY_CANCELLED:
                  case TASK_CREATED:
                  case TASK_COMPLETED:
                  case PROCESS_STARTED:
                  case PROCESS_COMPLETED:
                  case PROCESS_CANCELLED:
                      eventsReceived.add(event);
                      break;
                  default:
                      break;
              }
          }
  
          @Override
          public boolean isFailOnException() {
              return false;
          }
      }
  }
  