protected void removeObsoleteTimers(ProcessDefinitionEntity processDefinition) {

    List<Job> jobsToDelete = null;

    if (processDefinition.getTenantId() != null
        && !ProcessEngineConfiguration.NO_TENANT_ID.equals(processDefinition.getTenantId())) {
      jobsToDelete =
          Context.getCommandContext()
              .getJobEntityManager()
              .findJobsByTypeAndProcessDefinitionKeyAndTenantId(
                  TimerStartEventJobHandler.TYPE,
                  processDefinition.getKey(),
                  processDefinition.getTenantId());
    } else {
      jobsToDelete =
          Context.getCommandContext()
              .getJobEntityManager()
              .findJobsByTypeAndProcessDefinitionKeyNoTenantId(
                  TimerStartEventJobHandler.TYPE, processDefinition.getKey());
    }

    if (jobsToDelete != null) {
      for (Job job : jobsToDelete) {
        new CancelJobsCmd(job.getId()).execute(Context.getCommandContext());
      }
    }
  }
  @SuppressWarnings("unchecked")
  protected void addSignalEventSubscriptions(ProcessDefinitionEntity processDefinition) {
    List<EventSubscriptionDeclaration> eventDefinitions =
        (List<EventSubscriptionDeclaration>)
            processDefinition.getProperty(BpmnParse.PROPERTYNAME_EVENT_SUBSCRIPTION_DECLARATION);
    if (eventDefinitions != null) {
      for (EventSubscriptionDeclaration eventDefinition : eventDefinitions) {
        if (eventDefinition.getEventType().equals("signal") && eventDefinition.isStartEvent()) {

          SignalEventSubscriptionEntity subscriptionEntity = new SignalEventSubscriptionEntity();
          subscriptionEntity.setEventName(eventDefinition.getEventName());
          subscriptionEntity.setActivityId(eventDefinition.getActivityId());
          subscriptionEntity.setProcessDefinitionId(processDefinition.getId());
          if (processDefinition.getTenantId() != null) {
            subscriptionEntity.setTenantId(processDefinition.getTenantId());
          }
          subscriptionEntity.insert();
        }
      }
    }
  }
  @SuppressWarnings("unchecked")
  protected void addTimerDeclarations(
      ProcessDefinitionEntity processDefinition, List<TimerEntity> timers) {
    List<TimerDeclarationImpl> timerDeclarations =
        (List<TimerDeclarationImpl>)
            processDefinition.getProperty(BpmnParse.PROPERTYNAME_START_TIMER);
    if (timerDeclarations != null) {
      for (TimerDeclarationImpl timerDeclaration : timerDeclarations) {
        TimerEntity timer = timerDeclaration.prepareTimerEntity(null);
        if (timer != null) {
          timer.setProcessDefinitionId(processDefinition.getId());

          // Inherit timer (if appliccable)
          if (processDefinition.getTenantId() != null) {
            timer.setTenantId(processDefinition.getTenantId());
          }
          timers.add(timer);
        }
      }
    }
  }
  protected void removeExistingSignalEventSubScription(
      ProcessDefinitionEntity processDefinition, ProcessDefinitionEntity latestProcessDefinition) {
    if (latestProcessDefinition != null) {
      CommandContext commandContext = Context.getCommandContext();

      List<EventSubscriptionEntity> subscriptionsToDisable =
          commandContext
              .getEventSubscriptionEntityManager()
              .findEventSubscriptionsByTypeAndProcessDefinitionId(
                  SignalEventHandler.EVENT_HANDLER_TYPE,
                  latestProcessDefinition.getId(),
                  latestProcessDefinition.getTenantId());

      for (EventSubscriptionEntity eventSubscriptionEntity : subscriptionsToDisable) {
        eventSubscriptionEntity.delete();
      }
    }
  }
  public void deploy(DeploymentEntity deployment, Map<String, Object> deploymentSettings) {
    log.debug("Processing deployment {}", deployment.getName());

    List<ProcessDefinitionEntity> processDefinitions = new ArrayList<ProcessDefinitionEntity>();
    Map<String, ResourceEntity> resources = deployment.getResources();
    Map<String, BpmnModel> bpmnModelMap = new HashMap<String, BpmnModel>();

    final ProcessEngineConfigurationImpl processEngineConfiguration =
        Context.getProcessEngineConfiguration();
    for (String resourceName : resources.keySet()) {

      log.info("Processing resource {}", resourceName);
      if (isBpmnResource(resourceName)) {
        ResourceEntity resource = resources.get(resourceName);
        byte[] bytes = resource.getBytes();
        ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);

        BpmnParse bpmnParse =
            bpmnParser
                .createParse()
                .sourceInputStream(inputStream)
                .setSourceSystemId(resourceName)
                .deployment(deployment)
                .name(resourceName);

        if (deploymentSettings != null) {

          // Schema validation if needed
          if (deploymentSettings.containsKey(DeploymentSettings.IS_BPMN20_XSD_VALIDATION_ENABLED)) {
            bpmnParse.setValidateSchema(
                (Boolean)
                    deploymentSettings.get(DeploymentSettings.IS_BPMN20_XSD_VALIDATION_ENABLED));
          }

          // Process validation if needed
          if (deploymentSettings.containsKey(DeploymentSettings.IS_PROCESS_VALIDATION_ENABLED)) {
            bpmnParse.setValidateProcess(
                (Boolean) deploymentSettings.get(DeploymentSettings.IS_PROCESS_VALIDATION_ENABLED));
          }

        } else {
          // On redeploy, we assume it is validated at the first deploy
          bpmnParse.setValidateSchema(false);
          bpmnParse.setValidateProcess(false);
        }

        bpmnParse.execute();

        for (ProcessDefinitionEntity processDefinition : bpmnParse.getProcessDefinitions()) {
          processDefinition.setResourceName(resourceName);

          if (deployment.getTenantId() != null) {
            processDefinition.setTenantId(
                deployment.getTenantId()); // process definition inherits the tenant id
          }

          String diagramResourceName =
              getDiagramResourceForProcess(resourceName, processDefinition.getKey(), resources);

          // Only generate the resource when deployment is new to prevent modification of deployment
          // resources
          // after the process-definition is actually deployed. Also to prevent resource-generation
          // failure every
          // time the process definition is added to the deployment-cache when diagram-generation
          // has failed the first time.
          if (deployment.isNew()) {
            if (processEngineConfiguration.isCreateDiagramOnDeploy()
                && diagramResourceName == null
                && processDefinition.isGraphicalNotationDefined()) {
              try {
                byte[] diagramBytes =
                    IoUtil.readInputStream(
                        processEngineConfiguration
                            .getProcessDiagramGenerator()
                            .generateDiagram(
                                bpmnParse.getBpmnModel(),
                                "png",
                                processEngineConfiguration.getActivityFontName(),
                                processEngineConfiguration.getLabelFontName(),
                                processEngineConfiguration.getClassLoader()),
                        null);
                diagramResourceName =
                    getProcessImageResourceName(resourceName, processDefinition.getKey(), "png");
                createResource(diagramResourceName, diagramBytes, deployment);
              } catch (
                  Throwable
                      t) { // if anything goes wrong, we don't store the image (the process will
                           // still be executable).
                log.warn(
                    "Error while generating process diagram, image will not be stored in repository",
                    t);
              }
            }
          }

          processDefinition.setDiagramResourceName(diagramResourceName);
          processDefinitions.add(processDefinition);
          bpmnModelMap.put(processDefinition.getKey(), bpmnParse.getBpmnModel());
        }
      }
    }

    // check if there are process definitions with the same process key to prevent database unique
    // index violation
    List<String> keyList = new ArrayList<String>();
    for (ProcessDefinitionEntity processDefinition : processDefinitions) {
      if (keyList.contains(processDefinition.getKey())) {
        throw new ActivitiException(
            "The deployment contains process definitions with the same key (process id atrribute), this is not allowed");
      }
      keyList.add(processDefinition.getKey());
    }

    CommandContext commandContext = Context.getCommandContext();
    ProcessDefinitionEntityManager processDefinitionManager =
        commandContext.getProcessDefinitionEntityManager();
    DbSqlSession dbSqlSession = commandContext.getSession(DbSqlSession.class);
    for (ProcessDefinitionEntity processDefinition : processDefinitions) {
      List<TimerEntity> timers = new ArrayList<TimerEntity>();
      if (deployment.isNew()) {
        int processDefinitionVersion;

        ProcessDefinitionEntity latestProcessDefinition = null;
        if (processDefinition.getTenantId() != null
            && !ProcessEngineConfiguration.NO_TENANT_ID.equals(processDefinition.getTenantId())) {
          latestProcessDefinition =
              processDefinitionManager.findLatestProcessDefinitionByKeyAndTenantId(
                  processDefinition.getKey(), processDefinition.getTenantId());
        } else {
          latestProcessDefinition =
              processDefinitionManager.findLatestProcessDefinitionByKey(processDefinition.getKey());
        }

        if (latestProcessDefinition != null) {
          processDefinitionVersion = latestProcessDefinition.getVersion() + 1;
        } else {
          processDefinitionVersion = 1;
        }

        processDefinition.setVersion(processDefinitionVersion);
        processDefinition.setDeploymentId(deployment.getId());

        String nextId = idGenerator.getNextId();
        String processDefinitionId =
            processDefinition.getKey()
                + ":"
                + processDefinition.getVersion()
                + ":"
                + nextId; // ACT-505

        // ACT-115: maximum id length is 64 charcaters
        if (processDefinitionId.length() > 64) {
          processDefinitionId = nextId;
        }
        processDefinition.setId(processDefinitionId);

        if (commandContext.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
          commandContext
              .getProcessEngineConfiguration()
              .getEventDispatcher()
              .dispatchEvent(
                  ActivitiEventBuilder.createEntityEvent(
                      ActivitiEventType.ENTITY_CREATED, processDefinition));
        }

        removeObsoleteTimers(processDefinition);
        addTimerDeclarations(processDefinition, timers);

        removeExistingMessageEventSubscriptions(processDefinition, latestProcessDefinition);
        addMessageEventSubscriptions(processDefinition);

        removeExistingSignalEventSubScription(processDefinition, latestProcessDefinition);
        addSignalEventSubscriptions(processDefinition);

        dbSqlSession.insert(processDefinition);
        addAuthorizations(processDefinition);

        if (commandContext.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
          commandContext
              .getProcessEngineConfiguration()
              .getEventDispatcher()
              .dispatchEvent(
                  ActivitiEventBuilder.createEntityEvent(
                      ActivitiEventType.ENTITY_INITIALIZED, processDefinition));
        }

        scheduleTimers(timers);

      } else {
        String deploymentId = deployment.getId();
        processDefinition.setDeploymentId(deploymentId);

        ProcessDefinitionEntity persistedProcessDefinition = null;
        if (processDefinition.getTenantId() == null
            || ProcessEngineConfiguration.NO_TENANT_ID.equals(processDefinition.getTenantId())) {
          persistedProcessDefinition =
              processDefinitionManager.findProcessDefinitionByDeploymentAndKey(
                  deploymentId, processDefinition.getKey());
        } else {
          persistedProcessDefinition =
              processDefinitionManager.findProcessDefinitionByDeploymentAndKeyAndTenantId(
                  deploymentId, processDefinition.getKey(), processDefinition.getTenantId());
        }

        if (persistedProcessDefinition != null) {
          processDefinition.setId(persistedProcessDefinition.getId());
          processDefinition.setVersion(persistedProcessDefinition.getVersion());
          processDefinition.setSuspensionState(persistedProcessDefinition.getSuspensionState());
        }
      }

      // Add to cache
      DeploymentManager deploymentManager = processEngineConfiguration.getDeploymentManager();
      deploymentManager
          .getProcessDefinitionCache()
          .add(processDefinition.getId(), processDefinition);
      addDefinitionInfoToCache(processDefinition, processEngineConfiguration, commandContext);

      // Add to deployment for further usage
      deployment.addDeployedArtifact(processDefinition);

      createLocalizationValues(
          processDefinition.getId(),
          bpmnModelMap.get(processDefinition.getKey()).getProcessById(processDefinition.getKey()));
    }
  }
  @SuppressWarnings("unchecked")
  protected void addMessageEventSubscriptions(ProcessDefinitionEntity processDefinition) {
    CommandContext commandContext = Context.getCommandContext();
    List<EventSubscriptionDeclaration> eventDefinitions =
        (List<EventSubscriptionDeclaration>)
            processDefinition.getProperty(BpmnParse.PROPERTYNAME_EVENT_SUBSCRIPTION_DECLARATION);
    if (eventDefinitions != null) {

      Set<String> messageNames = new HashSet<String>();
      for (EventSubscriptionDeclaration eventDefinition : eventDefinitions) {
        if (eventDefinition.getEventType().equals("message") && eventDefinition.isStartEvent()) {

          if (!messageNames.contains(eventDefinition.getEventName())) {
            messageNames.add(eventDefinition.getEventName());
          } else {
            throw new ActivitiException(
                "Cannot deploy process definition '"
                    + processDefinition.getResourceName()
                    + "': there are multiple message event subscriptions for the message with name '"
                    + eventDefinition.getEventName()
                    + "'.");
          }

          // look for subscriptions for the same name in db:
          List<EventSubscriptionEntity> subscriptionsForSameMessageName =
              commandContext
                  .getEventSubscriptionEntityManager()
                  .findEventSubscriptionsByName(
                      MessageEventHandler.EVENT_HANDLER_TYPE,
                      eventDefinition.getEventName(),
                      processDefinition.getTenantId());

          // also look for subscriptions created in the session:
          List<MessageEventSubscriptionEntity> cachedSubscriptions =
              commandContext.getDbSqlSession().findInCache(MessageEventSubscriptionEntity.class);
          for (MessageEventSubscriptionEntity cachedSubscription : cachedSubscriptions) {
            if (eventDefinition.getEventName().equals(cachedSubscription.getEventName())
                && !subscriptionsForSameMessageName.contains(cachedSubscription)) {
              subscriptionsForSameMessageName.add(cachedSubscription);
            }
          }

          // remove subscriptions deleted in the same command
          subscriptionsForSameMessageName =
              commandContext
                  .getDbSqlSession()
                  .pruneDeletedEntities(subscriptionsForSameMessageName);

          for (EventSubscriptionEntity eventSubscriptionEntity : subscriptionsForSameMessageName) {
            // throw exception only if there's already a subscription as start event

            // no process instance-id = it's a message start event
            if (StringUtils.isEmpty(eventSubscriptionEntity.getProcessInstanceId())) {
              throw new ActivitiException(
                  "Cannot deploy process definition '"
                      + processDefinition.getResourceName()
                      + "': there already is a message event subscription for the message with name '"
                      + eventDefinition.getEventName()
                      + "'.");
            }
          }

          MessageEventSubscriptionEntity newSubscription = new MessageEventSubscriptionEntity();
          newSubscription.setEventName(eventDefinition.getEventName());
          newSubscription.setActivityId(eventDefinition.getActivityId());
          newSubscription.setConfiguration(processDefinition.getId());
          newSubscription.setProcessDefinitionId(processDefinition.getId());

          if (processDefinition.getTenantId() != null) {
            newSubscription.setTenantId(processDefinition.getTenantId());
          }

          newSubscription.insert();
        }
      }
    }
  }