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.impl.bpmn.deployer;
  
  import com.fasterxml.jackson.databind.JsonNode;
  import com.fasterxml.jackson.databind.node.ObjectNode;
  import java.util.Collection;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import org.activiti.bpmn.constants.BpmnXMLConstants;
  import org.activiti.bpmn.model.BpmnModel;
  import org.activiti.bpmn.model.ExtensionElement;
  import org.activiti.bpmn.model.FlowElement;
  import org.activiti.bpmn.model.Process;
  import org.activiti.bpmn.model.SubProcess;
  import org.activiti.bpmn.model.UserTask;
  import org.activiti.bpmn.model.ValuedDataObject;
  import org.activiti.engine.DynamicBpmnConstants;
  import org.activiti.engine.DynamicBpmnService;
  import org.activiti.engine.delegate.event.ActivitiEventType;
  import org.activiti.engine.delegate.event.impl.ActivitiEventBuilder;
  import org.activiti.engine.impl.cfg.IdGenerator;
  import org.activiti.engine.impl.context.Context;
  import org.activiti.engine.impl.interceptor.CommandContext;
  import org.activiti.engine.impl.persistence.deploy.Deployer;
  import org.activiti.engine.impl.persistence.entity.DeploymentEntity;
  import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
  import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntityManager;
  import org.activiti.engine.impl.persistence.entity.ResourceEntity;
  import org.activiti.engine.repository.ProcessDefinition;
  import org.apache.commons.lang3.StringUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  public class BpmnDeployer implements Deployer {
  
      private static final Logger log = LoggerFactory.getLogger(BpmnDeployer.class);
  
      protected IdGenerator idGenerator;
      protected ParsedDeploymentBuilderFactory parsedDeploymentBuilderFactory;
      protected BpmnDeploymentHelper bpmnDeploymentHelper;
      protected CachingAndArtifactsManager cachingAndArtifactsManager;
      private boolean disableExistingStartEventSubscriptions;
  
      @Override
      public void deploy(DeploymentEntity deployment, Map<String, Object> deploymentSettings) {
          log.debug("Processing deployment {}", deployment.getName());
  
          // The ParsedDeployment represents the deployment, the process definitions, and the BPMN
          // resource, parse, and model associated with each process definition.
          ParsedDeployment parsedDeployment = parsedDeploymentBuilderFactory
              .getBuilderForDeploymentAndSettings(deployment, deploymentSettings)
              .build();
  
          bpmnDeploymentHelper.verifyProcessDefinitionsDoNotShareKeys(parsedDeployment.getAllProcessDefinitions());
  
          bpmnDeploymentHelper.copyDeploymentValuesToProcessDefinitions(
              parsedDeployment.getDeployment(),
              parsedDeployment.getAllProcessDefinitions()
          );
          bpmnDeploymentHelper.setResourceNamesOnProcessDefinitions(parsedDeployment);
  
          //    createAndPersistNewDiagramsIfNeeded(parsedDeployment);
          setProcessDefinitionDiagramNames(parsedDeployment);
  
          if (deployment.isNew()) {
              Map<ProcessDefinitionEntity, ProcessDefinitionEntity> mapOfNewProcessDefinitionToPreviousVersion =
                  getPreviousVersionsOfProcessDefinitions(parsedDeployment);
              setProcessDefinitionVersionsAndIds(parsedDeployment, mapOfNewProcessDefinitionToPreviousVersion);
              setProcessDefinitionAppVersion(parsedDeployment);
              persistProcessDefinitionsAndAuthorizations(parsedDeployment);
              if (shouldDisableExistingStartEventSubscriptions()) {
                  bpmnDeploymentHelper.disableExistingStartEventSubscriptions();
              }
              updateTimersAndEvents(parsedDeployment, mapOfNewProcessDefinitionToPreviousVersion);
              dispatchProcessDefinitionEntityInitializedEvent(parsedDeployment);
          } else {
              makeProcessDefinitionsConsistentWithPersistedVersions(parsedDeployment);
          }
  
          cachingAndArtifactsManager.updateCachingAndArtifacts(parsedDeployment);
  
          for (ProcessDefinitionEntity processDefinition : parsedDeployment.getAllProcessDefinitions()) {
              BpmnModel bpmnModel = parsedDeployment.getBpmnModelForProcessDefinition(processDefinition);
              createLocalizationValues(processDefinition, bpmnModel.getProcessById(processDefinition.getKey()));
          }
      }
  
      //
      //  /**
      //   * Creates new diagrams for process definitions if the deployment is new, the process definition in
      //   * question supports it, and the engine is configured to make new diagrams.
      //   *
      //   * When this method creates a new diagram, it also persists it via the ResourceEntityManager
      //   * and adds it to the resources of the deployment.
      //   */
      //  protected void createAndPersistNewDiagramsIfNeeded(ParsedDeployment parsedDeployment) {
      //
      //    final ProcessEngineConfigurationImpl processEngineConfiguration = Context.getProcessEngineConfiguration();
      //    final DeploymentEntity deploymentEntity = parsedDeployment.getDeployment();
      //
      //    final ResourceEntityManager resourceEntityManager = processEngineConfiguration.getResourceEntityManager();
      //
      //    for (ProcessDefinitionEntity processDefinition : parsedDeployment.getAllProcessDefinitions()) {
      //      if (processDefinitionDiagramHelper.shouldCreateDiagram(processDefinition, deploymentEntity)) {
      //        ResourceEntity resource = processDefinitionDiagramHelper.createDiagramForProcessDefinition(
      //            processDefinition, parsedDeployment.getBpmnParseForProcessDefinition(processDefinition));
      //        if (resource != null) {
      //          resourceEntityManager.insert(resource, false);
      //          deploymentEntity.addResource(resource);  // now we'll find it if we look for the diagram name later.
      //        }
      //      }
      //    }
      //  }
  
      /**
       * Updates all the process definition entities to have the correct diagram resource name.  Must
       * be called after createAndPersistNewDiagramsAsNeeded to ensure that any newly-created diagrams
       * already have their resources attached to the deployment.
       */
      protected void setProcessDefinitionDiagramNames(ParsedDeployment parsedDeployment) {
          Map<String, ResourceEntity> resources = parsedDeployment.getDeployment().getResources();
  
          for (ProcessDefinitionEntity processDefinition : parsedDeployment.getAllProcessDefinitions()) {
              String diagramResourceName = ResourceNameUtil.getProcessDiagramResourceNameFromDeployment(
                  processDefinition,
                  resources
              );
              processDefinition.setDiagramResourceName(diagramResourceName);
          }
      }
  
      /**
       * Constructs a map from new ProcessDefinitionEntities to the previous version by key and tenant.
       * If no previous version exists, no map entry is created.
       */
      protected Map<ProcessDefinitionEntity, ProcessDefinitionEntity> getPreviousVersionsOfProcessDefinitions(
          ParsedDeployment parsedDeployment
      ) {
          Map<ProcessDefinitionEntity, ProcessDefinitionEntity> result = new LinkedHashMap<
              ProcessDefinitionEntity,
              ProcessDefinitionEntity
          >();
  
          for (ProcessDefinitionEntity newDefinition : parsedDeployment.getAllProcessDefinitions()) {
              ProcessDefinitionEntity existingDefinition = bpmnDeploymentHelper.getMostRecentVersionOfProcessDefinition(
                  newDefinition
              );
  
              if (existingDefinition != null) {
                  result.put(newDefinition, existingDefinition);
              }
          }
  
          return result;
      }
  
      /**
       * Sets the version on each process definition entity, and the identifier.  If the map contains
       * an older version for a process definition, then the version is set to that older entity's
       * version plus one; otherwise it is set to 1.  Also dispatches an ENTITY_CREATED event.
       */
      protected void setProcessDefinitionVersionsAndIds(
          ParsedDeployment parsedDeployment,
          Map<ProcessDefinitionEntity, ProcessDefinitionEntity> mapNewToOldProcessDefinitions
      ) {
          CommandContext commandContext = Context.getCommandContext();
  
          if (parsedDeployment.getDeployment().getProjectReleaseVersion() != null) {
              Integer version = parsedDeployment.getDeployment().getVersion();
              for (ProcessDefinitionEntity processDefinition : parsedDeployment.getAllProcessDefinitions()) {
                  processDefinition.setVersion(version);
                  processDefinition.setId(getIdForNewProcessDefinition(processDefinition));
  
                  if (commandContext.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
                      commandContext
                          .getProcessEngineConfiguration()
                          .getEventDispatcher()
                          .dispatchEvent(
                              ActivitiEventBuilder.createEntityEvent(ActivitiEventType.ENTITY_CREATED, processDefinition)
                          );
                  }
              }
          } else {
              for (ProcessDefinitionEntity processDefinition : parsedDeployment.getAllProcessDefinitions()) {
                  int version = 1;
  
                  ProcessDefinitionEntity latest = mapNewToOldProcessDefinitions.get(processDefinition);
                  if (latest != null) {
                      version = latest.getVersion() + 1;
                  }
  
                  processDefinition.setVersion(version);
                  processDefinition.setId(getIdForNewProcessDefinition(processDefinition));
  
                  if (commandContext.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
                      commandContext
                          .getProcessEngineConfiguration()
                          .getEventDispatcher()
                          .dispatchEvent(
                              ActivitiEventBuilder.createEntityEvent(ActivitiEventType.ENTITY_CREATED, processDefinition)
                          );
                  }
              }
          }
      }
  
      /**
       * Saves each process definition.  It is assumed that the deployment is new, the definitions
       * have never been saved before, and that they have all their values properly set up.
       */
      protected void persistProcessDefinitionsAndAuthorizations(ParsedDeployment parsedDeployment) {
          CommandContext commandContext = Context.getCommandContext();
          ProcessDefinitionEntityManager processDefinitionManager = commandContext.getProcessDefinitionEntityManager();
  
          for (ProcessDefinitionEntity processDefinition : parsedDeployment.getAllProcessDefinitions()) {
              processDefinitionManager.insert(processDefinition, false);
              bpmnDeploymentHelper.addAuthorizationsForNewProcessDefinition(
                  parsedDeployment.getProcessModelForProcessDefinition(processDefinition),
                  processDefinition
              );
          }
      }
  
      protected void updateTimersAndEvents(
          ParsedDeployment parsedDeployment,
          Map<ProcessDefinitionEntity, ProcessDefinitionEntity> mapNewToOldProcessDefinitions
      ) {
          for (ProcessDefinitionEntity processDefinition : parsedDeployment.getAllProcessDefinitions()) {
              bpmnDeploymentHelper.updateTimersAndEvents(
                  processDefinition,
                  mapNewToOldProcessDefinitions.get(processDefinition),
                  parsedDeployment
              );
          }
      }
  
      protected void dispatchProcessDefinitionEntityInitializedEvent(ParsedDeployment parsedDeployment) {
          CommandContext commandContext = Context.getCommandContext();
          for (ProcessDefinitionEntity processDefinitionEntity : parsedDeployment.getAllProcessDefinitions()) {
              log.info(
                  "Process deployed: {id: " +
                  processDefinitionEntity.getId() +
                  ", key: " +
                  processDefinitionEntity.getKey() +
                  ", name: " +
                  processDefinitionEntity.getName() +
                  " }"
              );
              if (commandContext.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
                  commandContext
                      .getProcessEngineConfiguration()
                      .getEventDispatcher()
                      .dispatchEvent(
                          ActivitiEventBuilder.createEntityEvent(
                              ActivitiEventType.ENTITY_INITIALIZED,
                              processDefinitionEntity
                          )
                      );
              }
          }
      }
  
      /**
       * Returns the ID to use for a new process definition; subclasses may override this to provide
       * their own identification scheme.
       * <p>
       * Process definition ids NEED to be unique accross the whole engine!
       */
      protected String getIdForNewProcessDefinition(ProcessDefinitionEntity processDefinition) {
          String nextId = idGenerator.getNextId();
  
          String result = processDefinition.getKey() + ":" + processDefinition.getVersion() + ":" + nextId; // ACT-505
          // ACT-115: maximum id length is 64 characters
          if (result.length() > 64) {
              result = nextId;
          }
  
          return result;
      }
  
      /**
       * Loads the persisted version of each process definition and set values on the in-memory
       * version to be consistent.
       */
      protected void makeProcessDefinitionsConsistentWithPersistedVersions(ParsedDeployment parsedDeployment) {
          for (ProcessDefinitionEntity processDefinition : parsedDeployment.getAllProcessDefinitions()) {
              ProcessDefinitionEntity persistedProcessDefinition =
                  bpmnDeploymentHelper.getPersistedInstanceOfProcessDefinition(processDefinition);
  
              if (persistedProcessDefinition != null) {
                  processDefinition.setId(persistedProcessDefinition.getId());
                  processDefinition.setVersion(persistedProcessDefinition.getVersion());
                  processDefinition.setAppVersion(persistedProcessDefinition.getAppVersion());
                  processDefinition.setSuspensionState(persistedProcessDefinition.getSuspensionState());
                  processDefinition.setEngineVersion(persistedProcessDefinition.getEngineVersion());
              }
          }
      }
  
      protected void createLocalizationValues(ProcessDefinition processDefinition, Process process) {
          if (process == null) {
              return;
          }
  
          CommandContext commandContext = Context.getCommandContext();
          DynamicBpmnService dynamicBpmnService = commandContext.getProcessEngineConfiguration().getDynamicBpmnService();
          ObjectNode infoNode = dynamicBpmnService.getProcessDefinitionInfo(processDefinition);
  
          boolean localizationValuesChanged = false;
          List<ExtensionElement> localizationElements = process.getExtensionElements().get("localization");
          if (localizationElements != null) {
              for (ExtensionElement localizationElement : localizationElements) {
                  if (BpmnXMLConstants.ACTIVITI_EXTENSIONS_PREFIX.equals(localizationElement.getNamespacePrefix())) {
                      String locale = localizationElement.getAttributeValue(null, "locale");
                      String name = localizationElement.getAttributeValue(null, "name");
                      String documentation = null;
                      List<ExtensionElement> documentationElements = localizationElement
                          .getChildElements()
                          .get("documentation");
                      if (documentationElements != null) {
                          for (ExtensionElement documentationElement : documentationElements) {
                              documentation = StringUtils.trimToNull(documentationElement.getElementText());
                              break;
                          }
                      }
  
                      String processId = process.getId();
                      if (!isEqualToCurrentLocalizationValue(locale, processId, "name", name, infoNode)) {
                          dynamicBpmnService.changeLocalizationName(locale, processId, name, infoNode);
                          localizationValuesChanged = true;
                      }
  
                      if (
                          documentation != null &&
                          !isEqualToCurrentLocalizationValue(locale, processId, "description", documentation, infoNode)
                      ) {
                          dynamicBpmnService.changeLocalizationDescription(locale, processId, documentation, infoNode);
                          localizationValuesChanged = true;
                      }
  
                      break;
                  }
              }
          }
  
          boolean isFlowElementLocalizationChanged = localizeFlowElements(process.getFlowElements(), infoNode);
          boolean isDataObjectLocalizationChanged = localizeDataObjectElements(process.getDataObjects(), infoNode);
          if (isFlowElementLocalizationChanged || isDataObjectLocalizationChanged) {
              localizationValuesChanged = true;
          }
  
          if (localizationValuesChanged) {
              dynamicBpmnService.saveProcessDefinitionInfo(processDefinition.getId(), infoNode);
          }
      }
  
      protected boolean localizeFlowElements(Collection<FlowElement> flowElements, ObjectNode infoNode) {
          boolean localizationValuesChanged = false;
  
          if (flowElements == null) {
              return localizationValuesChanged;
          }
  
          CommandContext commandContext = Context.getCommandContext();
          DynamicBpmnService dynamicBpmnService = commandContext.getProcessEngineConfiguration().getDynamicBpmnService();
  
          for (FlowElement flowElement : flowElements) {
              if (flowElement instanceof UserTask || flowElement instanceof SubProcess) {
                  List<ExtensionElement> localizationElements = flowElement.getExtensionElements().get("localization");
                  if (localizationElements != null) {
                      for (ExtensionElement localizationElement : localizationElements) {
                          if (
                              BpmnXMLConstants.ACTIVITI_EXTENSIONS_PREFIX.equals(localizationElement.getNamespacePrefix())
                          ) {
                              String locale = localizationElement.getAttributeValue(null, "locale");
                              String name = localizationElement.getAttributeValue(null, "name");
                              String documentation = null;
                              List<ExtensionElement> documentationElements = localizationElement
                                  .getChildElements()
                                  .get("documentation");
                              if (documentationElements != null) {
                                  for (ExtensionElement documentationElement : documentationElements) {
                                      documentation = StringUtils.trimToNull(documentationElement.getElementText());
                                      break;
                                  }
                              }
  
                              String flowElementId = flowElement.getId();
                              if (
                                  isEqualToCurrentLocalizationValue(locale, flowElementId, "name", name, infoNode) ==
                                  false
                              ) {
                                  dynamicBpmnService.changeLocalizationName(locale, flowElementId, name, infoNode);
                                  localizationValuesChanged = true;
                              }
  
                              if (
                                  documentation != null &&
                                  isEqualToCurrentLocalizationValue(
                                      locale,
                                      flowElementId,
                                      "description",
                                      documentation,
                                      infoNode
                                  ) ==
                                  false
                              ) {
                                  dynamicBpmnService.changeLocalizationDescription(
                                      locale,
                                      flowElementId,
                                      documentation,
                                      infoNode
                                  );
                                  localizationValuesChanged = true;
                              }
  
                              break;
                          }
                      }
                  }
  
                  if (flowElement instanceof SubProcess) {
                      SubProcess subprocess = (SubProcess) flowElement;
                      boolean isFlowElementLocalizationChanged = localizeFlowElements(
                          subprocess.getFlowElements(),
                          infoNode
                      );
                      boolean isDataObjectLocalizationChanged = localizeDataObjectElements(
                          subprocess.getDataObjects(),
                          infoNode
                      );
                      if (isFlowElementLocalizationChanged || isDataObjectLocalizationChanged) {
                          localizationValuesChanged = true;
                      }
                  }
              }
          }
  
          return localizationValuesChanged;
      }
  
      protected boolean isEqualToCurrentLocalizationValue(
          String language,
          String id,
          String propertyName,
          String propertyValue,
          ObjectNode infoNode
      ) {
          boolean isEqual = false;
          JsonNode localizationNode = infoNode.path("localization").path(language).path(id).path(propertyName);
          if (
              !localizationNode.isMissingNode() &&
              !localizationNode.isNull() &&
              localizationNode.asText().equals(propertyValue)
          ) {
              isEqual = true;
          }
          return isEqual;
      }
  
      private void setProcessDefinitionAppVersion(ParsedDeployment parsedDeployment) {
          if (parsedDeployment.getDeployment().getProjectReleaseVersion() != null) {
              Integer version = parsedDeployment.getDeployment().getVersion();
              for (ProcessDefinitionEntity processDefinition : parsedDeployment.getAllProcessDefinitions()) {
                  processDefinition.setAppVersion(version);
              }
          }
      }
  
      protected boolean localizeDataObjectElements(List<ValuedDataObject> dataObjects, ObjectNode infoNode) {
          boolean localizationValuesChanged = false;
          CommandContext commandContext = Context.getCommandContext();
          DynamicBpmnService dynamicBpmnService = commandContext.getProcessEngineConfiguration().getDynamicBpmnService();
  
          for (ValuedDataObject dataObject : dataObjects) {
              List<ExtensionElement> localizationElements = dataObject.getExtensionElements().get("localization");
              if (localizationElements != null) {
                  for (ExtensionElement localizationElement : localizationElements) {
                      if (BpmnXMLConstants.ACTIVITI_EXTENSIONS_PREFIX.equals(localizationElement.getNamespacePrefix())) {
                          String locale = localizationElement.getAttributeValue(null, "locale");
                          String name = localizationElement.getAttributeValue(null, "name");
                          String documentation = null;
  
                          List<ExtensionElement> documentationElements = localizationElement
                              .getChildElements()
                              .get("documentation");
                          if (documentationElements != null) {
                              for (ExtensionElement documentationElement : documentationElements) {
                                  documentation = StringUtils.trimToNull(documentationElement.getElementText());
                                  break;
                              }
                          }
  
                          if (
                              name != null &&
                              isEqualToCurrentLocalizationValue(
                                  locale,
                                  dataObject.getId(),
                                  DynamicBpmnConstants.LOCALIZATION_NAME,
                                  name,
                                  infoNode
                              ) ==
                              false
                          ) {
                              dynamicBpmnService.changeLocalizationName(locale, dataObject.getId(), name, infoNode);
                              localizationValuesChanged = true;
                          }
  
                          if (
                              documentation != null &&
                              isEqualToCurrentLocalizationValue(
                                  locale,
                                  dataObject.getId(),
                                  DynamicBpmnConstants.LOCALIZATION_DESCRIPTION,
                                  documentation,
                                  infoNode
                              ) ==
                              false
                          ) {
                              dynamicBpmnService.changeLocalizationDescription(
                                  locale,
                                  dataObject.getId(),
                                  documentation,
                                  infoNode
                              );
                              localizationValuesChanged = true;
                          }
                      }
                  }
              }
          }
  
          return localizationValuesChanged;
      }
  
      public IdGenerator getIdGenerator() {
          return idGenerator;
      }
  
      public void setIdGenerator(IdGenerator idGenerator) {
          this.idGenerator = idGenerator;
      }
  
      public ParsedDeploymentBuilderFactory getExParsedDeploymentBuilderFactory() {
          return parsedDeploymentBuilderFactory;
      }
  
      public void setParsedDeploymentBuilderFactory(ParsedDeploymentBuilderFactory parsedDeploymentBuilderFactory) {
          this.parsedDeploymentBuilderFactory = parsedDeploymentBuilderFactory;
      }
  
      public BpmnDeploymentHelper getBpmnDeploymentHelper() {
          return bpmnDeploymentHelper;
      }
  
      public void setBpmnDeploymentHelper(BpmnDeploymentHelper bpmnDeploymentHelper) {
          this.bpmnDeploymentHelper = bpmnDeploymentHelper;
      }
  
      public CachingAndArtifactsManager getCachingAndArtifcatsManager() {
          return cachingAndArtifactsManager;
      }
  
      public void setCachingAndArtifactsManager(CachingAndArtifactsManager manager) {
          this.cachingAndArtifactsManager = manager;
      }
  
      public void setDisableExistingStartEventSubscriptions(boolean disableExistingStartEventSubscriptions) {
          this.disableExistingStartEventSubscriptions = disableExistingStartEventSubscriptions;
      }
  
      public boolean shouldDisableExistingStartEventSubscriptions() {
          return disableExistingStartEventSubscriptions;
      }
  }
  