package alien4cloud.tosca.topology; import static alien4cloud.utils.AlienUtils.safe; import java.util.HashMap; import java.util.List; import java.util.Map; import alien4cloud.tosca.properties.constraints.exception.ConstraintFunctionalException; import alien4cloud.utils.services.ConstraintPropertyService; import org.alien4cloud.tosca.model.definitions.*; import org.alien4cloud.tosca.model.types.CapabilityType; import org.alien4cloud.tosca.model.types.NodeType; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang.StringUtils; import com.google.common.collect.Maps; import org.alien4cloud.tosca.model.templates.Capability; import org.alien4cloud.tosca.model.templates.NodeTemplate; import org.alien4cloud.tosca.model.templates.Requirement; import alien4cloud.tosca.context.ToscaContext; import alien4cloud.utils.PropertyUtil; import lombok.extern.slf4j.Slf4j; /** * Utility to create a Node Template by merging Node Type and Node Template data. */ @Slf4j public class NodeTemplateBuilder { /** * Build a node template. Note that a Tosca Context is required. * * @param indexedNodeType the type of the node * @param templateToMerge the template that can be used to merge into the new node template * @return new constructed node template. */ public static NodeTemplate buildNodeTemplate(NodeType indexedNodeType, NodeTemplate templateToMerge) { return buildNodeTemplate(indexedNodeType, templateToMerge, true); } /** * Build a node template. Note that a Tosca Context is required. * * @param indexedNodeType the type of the node * @param templateToMerge the template that can be used to merge into the new node template * @param adaptToType This flag allow to know if we should adapt the templateToMerge node to the type. * @return new constructed node template. */ public static NodeTemplate buildNodeTemplate(NodeType indexedNodeType, NodeTemplate templateToMerge, boolean adaptToType) { NodeTemplate nodeTemplate = new NodeTemplate(); nodeTemplate.setType(indexedNodeType.getElementId()); Map<String, Capability> capabilities = Maps.newLinkedHashMap(); Map<String, Requirement> requirements = Maps.newLinkedHashMap(); Map<String, AbstractPropertyValue> properties = Maps.newLinkedHashMap(); Map<String, DeploymentArtifact> deploymentArtifacts = Maps.newLinkedHashMap(); fillDeploymentArtifactsMap(deploymentArtifacts, indexedNodeType.getArtifacts(), templateToMerge != null ? templateToMerge.getArtifacts() : null); fillCapabilitiesMap(capabilities, indexedNodeType.getCapabilities(), templateToMerge != null ? templateToMerge.getCapabilities() : null, adaptToType); fillRequirementsMap(requirements, indexedNodeType.getRequirements(), templateToMerge != null ? templateToMerge.getRequirements() : null, adaptToType); fillProperties(properties, indexedNodeType.getProperties(), templateToMerge != null ? templateToMerge.getProperties() : null, adaptToType); nodeTemplate.setCapabilities(capabilities); nodeTemplate.setRequirements(requirements); nodeTemplate.setProperties(properties); nodeTemplate.setAttributes(indexedNodeType.getAttributes()); nodeTemplate.setArtifacts(deploymentArtifacts.isEmpty() ? null : deploymentArtifacts); if (templateToMerge != null) { if (templateToMerge.getInterfaces() != null) { nodeTemplate.setInterfaces(templateToMerge.getInterfaces()); } if (templateToMerge.getRelationships() != null) { nodeTemplate.setRelationships(templateToMerge.getRelationships()); } } return nodeTemplate; } private static void fillDeploymentArtifactsMap(Map<String, DeploymentArtifact> deploymentArtifacts, Map<String, DeploymentArtifact> fromTypeArtifacts, Map<String, DeploymentArtifact> mapToMerge) { if (MapUtils.isEmpty(fromTypeArtifacts)) { fromTypeArtifacts = new HashMap<>(); } deploymentArtifacts.putAll(fromTypeArtifacts); if (mapToMerge != null) { for (Map.Entry<String, DeploymentArtifact> entryArtifact : mapToMerge.entrySet()) { deploymentArtifacts.put(entryArtifact.getKey(), entryArtifact.getValue()); DeploymentArtifact artifactFromType = deploymentArtifacts.get(entryArtifact.getKey()); if (artifactFromType != null && StringUtils.isBlank(entryArtifact.getValue().getArtifactType())) { entryArtifact.getValue().setArtifactType(artifactFromType.getArtifactType()); } } } } private static void fillCapabilitiesMap(Map<String, Capability> map, List<CapabilityDefinition> elements, Map<String, Capability> mapToMerge, boolean adaptToType) { if (elements == null) { return; } for (CapabilityDefinition capa : elements) { Capability toAddCapa = MapUtils.getObject(mapToMerge, capa.getId()); Map<String, AbstractPropertyValue> capaProperties = null; CapabilityType capabilityType = ToscaContext.get(CapabilityType.class, capa.getType()); if (capabilityType != null && capabilityType.getProperties() != null) { capaProperties = PropertyUtil.getDefaultPropertyValuesFromPropertyDefinitions(capabilityType.getProperties()); } if (toAddCapa == null) { toAddCapa = new Capability(); toAddCapa.setType(capa.getType()); toAddCapa.setProperties(capaProperties); } else { if (StringUtils.isBlank(toAddCapa.getType())) { toAddCapa.setType(capa.getType()); } if (MapUtils.isNotEmpty(capaProperties)) { Map<String, AbstractPropertyValue> nodeCapaProperties = safe(toAddCapa.getProperties()); capaProperties.putAll(nodeCapaProperties); toAddCapa.setProperties(capaProperties); } } Map<String, AbstractPropertyValue> properties = Maps.newLinkedHashMap(); fillProperties(properties, capabilityType != null ? capabilityType.getProperties() : null, toAddCapa.getProperties(), adaptToType); toAddCapa.setProperties(properties); map.put(capa.getId(), toAddCapa); } } private static void fillRequirementsMap(Map<String, Requirement> map, List<RequirementDefinition> elements, Map<String, Requirement> mapToMerge, boolean adaptToType) { if (elements == null) { return; } for (RequirementDefinition requirement : elements) { Requirement toAddRequirement = MapUtils.getObject(mapToMerge, requirement.getId()); // the type of a requirement is a capability type in TOSCA as they match each other. CapabilityType requirementType = ToscaContext.get(CapabilityType.class, requirement.getType()); if (toAddRequirement == null) { toAddRequirement = new Requirement(); toAddRequirement.setType(requirement.getType()); } Map<String, AbstractPropertyValue> properties = Maps.newLinkedHashMap(); fillProperties(properties, requirementType != null ? requirementType.getProperties() : null, toAddRequirement.getProperties(), adaptToType); toAddRequirement.setProperties(properties); map.put(requirement.getId(), toAddRequirement); } } public static void fillProperties(Map<String, AbstractPropertyValue> properties, Map<String, PropertyDefinition> propertiesDefinitions, Map<String, AbstractPropertyValue> originalProperties) { fillProperties(properties, propertiesDefinitions, originalProperties, true); } public static void fillProperties(Map<String, AbstractPropertyValue> properties, Map<String, PropertyDefinition> propertiesDefinitions, Map<String, AbstractPropertyValue> originalProperties, boolean adaptToType) { if (propertiesDefinitions == null || properties == null) { return; } for (Map.Entry<String, PropertyDefinition> entry : propertiesDefinitions.entrySet()) { AbstractPropertyValue originalValue = MapUtils.getObject(originalProperties, entry.getKey()); if (originalValue == null) { AbstractPropertyValue pv = PropertyUtil.getDefaultPropertyValueFromPropertyDefinition(entry.getValue()); properties.put(entry.getKey(), pv); } else if (originalValue instanceof FunctionPropertyValue) { properties.put(entry.getKey(), originalValue); } else { // we check the property type before accepting it try { ConstraintPropertyService.checkPropertyConstraint(entry.getKey(), originalValue, entry.getValue()); properties.put(entry.getKey(), originalValue); } catch(ConstraintFunctionalException e) { log.debug("Not able to merge property <" + entry.getKey() + "> value due to a type check exception", e); } } } if (!adaptToType) { // we have to add the properties defined on the given originalProperties even if not defined on the type (could be for later validations). // maybe we could put validations here actually. for (Map.Entry<String, AbstractPropertyValue> originalProperty : safe(originalProperties).entrySet()) { if (!properties.containsKey(originalProperty.getKey())) { properties.put(originalProperty.getKey(), originalProperty.getValue()); } } } } }