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.runtime.api.impl;
  
  import java.text.MessageFormat;
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Optional;
  import java.util.Set;
  import java.util.stream.Collectors;
  import org.activiti.api.process.model.payloads.ReceiveMessagePayload;
  import org.activiti.api.process.model.payloads.SetProcessVariablesPayload;
  import org.activiti.api.process.model.payloads.SignalPayload;
  import org.activiti.api.process.model.payloads.StartMessagePayload;
  import org.activiti.api.process.model.payloads.StartProcessPayload;
  import org.activiti.common.util.DateFormatterProvider;
  import org.activiti.engine.ActivitiException;
  import org.activiti.spring.process.ProcessExtensionService;
  import org.activiti.spring.process.model.Extension;
  import org.activiti.spring.process.model.VariableDefinition;
  import org.activiti.spring.process.variable.VariableValidationService;
  
  public class ProcessVariablesPayloadValidator {
  
      private final VariableValidationService variableValidationService;
      private final DateFormatterProvider dateFormatterProvider;
      private final ProcessExtensionService processExtensionService;
      private final VariableNameValidator variableNameValidator;
      private final ExpressionResolver expressionResolver;
  
      public ProcessVariablesPayloadValidator(
          DateFormatterProvider dateFormatterProvider,
          ProcessExtensionService processExtensionService,
          VariableValidationService variableValidationService,
          VariableNameValidator variableNameValidator,
          ExpressionResolver expressionResolver
      ) {
          this.dateFormatterProvider = dateFormatterProvider;
          this.processExtensionService = processExtensionService;
          this.variableValidationService = variableValidationService;
          this.variableNameValidator = variableNameValidator;
          this.expressionResolver = expressionResolver;
      }
  
      private Optional<Map<String, VariableDefinition>> getVariableDefinitionMap(String processDefinitionId) {
          Extension processExtensionModel = processDefinitionId != null
              ? processExtensionService.getExtensionsForId(processDefinitionId)
              : null;
  
          return Optional.ofNullable(processExtensionModel).map(Extension::getProperties);
      }
  
      private boolean validateVariablesAgainstDefinitions(
          Optional<Map<String, VariableDefinition>> variableDefinitionMap,
          Map.Entry<String, Object> payloadVar,
          Set<String> mismatchedVars
      ) {
          if (variableDefinitionMap.isPresent()) {
              String name = payloadVar.getKey();
              Object value = payloadVar.getValue();
  
              for (Map.Entry<String, VariableDefinition> variableDefinitionEntry : variableDefinitionMap
                  .get()
                  .entrySet()) {
                  if (variableDefinitionEntry.getValue().getName().equals(name)) {
                      String type = variableDefinitionEntry.getValue().getType();
  
                      if (type.contains("date") && value != null) {
                          try {
                              payloadVar.setValue(dateFormatterProvider.toDate(value));
                          } catch (Exception e) {
                              //Do nothing here, keep value as a string
                          }
                      }
  
                      //Check type
                      if (
                          !variableValidationService
                              .validateWithErrors(payloadVar.getValue(), variableDefinitionEntry.getValue())
                              .isEmpty()
                      ) {
                          mismatchedVars.add(name);
                      }
  
                      return true;
                  }
              }
          }
  
          return false;
      }
  
      private void checkPayloadVariables(Map<String, Object> variablePayloadMap, String processDefinitionId) {
          if (variablePayloadMap == null) {
              return;
          }
  
          final String errorVariableName = "Variable has not a valid name: {0}";
          final String errorVariableType = "Variables fail type validation: {0}";
          final String errorVariableExpressionValue =
              "Expressions in variable values are only allowed as default value when modeling the process: {0}";
  
          final Optional<Map<String, VariableDefinition>> variableDefinitionMap = getVariableDefinitionMap(
              processDefinitionId
          );
          List<ActivitiException> activitiExceptions = new ArrayList<>();
          Set<String> mismatchedVars = new HashSet<>();
  
          for (Map.Entry<String, Object> payloadVar : variablePayloadMap.entrySet()) {
              String name = payloadVar.getKey();
              // Check variable name
              if (!variableNameValidator.validate(name)) {
                  activitiExceptions.add(
                      new ActivitiException(MessageFormat.format(errorVariableName, (name != null ? name : "null")))
                  );
              } else if (expressionResolver.containsExpression(payloadVar.getValue())) {
                  activitiExceptions.add(
                      new ActivitiException(
                          MessageFormat.format(errorVariableExpressionValue, (name != null ? name : "null"))
                      )
                  );
              } else {
                  boolean found = validateVariablesAgainstDefinitions(variableDefinitionMap, payloadVar, mismatchedVars);
  
                  if (!found) {
                      //Try to parse a new string variable as date
                      Object value = payloadVar.getValue();
                      if (value != null && (value instanceof String)) {
                          try {
                              payloadVar.setValue(dateFormatterProvider.toDate(value));
                          } catch (Exception e) {
                              //Do nothing here, keep value as a string
                          }
                      }
                  }
              }
          }
  
          if (!mismatchedVars.isEmpty()) {
              activitiExceptions.add(
                  new ActivitiException(MessageFormat.format(errorVariableType, String.join(", ", mismatchedVars)))
              );
          }
  
          if (!activitiExceptions.isEmpty()) {
              throw new IllegalStateException(
                  activitiExceptions
                      .stream()
                      .map(ex -> ex.getMessage())
                      .collect(Collectors.joining(","))
              );
          }
      }
  
      public void checkPayloadVariables(
          SetProcessVariablesPayload setProcessVariablesPayload,
          String processDefinitionId
      ) {
          checkPayloadVariables(setProcessVariablesPayload.getVariables(), processDefinitionId);
      }
  
      public void checkStartProcessPayloadVariables(StartProcessPayload startProcessPayload, String processDefinitionId) {
          checkPayloadVariables(startProcessPayload.getVariables(), processDefinitionId);
      }
  
      public void checkStartMessagePayloadVariables(StartMessagePayload startMessagePayload, String processDefinitionId) {
          checkPayloadVariables(startMessagePayload.getVariables(), processDefinitionId);
      }
  
      public void checkReceiveMessagePayloadVariables(
          ReceiveMessagePayload receiveMessagePayload,
          String processDefinitionId
      ) {
          checkPayloadVariables(receiveMessagePayload.getVariables(), processDefinitionId);
      }
  
      public void checkSignalPayloadVariables(SignalPayload signalPayload, String processDefinitionId) {
          checkPayloadVariables(signalPayload.getVariables(), processDefinitionId);
      }
  }
  