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.validation.validator.impl;
  
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.stream.Collectors;
  import org.activiti.bpmn.model.BpmnModel;
  import org.activiti.bpmn.model.Process;
  import org.activiti.validation.ValidationError;
  import org.activiti.validation.validator.Constraints;
  import org.activiti.validation.validator.Problems;
  import org.activiti.validation.validator.ValidatorImpl;
  
  /**
  
  
   */
  public class BpmnModelValidator extends ValidatorImpl {
  
      @Override
      public void validate(BpmnModel bpmnModel, List<ValidationError> errors) {
          List<Process> processesDuplicated = getProcessesWithSameId(bpmnModel.getProcesses());
          if (!processesDuplicated.isEmpty()) {
              addError(errors, Problems.PROCESS_DEFINITION_ID_NOT_UNIQUE, processesDuplicated.get(0));
          }
  
          // If all process definitions of this bpmnModel are not executable, raise an error
          boolean isAtLeastOneExecutable = validateAtLeastOneExecutable(bpmnModel, errors);
  
          // If at least one process definition is executable, show a warning for each of the none-executables
          if (isAtLeastOneExecutable) {
              for (Process process : bpmnModel.getProcesses()) {
                  if (!process.isExecutable()) {
                      addWarning(errors, Problems.PROCESS_DEFINITION_NOT_EXECUTABLE, process, process);
                  }
                  handleProcessConstraints(bpmnModel, process, errors);
              }
          }
          handleBPMNModelConstraints(bpmnModel, errors);
      }
  
      protected void handleProcessConstraints(BpmnModel bpmnModel, Process process, List<ValidationError> errors) {
          if (process.getId() != null && process.getId().length() > Constraints.PROCESS_DEFINITION_ID_MAX_LENGTH) {
              Map<String, String> params = new HashMap<>();
              params.put("maxLength", String.valueOf(Constraints.PROCESS_DEFINITION_ID_MAX_LENGTH));
              addError(errors, Problems.PROCESS_DEFINITION_ID_TOO_LONG, process, params);
          }
          if (process.getName() != null && process.getName().length() > Constraints.PROCESS_DEFINITION_NAME_MAX_LENGTH) {
              Map<String, String> params = new HashMap<>();
              params.put("maxLength", String.valueOf(Constraints.PROCESS_DEFINITION_NAME_MAX_LENGTH));
              addError(errors, Problems.PROCESS_DEFINITION_NAME_TOO_LONG, process, params);
          }
          if (
              process.getDocumentation() != null &&
              process.getDocumentation().length() > Constraints.PROCESS_DEFINITION_DOCUMENTATION_MAX_LENGTH
          ) {
              Map<String, String> params = new HashMap<>();
              params.put("maxLength", String.valueOf(Constraints.PROCESS_DEFINITION_DOCUMENTATION_MAX_LENGTH));
              addError(errors, Problems.PROCESS_DEFINITION_DOCUMENTATION_TOO_LONG, process, params);
          }
      }
  
      protected void handleBPMNModelConstraints(BpmnModel bpmnModel, List<ValidationError> errors) {
          if (
              bpmnModel.getTargetNamespace() != null &&
              bpmnModel.getTargetNamespace().length() > Constraints.BPMN_MODEL_TARGET_NAMESPACE_MAX_LENGTH
          ) {
              Map<String, String> params = new HashMap<>();
              params.put("maxLength", String.valueOf(Constraints.BPMN_MODEL_TARGET_NAMESPACE_MAX_LENGTH));
              addError(errors, Problems.BPMN_MODEL_TARGET_NAMESPACE_TOO_LONG, params);
          }
      }
  
      /**
       * Returns 'true' if at least one process definition in the {@link BpmnModel} is executable.
       */
      protected boolean validateAtLeastOneExecutable(BpmnModel bpmnModel, List<ValidationError> errors) {
          int nrOfExecutableDefinitions = 0;
          for (Process process : bpmnModel.getProcesses()) {
              if (process.isExecutable()) {
                  nrOfExecutableDefinitions++;
              }
          }
  
          if (nrOfExecutableDefinitions == 0) {
              addError(errors, Problems.ALL_PROCESS_DEFINITIONS_NOT_EXECUTABLE);
          }
  
          return nrOfExecutableDefinitions > 0;
      }
  
      protected List<Process> getProcessesWithSameId(final List<Process> processes) {
          List<Process> filteredProcesses = processes
              .stream()
              .filter(process -> process.getName() != null)
              .collect(Collectors.toList());
          return getDuplicatesMap(filteredProcesses)
              .values()
              .stream()
              .filter(duplicates -> duplicates.size() > 1)
              .flatMap(Collection::stream)
              .collect(Collectors.toList());
      }
  
      private static Map<String, List<Process>> getDuplicatesMap(List<Process> processes) {
          return processes.stream().collect(Collectors.groupingBy(Process::getId));
      }
  }
  