content: package alien4cloud.topology.validation;
  
  import java.util.List;
  import java.util.Map;
  
  import javax.annotation.Resource;
  import javax.inject.Inject;
  
  import org.alien4cloud.tosca.catalog.index.IToscaTypeSearchService;
  import org.alien4cloud.tosca.exceptions.ConstraintValueDoNotMatchPropertyTypeException;
  import org.alien4cloud.tosca.exceptions.ConstraintViolationException;
  import org.alien4cloud.tosca.model.definitions.AbstractPropertyValue;
  import org.alien4cloud.tosca.model.definitions.CapabilityDefinition;
  import org.alien4cloud.tosca.model.definitions.FilterDefinition;
  import org.alien4cloud.tosca.model.definitions.FunctionPropertyValue;
  import org.alien4cloud.tosca.model.definitions.NodeFilter;
  import org.alien4cloud.tosca.model.definitions.PropertyConstraint;
  import org.alien4cloud.tosca.model.definitions.PropertyDefinition;
  import org.alien4cloud.tosca.model.definitions.RequirementDefinition;
  import org.alien4cloud.tosca.model.definitions.ScalarPropertyValue;
  import org.alien4cloud.tosca.model.templates.NodeTemplate;
  import org.alien4cloud.tosca.model.templates.RelationshipTemplate;
  import org.alien4cloud.tosca.model.templates.Topology;
  import org.alien4cloud.tosca.model.types.CapabilityType;
  import org.alien4cloud.tosca.model.types.NodeType;
  import org.alien4cloud.tosca.normative.types.IPropertyType;
  import org.alien4cloud.tosca.normative.types.ToscaTypes;
  import org.springframework.stereotype.Component;
  
  import com.google.common.collect.Lists;
  import com.google.common.collect.Maps;
  
  import alien4cloud.paas.function.FunctionEvaluator;
  import alien4cloud.rest.model.RestErrorCode;
  import alien4cloud.topology.TopologyServiceCore;
  import alien4cloud.topology.task.NodeFilterConstraintViolation;
  import alien4cloud.topology.task.NodeFilterToSatisfy;
  import alien4cloud.topology.task.NodeFilterToSatisfy.Violations;
  import alien4cloud.topology.task.NodeFiltersTask;
  import alien4cloud.topology.task.TaskCode;
  
  import static alien4cloud.utils.AlienUtils.safe;
  
  /**
   * Performs validation of node filters for all relationship of topology.
   */
  @Component
  public class NodeFilterValidationService {
      @Inject
      private IToscaTypeSearchService toscaTypeSearchService;
      @Resource
      private TopologyServiceCore topologyServiceCore;
  
      private Map<String, RequirementDefinition> getRequirementsAsMap(NodeType nodeType) {
          Map<String, RequirementDefinition> requirementDefinitionMap = Maps.newHashMap();
          for (RequirementDefinition definition : nodeType.getRequirements()) {
              requirementDefinitionMap.put(definition.getId(), definition);
          }
          return requirementDefinitionMap;
      }
  
      public List<NodeFiltersTask> validateStaticRequirementFilters(Topology topology) {
          return validateRequirementFilters(topology, true);
      }
  
      public List<NodeFiltersTask> validateAllRequirementFilters(Topology topology) {
          return validateRequirementFilters(topology, false);
      }
  
      /**
       * Performs validation of the node filters to check that relationships targets the filter requirements.
       */
      private List<NodeFiltersTask> validateRequirementFilters(Topology topology, boolean skipInputs) {
          List<NodeFiltersTask> toReturnTaskList = Lists.newArrayList();
          Map<String, NodeTemplate> nodeTemplates = topology.getNodeTemplates();
          Map<String, NodeType> nodeTypes = topologyServiceCore.getIndexedNodeTypesFromTopology(topology, false, true, true);
          Map<String, CapabilityType> capabilityTypes = topologyServiceCore.getIndexedCapabilityTypesFromTopology(topology);
          for (Map.Entry<String, NodeTemplate> nodeTempEntry : nodeTemplates.entrySet()) {
              Map<String, RelationshipTemplate> relationshipsMap = nodeTempEntry.getValue().getRelationships();
              if (relationshipsMap == null || relationshipsMap.isEmpty()) {
                  continue;
              }
              NodeType sourceNodeType = toscaTypeSearchService.getRequiredElementInDependencies(NodeType.class, nodeTempEntry.getValue().getType(),
                      topology.getDependencies());
              if (sourceNodeType.isAbstract()) {
                  continue;
              }
  
              NodeFiltersTask task = new NodeFiltersTask();
              task.setNodeTemplateName(nodeTempEntry.getKey());
              task.setCode(TaskCode.NODE_FILTER_INVALID);
              task.setComponent(sourceNodeType);
              task.setNodeFiltersToSatisfy(Lists.<NodeFilterToSatisfy> newArrayList());
  
              validateFiltersForNode(sourceNodeType, relationshipsMap, topology, nodeTypes, capabilityTypes, task, skipInputs);
  
              if (!task.getNodeFiltersToSatisfy().isEmpty()) {
                  toReturnTaskList.add(task);
              }
          }
          return toReturnTaskList.isEmpty() ? null : toReturnTaskList;
      }
  
      private void validateFiltersForNode(NodeType sourceNodeType, Map<String, RelationshipTemplate> relationshipsMap, Topology topology,
              Map<String, NodeType> nodeTypes, Map<String, CapabilityType> capabilityTypes, NodeFiltersTask task, boolean skipInputs) {
          Map<String, RequirementDefinition> requirementDefinitionMap = getRequirementsAsMap(sourceNodeType);
          for (Map.Entry<String, RelationshipTemplate> relationshipEntry : relationshipsMap.entrySet()) {
              RequirementDefinition requirementDefinition = requirementDefinitionMap.get(relationshipEntry.getValue().getRequirementName());
              NodeFilter nodeFilter = requirementDefinition.getNodeFilter();
              if (nodeFilter != null) {
                  NodeTemplate targetNode = topology.getNodeTemplates().get(relationshipEntry.getValue().getTarget());
                  NodeType targetType = nodeTypes.get(relationshipEntry.getValue().getTarget());
  
                  NodeFilterToSatisfy nodeFilterToSatisfy = new NodeFilterToSatisfy();
                  nodeFilterToSatisfy.setRelationshipName(relationshipEntry.getKey());
                  nodeFilterToSatisfy.setTargetName(targetNode.getName());
                  nodeFilterToSatisfy.setMissingCapabilities(Lists.<String> newArrayList());
                  nodeFilterToSatisfy.setViolations(Lists.<Violations> newArrayList());
  
                  validateNodeFilter(nodeFilter, targetNode, targetType, capabilityTypes, nodeFilterToSatisfy, skipInputs);
  
                  if (!nodeFilterToSatisfy.getViolations().isEmpty() || !nodeFilterToSatisfy.getMissingCapabilities().isEmpty()) {
                      task.getNodeFiltersToSatisfy().add(nodeFilterToSatisfy);
                  }
              }
          }
      }
  
      private void validateNodeFilter(NodeFilter nodeFilter, NodeTemplate target, NodeType targetType, Map<String, CapabilityType> capabilityTypes,
              NodeFilterToSatisfy nodeFilterToSatisfy, boolean skipInputs) {
          List<Violations> violations = validateNodeFilterProperties(nodeFilter, target, targetType, skipInputs);
          nodeFilterToSatisfy.setViolations(violations);
  
          validateNodeFilterCapabilities(nodeFilter, target, targetType, capabilityTypes, nodeFilterToSatisfy, skipInputs);
      }
  
      private List<Violations> validateNodeFilterProperties(NodeFilter nodeFilter, NodeTemplate target, NodeType targetType, boolean skipInputs) {
          if (nodeFilter.getProperties() == null || nodeFilter.getProperties().isEmpty()) {
              return Lists.newArrayList();
          }
  
          Map<String, List<PropertyConstraint>> propertyFilters = nodeFilter.getProperties();
          Map<String, AbstractPropertyValue> propertyValues = target.getProperties();
          return validatePropertyFilters(propertyFilters, propertyValues, targetType.getProperties(), skipInputs);
      }
  
      private List<Violations> validatePropertyFilters(Map<String, List<PropertyConstraint>> propertyFilters, Map<String, AbstractPropertyValue> propertyValues,
              Map<String, PropertyDefinition> propertyDefinitionMap, boolean skipInputs) {
          List<Violations> violations = Lists.newArrayList();
  
          for (Map.Entry<String, List<PropertyConstraint>> propertyEntry : propertyFilters.entrySet()) {
              Violations violation = new Violations(propertyEntry.getKey());
              List<NodeFilterConstraintViolation> violatedConstraints = Lists.newArrayList();
              violation.violatedConstraints = violatedConstraints;
              AbstractPropertyValue value = propertyValues.get(propertyEntry.getKey());
              String propertyValue = null;
              if (value == null) {
                  propertyValue = null;
              } else if (value instanceof ScalarPropertyValue) {
                  propertyValue = ((ScalarPropertyValue) value).getValue();
              } else {
                  if (skipInputs) {
                      continue;
                  }
                  if (FunctionEvaluator.isGetInput((FunctionPropertyValue) value)) {
                      violation.relatedInput = ((FunctionPropertyValue) value).getElementNameToFetch();
                  }
              }
  
              for (PropertyConstraint constraint : propertyEntry.getValue()) {
                  if (!propertyDefinitionMap.containsKey(propertyEntry.getKey())) {
                      continue;
                  }
  
                  // the constraint need to be initiazed with the type of the property (to check that actual value type matches the definition type).
                  IPropertyType<?> toscaType = ToscaTypes.fromYamlTypeName(propertyDefinitionMap.get(propertyEntry.getKey()).getType());
                  try {
                      constraint.initialize(toscaType);
                      constraint.validate(toscaType, propertyValue);
                  } catch (ConstraintViolationException e) {
                      violatedConstraints.add(
                              new NodeFilterConstraintViolation(RestErrorCode.PROPERTY_CONSTRAINT_VIOLATION_ERROR, e.getMessage(), e.getConstraintInformation()));
                  } catch (ConstraintValueDoNotMatchPropertyTypeException e) {
                      violatedConstraints.add(new NodeFilterConstraintViolation(RestErrorCode.PROPERTY_TYPE_VIOLATION_ERROR, e.getMessage(), null));
                  }
              }
  
              if (!violatedConstraints.isEmpty()) {
                  violations.add(violation);
              }
          }
  
          return violations;
      }
  
      private void validateNodeFilterCapabilities(NodeFilter nodeFilter, NodeTemplate target, NodeType targetType, Map<String, CapabilityType> capabilityTypes,
              NodeFilterToSatisfy nodeFilterToSatisfy, boolean skipInputs) {
          if (nodeFilter.getCapabilities() == null || nodeFilter.getCapabilities().isEmpty()) {
              return;
          }
  
          Map<String, FilterDefinition> capabilities = nodeFilter.getCapabilities();
          for (Map.Entry<String, FilterDefinition> filterDefinitionEntry : capabilities.entrySet()) {
              String capabilityName = filterDefinitionEntry.getKey();
              CapabilityDefinition definition = getCapabilityDefinition(targetType, capabilityName);
  
              if (definition == null) {
                  nodeFilterToSatisfy.getMissingCapabilities().add(capabilityName);
                  continue;
              }
              CapabilityType capabilityType = capabilityTypes.get(definition.getType());
  
              List<Violations> violations = validatePropertyFilters(filterDefinitionEntry.getValue().getProperties(),
                      target.getCapabilities().get(definition.getId()).getProperties(), capabilityType.getProperties(), skipInputs);
              violations.forEach(violation -> violation.capabilityName = capabilityName);
              if (nodeFilterToSatisfy.getViolations() == null) {
                  nodeFilterToSatisfy.setViolations(violations);
              } else {
                  nodeFilterToSatisfy.getViolations().addAll(violations);
              }
          }
      }
  
      private CapabilityDefinition getCapabilityDefinition(NodeType targetType, String filterCapabilityKey) {
          for (CapabilityDefinition capabilityDefinition : safe(targetType.getCapabilities())) {
              if (filterCapabilityKey.equals(capabilityDefinition.getId()) || filterCapabilityKey.equals(capabilityDefinition.getType())) {
                  return capabilityDefinition;
              }
          }
          return null;
      }
  }
  