All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.camunda.bpm.engine.impl.cmd.DeployCmd Maven / Gradle / Ivy

Go to download

The Camunda BPMN engine + configurable support for GraalVM and support for CockroachDB.

The newest version!
/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership. Camunda licenses this file to you under the Apache License,
 * Version 2.0; 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.camunda.bpm.engine.impl.cmd;

import java.io.ByteArrayInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;

import org.camunda.bpm.application.ProcessApplicationReference;
import org.camunda.bpm.application.ProcessApplicationRegistration;
import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.exception.NotFoundException;
import org.camunda.bpm.engine.exception.NotValidException;
import org.camunda.bpm.engine.history.UserOperationLogEntry;
import org.camunda.bpm.engine.impl.ProcessEngineLogger;
import org.camunda.bpm.engine.impl.bpmn.deployer.BpmnDeployer;
import org.camunda.bpm.engine.impl.cfg.CommandChecker;
import org.camunda.bpm.engine.impl.cfg.TransactionLogger;
import org.camunda.bpm.engine.impl.cfg.TransactionState;
import org.camunda.bpm.engine.impl.cmmn.deployer.CmmnDeployer;
import org.camunda.bpm.engine.impl.interceptor.Command;
import org.camunda.bpm.engine.impl.interceptor.CommandContext;
import org.camunda.bpm.engine.impl.persistence.deploy.DeploymentFailListener;
import org.camunda.bpm.engine.impl.persistence.entity.DeploymentEntity;
import org.camunda.bpm.engine.impl.persistence.entity.DeploymentManager;
import org.camunda.bpm.engine.impl.persistence.entity.ProcessApplicationDeploymentImpl;
import org.camunda.bpm.engine.impl.persistence.entity.ProcessDefinitionManager;
import org.camunda.bpm.engine.impl.persistence.entity.PropertyChange;
import org.camunda.bpm.engine.impl.persistence.entity.ResourceEntity;
import org.camunda.bpm.engine.impl.persistence.entity.ResourceManager;
import org.camunda.bpm.engine.impl.persistence.entity.UserOperationLogManager;
import org.camunda.bpm.engine.impl.repository.CandidateDeploymentImpl;
import org.camunda.bpm.engine.impl.repository.DeploymentBuilderImpl;
import org.camunda.bpm.engine.impl.repository.ProcessApplicationDeploymentBuilderImpl;
import org.camunda.bpm.engine.impl.util.ClockUtil;
import org.camunda.bpm.engine.impl.util.StringUtil;
import org.camunda.bpm.engine.repository.CandidateDeployment;
import org.camunda.bpm.engine.repository.Deployment;
import org.camunda.bpm.engine.repository.DeploymentHandler;
import org.camunda.bpm.engine.repository.DeploymentWithDefinitions;
import org.camunda.bpm.engine.repository.ProcessApplicationDeployment;
import org.camunda.bpm.engine.repository.ProcessApplicationDeploymentBuilder;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.repository.Resource;
import org.camunda.bpm.engine.repository.ResumePreviousBy;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.Process;
import org.camunda.bpm.model.cmmn.Cmmn;
import org.camunda.bpm.model.cmmn.CmmnModelInstance;
import org.camunda.bpm.model.cmmn.instance.Case;

/**
 * @author Tom Baeyens
 * @author Joram Barrez
 * @author Thorben Lindhauer
 * @author Daniel Meyer
 */
public class DeployCmd implements Command, Serializable {

  private static final long serialVersionUID = 1L;
  private static final CommandLogger LOG = ProcessEngineLogger.CMD_LOGGER;
  private static final TransactionLogger TX_LOG = ProcessEngineLogger.TX_LOGGER;

  protected DeploymentBuilderImpl deploymentBuilder;
  protected DeploymentHandler deploymentHandler;

  public DeployCmd(DeploymentBuilderImpl deploymentBuilder) {
    this.deploymentBuilder = deploymentBuilder;
  }

  @Override
  public DeploymentWithDefinitions execute(final CommandContext commandContext) {
    if (commandContext.getProcessEngineConfiguration().isDeploymentSynchronized()) {
      // ensure serial processing of multiple deployments on the same node.
      // We experienced deadlock situations with highly concurrent deployment of multiple
      // applications on Jboss & Wildfly
      synchronized (ProcessEngine.class) {
        return doExecute(commandContext);
      }
    } else {
      return doExecute(commandContext);
    }
  }

  protected DeploymentWithDefinitions doExecute(final CommandContext commandContext) {
    DeploymentManager deploymentManager = commandContext.getDeploymentManager();

    // load deployment handler
    ProcessEngine processEngine = commandContext.getProcessEngineConfiguration().getProcessEngine();
    deploymentHandler = commandContext.getProcessEngineConfiguration()
        .getDeploymentHandlerFactory()
        .buildDeploymentHandler(processEngine);

    Set deploymentIds = getAllDeploymentIds(deploymentBuilder);
    if (!deploymentIds.isEmpty()) {
      String[] deploymentIdArray = deploymentIds.toArray(new String[deploymentIds.size()]);
      List deployments = deploymentManager.findDeploymentsByIds(deploymentIdArray);
      ensureDeploymentsWithIdsExists(deploymentIds, deployments);
    }

    checkCreateAndReadDeployments(commandContext, deploymentIds);

    // set deployment name if it should retrieved from an existing deployment
    String nameFromDeployment = deploymentBuilder.getNameFromDeployment();
    setDeploymentName(nameFromDeployment, deploymentBuilder, commandContext);

    // get resources to re-deploy
    List resources = getResources(deploymentBuilder, commandContext);
    // .. and add them the builder
    addResources(resources, deploymentBuilder);

    Collection resourceNames = deploymentBuilder.getResourceNames();
    if (resourceNames == null || resourceNames.isEmpty()) {
      throw new NotValidException("No deployment resources contained to deploy.");
    }

    // perform deployment
    DeploymentWithDefinitions deployment = commandContext.runWithoutAuthorization(new Callable() {
      @Override
      public DeploymentWithDefinitions call() throws Exception {
        acquireExclusiveLock(commandContext);
        DeploymentEntity deploymentToRegister = initDeployment();
        Map resourcesToDeploy =
            resolveResourcesToDeploy(commandContext, deploymentToRegister);

        // save initial deployment resources before they are replaced with only the deployed ones
        CandidateDeployment candidateDeployment =
            CandidateDeploymentImpl.fromDeploymentEntity(deploymentToRegister);
        if (!resourcesToDeploy.isEmpty()) {
          LOG.debugCreatingNewDeployment();
          deploymentToRegister.setResources(resourcesToDeploy);
          deploy(commandContext, deploymentToRegister);
        } else {
          // if there are no resources to be deployed, find an existing deployment
          String duplicateDeploymentId =
              deploymentHandler.determineDuplicateDeployment(candidateDeployment);
          deploymentToRegister =
              commandContext.getDeploymentManager().findDeploymentById(duplicateDeploymentId);
        }

        scheduleProcessDefinitionActivation(commandContext, deploymentToRegister);

        if(deploymentBuilder instanceof ProcessApplicationDeploymentBuilder) {
          // for process application deployments, job executor registration
          // is managed by the ProcessApplicationManager
          ProcessApplicationRegistration registration = registerProcessApplication(
              commandContext,
              deploymentToRegister,
              candidateDeployment);

          return new ProcessApplicationDeploymentImpl(deploymentToRegister, registration);
        } else {
          registerWithJobExecutor(commandContext, deploymentToRegister);
        }

        return deploymentToRegister;
      }
    });

    createUserOperationLog(deploymentBuilder, deployment, commandContext);

    return deployment;
  }

  protected void acquireExclusiveLock(CommandContext commandContext) {
    if (commandContext.getProcessEngineConfiguration().isDeploymentLockUsed()) {
      // Acquire global exclusive lock: this ensures that there can be only one
      // transaction in the cluster which is allowed to perform deployments.
      // This is important to ensure that duplicate filtering works correctly
      // in a multi-node cluster. See also https://app.camunda.com/jira/browse/CAM-2128

      // It is also important to ensure the uniqueness of a process definition key,
      // version and tenant-id since there is no database constraint to check it.

      commandContext.getPropertyManager().acquireExclusiveLock();
    } else {
      LOG.warnDisabledDeploymentLock();
    }
  }

  protected Map resolveResourcesToDeploy(
      CommandContext commandContext,
      DeploymentEntity candidateDeployment) {

    Map resourcesToDeploy = new HashMap<>();
    Map candidateResources = candidateDeployment.getResources();

    if (deploymentBuilder.isDuplicateFilterEnabled()) {

      String source = candidateDeployment.getSource();
      if (source == null || source.isEmpty()) {
        source = ProcessApplicationDeployment.PROCESS_APPLICATION_DEPLOYMENT_SOURCE;
      }

      Map existingResources = commandContext
          .getResourceManager()
          .findLatestResourcesByDeploymentName(
              candidateDeployment.getName(),
              candidateResources.keySet(),
              source,
              candidateDeployment.getTenantId());

      for (ResourceEntity deployedResource : candidateResources.values()) {
        String resourceName = deployedResource.getName();
        ResourceEntity existingResource = existingResources.get(resourceName);

        if (existingResource == null
            || existingResource.isGenerated()
            || deploymentHandler.shouldDeployResource(deployedResource, existingResource)) {

          if (deploymentBuilder.isDeployChangedOnly()) {
            // resource should be deployed
            resourcesToDeploy.put(resourceName, deployedResource);
          } else {
            // all resources should be deployed
            resourcesToDeploy = candidateResources;
            break;
          }
        }
      }

    } else {
      resourcesToDeploy = candidateResources;
    }

    return resourcesToDeploy;
  }

  protected void deploy(CommandContext commandContext, DeploymentEntity deployment) {
    deployment.setNew(true);
    commandContext.getDeploymentManager().insertDeployment(deployment);
  }

  protected void scheduleProcessDefinitionActivation(CommandContext commandContext,
      DeploymentWithDefinitions deployment) {

    if (deploymentBuilder.getProcessDefinitionsActivationDate() != null) {
      RepositoryService repositoryService = commandContext.getProcessEngineConfiguration()
          .getRepositoryService();

      for (ProcessDefinition processDefinition: getDeployedProcesses(commandContext, deployment)) {

        // If activation date is set, we first suspend all the process definition
        repositoryService
            .updateProcessDefinitionSuspensionState()
            .byProcessDefinitionId(processDefinition.getId())
            .suspend();

        // And we schedule an activation at the provided date
        repositoryService
            .updateProcessDefinitionSuspensionState()
            .byProcessDefinitionId(processDefinition.getId())
            .executionDate(deploymentBuilder.getProcessDefinitionsActivationDate())
            .activate();
      }
    }
  }

  protected ProcessApplicationRegistration registerProcessApplication(CommandContext commandContext,
      DeploymentEntity deploymentToRegister,
      CandidateDeployment candidateDeployment) {

    ProcessApplicationDeploymentBuilderImpl appDeploymentBuilder = (ProcessApplicationDeploymentBuilderImpl) deploymentBuilder;
    final ProcessApplicationReference appReference = appDeploymentBuilder.getProcessApplicationReference();

    // build set of deployment ids this process app should be registered for:
    Set deploymentsToRegister = new HashSet<>(Collections.singleton(deploymentToRegister.getId()));
    if (appDeploymentBuilder.isResumePreviousVersions()) {
      String resumePreviousBy = appDeploymentBuilder.getResumePreviousVersionsBy();

      switch (resumePreviousBy) {

      case ResumePreviousBy.RESUME_BY_DEPLOYMENT_NAME:

        deploymentsToRegister.addAll(deploymentHandler
            .determineDeploymentsToResumeByDeploymentName(candidateDeployment));
        break;

      case ResumePreviousBy.RESUME_BY_PROCESS_DEFINITION_KEY:
      default:

        String[] processDefinitionKeys = getProcessDefinitionsFromResources(commandContext,
            deploymentToRegister,
            candidateDeployment);

        // only determine deployments to resume if there are actual process definitions to look for
        if (processDefinitionKeys.length > 0) {
          deploymentsToRegister.addAll(deploymentHandler
              .determineDeploymentsToResumeByProcessDefinitionKey(processDefinitionKeys));
        }
        break;
      }
    }

    // register process application for deployments
    return new RegisterProcessApplicationCmd(deploymentsToRegister, appReference).execute(commandContext);
  }

  protected void registerWithJobExecutor(CommandContext commandContext, Deployment deployment) {
    try {
      new RegisterDeploymentCmd(deployment.getId()).execute(commandContext);

    } finally {
      DeploymentFailListener listener = new DeploymentFailListener(deployment.getId(),
          commandContext.getProcessEngineConfiguration().getCommandExecutorTxRequiresNew());

      try {
        commandContext.getTransactionContext().addTransactionListener(TransactionState.ROLLED_BACK, listener);
      } catch (Exception e) {
        TX_LOG.debugTransactionOperation("Could not register transaction synchronization. Probably the TX has already been rolled back by application code.");
        listener.execute(commandContext);
      }
    }
  }

  // setters, initializers etc.

  protected void createUserOperationLog(DeploymentBuilderImpl deploymentBuilder, Deployment deployment, CommandContext commandContext) {
    UserOperationLogManager logManager = commandContext.getOperationLogManager();

    List properties = new ArrayList<>();

    PropertyChange filterDuplicate = new PropertyChange("duplicateFilterEnabled", null, deploymentBuilder.isDuplicateFilterEnabled());
    properties.add(filterDuplicate);

    if (deploymentBuilder.isDuplicateFilterEnabled()) {
      PropertyChange deployChangedOnly = new PropertyChange("deployChangedOnly", null, deploymentBuilder.isDeployChangedOnly());
      properties.add(deployChangedOnly);
    }

    logManager.logDeploymentOperation(UserOperationLogEntry.OPERATION_TYPE_CREATE, deployment.getId(), properties);
  }

  protected DeploymentEntity initDeployment() {
    DeploymentEntity deployment = deploymentBuilder.getDeployment();
    deployment.setDeploymentTime(ClockUtil.getCurrentTime());
    return deployment;
  }

  protected void setDeploymentName(String deploymentId, DeploymentBuilderImpl deploymentBuilder, CommandContext commandContext) {
    if (deploymentId != null && !deploymentId.isEmpty()) {
      DeploymentManager deploymentManager = commandContext.getDeploymentManager();
      DeploymentEntity deployment = deploymentManager.findDeploymentById(deploymentId);
      deploymentBuilder.getDeployment().setName(deployment.getName());
    }
  }

  protected void addResources(List resources, DeploymentBuilderImpl deploymentBuilder) {
    DeploymentEntity deployment = deploymentBuilder.getDeployment();
    Map existingResources = deployment.getResources();

    for (ResourceEntity resource : resources) {
      String resourceName = resource.getName();

      if (existingResources != null && existingResources.containsKey(resourceName)) {
        String message = String.format("Cannot add resource with id '%s' and name '%s' from "
            + "deployment with id '%s' to new deployment because the new deployment contains "
            + "already a resource with same name.", resource.getId(), resourceName, resource.getDeploymentId());

        throw new NotValidException(message);
      }

      ByteArrayInputStream inputStream = new ByteArrayInputStream(resource.getBytes());
      deploymentBuilder.addInputStream(resourceName, inputStream);
    }
  }

  // getters

  protected List getMissingElements(Set expected, Map actual) {
    List missingElements = new ArrayList<>();
    for (String value : expected) {
      if (!actual.containsKey(value)) {
        missingElements.add(value);
      }
    }
    return missingElements;
  }

  protected List getResources(final DeploymentBuilderImpl deploymentBuilder, final CommandContext commandContext) {
    List resources = new ArrayList<>();

    Set deploymentIds = deploymentBuilder.getDeployments();
    resources.addAll(getResourcesByDeploymentId(deploymentIds, commandContext));

    Map> deploymentResourcesById = deploymentBuilder.getDeploymentResourcesById();
    resources.addAll(getResourcesById(deploymentResourcesById, commandContext));

    Map> deploymentResourcesByName = deploymentBuilder.getDeploymentResourcesByName();
    resources.addAll(getResourcesByName(deploymentResourcesByName, commandContext));

    checkDuplicateResourceName(resources);

    return resources;
  }

  protected List getResourcesByDeploymentId(Set deploymentIds, CommandContext commandContext) {
    List result = new ArrayList<>();

    if (!deploymentIds.isEmpty()) {

      DeploymentManager deploymentManager = commandContext.getDeploymentManager();

      for (String deploymentId : deploymentIds) {
        DeploymentEntity deployment = deploymentManager.findDeploymentById(deploymentId);
        Map resources = deployment.getResources();
        Collection values = resources.values();
        result.addAll(values);
      }
    }

    return result;
  }

  protected List getResourcesById(Map> resourcesById, CommandContext commandContext) {
    List result = new ArrayList<>();

    ResourceManager resourceManager = commandContext.getResourceManager();

    for (String deploymentId : resourcesById.keySet()) {
      Set resourceIds = resourcesById.get(deploymentId);

      String[] resourceIdArray = resourceIds.toArray(new String[resourceIds.size()]);
      List resources = resourceManager.findResourceByDeploymentIdAndResourceIds(deploymentId, resourceIdArray);

      ensureResourcesWithIdsExist(deploymentId, resourceIds, resources);

      result.addAll(resources);
    }

    return result;
  }

  protected List getResourcesByName(Map> resourcesByName, CommandContext commandContext) {
    List result = new ArrayList<>();

    ResourceManager resourceManager = commandContext.getResourceManager();

    for (String deploymentId : resourcesByName.keySet()) {
      Set resourceNames = resourcesByName.get(deploymentId);

      String[] resourceNameArray = resourceNames.toArray(new String[resourceNames.size()]);
      List resources = resourceManager.findResourceByDeploymentIdAndResourceNames(deploymentId, resourceNameArray);

      ensureResourcesWithNamesExist(deploymentId, resourceNames, resources);

      result.addAll(resources);
    }

    return result;
  }

  protected List getDeployedProcesses(CommandContext commandContext, DeploymentWithDefinitions deployment) {
    List deployedProcessDefinitions = deployment.getDeployedProcessDefinitions();
    if (deployedProcessDefinitions == null) {
      // existing deployment
      ProcessDefinitionManager manager = commandContext.getProcessDefinitionManager();
      deployedProcessDefinitions = manager.findProcessDefinitionsByDeploymentId(deployment.getId());
    }

    return deployedProcessDefinitions;
  }

  protected String[] getProcessDefinitionsFromResources(CommandContext commandContext,
      DeploymentEntity deploymentToRegister,
      CandidateDeployment candidateDeployment) {

    Set processDefinitionKeys = new HashSet<>();

    // get process definition keys for updated process definitions
    for (ProcessDefinition processDefinition :
        getDeployedProcesses(commandContext, deploymentToRegister)) {
      if (processDefinition.getVersion() > 1) {
        processDefinitionKeys.add(processDefinition.getKey());
      }
    }

    // get process definition keys for already available process definitions
    Map candidateResources = candidateDeployment.getResources();
    // and remove resources that are getting deployed, so that we don't do double checks
    candidateResources.keySet().removeAll(deploymentToRegister.getResources().keySet());

    for (Resource resource : candidateResources.values()) {
      if (isBpmnResource(resource)) {

        ByteArrayInputStream byteStream = new ByteArrayInputStream(resource.getBytes());
        BpmnModelInstance model = Bpmn.readModelFromStream(byteStream);
        for (Process process : model.getDefinitions().getChildElementsByType(Process.class)) {
          processDefinitionKeys.add(process.getId());
        }
      } else if (isCmmnResource(resource)) {

        ByteArrayInputStream byteStream = new ByteArrayInputStream(resource.getBytes());
        CmmnModelInstance model = Cmmn.readModelFromStream(byteStream);
        for (Case cmmnCase : model.getDefinitions().getCases()) {
          processDefinitionKeys.add(cmmnCase.getId());
        }
      }
    }

    return processDefinitionKeys.toArray(new String[processDefinitionKeys.size()]);
  }

  protected Set getAllDeploymentIds(DeploymentBuilderImpl deploymentBuilder) {
    Set result = new HashSet<>();

    String nameFromDeployment = deploymentBuilder.getNameFromDeployment();
    if (nameFromDeployment != null && !nameFromDeployment.isEmpty()) {
      result.add(nameFromDeployment);
    }

    Set deployments = deploymentBuilder.getDeployments();
    result.addAll(deployments);

    deployments = deploymentBuilder.getDeploymentResourcesById().keySet();
    result.addAll(deployments);

    deployments = deploymentBuilder.getDeploymentResourcesByName().keySet();
    result.addAll(deployments);

    return result;
  }

  // checkers

  protected void checkDuplicateResourceName(List resources) {
    Map resourceMap = new HashMap<>();

    for (ResourceEntity resource : resources) {
      String name = resource.getName();

      ResourceEntity duplicate = resourceMap.get(name);
      if (duplicate != null) {
        String deploymentId = resource.getDeploymentId();
        if (!deploymentId.equals(duplicate.getDeploymentId())) {
          String message = String.format("The deployments with id '%s' and '%s' contain a resource with same name '%s'.", deploymentId, duplicate.getDeploymentId(), name);
          throw new NotValidException(message);
        }
      }
      resourceMap.put(name, resource);
    }
  }

  protected void checkCreateAndReadDeployments(CommandContext commandContext, Set deploymentIds) {
    for(CommandChecker checker : commandContext.getProcessEngineConfiguration().getCommandCheckers()) {
      checker.checkCreateDeployment();
      for (String deploymentId : deploymentIds) {
        checker.checkReadDeployment(deploymentId);
      }
    }
  }

  protected boolean isBpmnResource(Resource resourceEntity) {
    return StringUtil.hasAnySuffix(resourceEntity.getName(), BpmnDeployer.BPMN_RESOURCE_SUFFIXES);
  }

  protected boolean isCmmnResource(Resource resourceEntity) {
    return StringUtil.hasAnySuffix(resourceEntity.getName(), CmmnDeployer.CMMN_RESOURCE_SUFFIXES);
  }

  // ensures

  protected void ensureDeploymentsWithIdsExists(Set expected, List actual) {
    Map deploymentMap = new HashMap<>();
    for (DeploymentEntity deployment : actual) {
      deploymentMap.put(deployment.getId(), deployment);
    }

    List missingDeployments = getMissingElements(expected, deploymentMap);

    if (!missingDeployments.isEmpty()) {
      StringBuilder builder = new StringBuilder();

      builder.append("The following deployments are not found by id: ");

      boolean first = true;
      for(String missingDeployment: missingDeployments) {
        if (!first) {
          builder.append(", ");
        } else {
          first = false;
        }
        builder.append(missingDeployment);
      }

      throw new NotFoundException(builder.toString());
    }
  }

  protected void ensureResourcesWithIdsExist(String deploymentId, Set expectedIds, List actual) {
    Map resources = new HashMap<>();
    for (ResourceEntity resource : actual) {
      resources.put(resource.getId(), resource);
    }
    ensureResourcesWithKeysExist(deploymentId, expectedIds, resources, "id");
  }

  protected void ensureResourcesWithNamesExist(String deploymentId, Set expectedNames, List actual) {
    Map resources = new HashMap<>();
    for (ResourceEntity resource : actual) {
      resources.put(resource.getName(), resource);
    }
    ensureResourcesWithKeysExist(deploymentId, expectedNames, resources, "name");
  }

  protected void ensureResourcesWithKeysExist(String deploymentId, Set expectedKeys, Map actual, String valueProperty) {
    List missingResources = getMissingElements(expectedKeys, actual);

    if (!missingResources.isEmpty()) {
      StringBuilder builder = new StringBuilder();

      builder.append("The deployment with id '");
      builder.append(deploymentId);
      builder.append("' does not contain the following resources with ");
      builder.append(valueProperty);
      builder.append(": ");

      boolean first = true;
      for(String missingResource: missingResources) {
        if (!first) {
          builder.append(", ");
        } else {
          first = false;
        }
        builder.append(missingResource);
      }

      throw new NotFoundException(builder.toString());
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy