content: package alien4cloud.application;
  
  import static alien4cloud.paas.function.FunctionEvaluator.isGetInput;
  import static org.alien4cloud.tosca.normative.constants.NormativeWorkflowNameConstants.INSTALL;
  import static org.alien4cloud.tosca.normative.constants.NormativeWorkflowNameConstants.START;
  import static org.alien4cloud.tosca.normative.constants.NormativeWorkflowNameConstants.STOP;
  import static org.alien4cloud.tosca.normative.constants.NormativeWorkflowNameConstants.UNINSTALL;
  
  import java.util.ArrayDeque;
  import java.util.Deque;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.Set;
  
  import javax.annotation.Resource;
  
  import org.alien4cloud.tosca.model.definitions.AbstractPropertyValue;
  import org.alien4cloud.tosca.model.definitions.FunctionPropertyValue;
  import org.alien4cloud.tosca.model.templates.Capability;
  import org.alien4cloud.tosca.model.templates.NodeGroup;
  import org.alien4cloud.tosca.model.templates.NodeTemplate;
  import org.alien4cloud.tosca.model.templates.RelationshipTemplate;
  import org.alien4cloud.tosca.model.templates.SubstitutionTarget;
  import org.alien4cloud.tosca.model.templates.Topology;
  import org.alien4cloud.tosca.model.types.NodeType;
  import org.springframework.stereotype.Service;
  
  import com.google.common.collect.Maps;
  import com.google.common.collect.Sets;
  
  import alien4cloud.component.ICSARRepositorySearchService;
  import alien4cloud.exception.AlreadyExistException;
  import alien4cloud.exception.CyclicReferenceException;
  import alien4cloud.paas.wf.TopologyContext;
  import alien4cloud.paas.wf.WorkflowsBuilderService;
  import alien4cloud.topology.TopologyServiceCore;
  import alien4cloud.utils.MapUtil;
  import lombok.extern.slf4j.Slf4j;
  
  @Slf4j
  @Service
  public class TopologyCompositionService {
  
      @Resource
      private ICSARRepositorySearchService csarRepoSearchService;
  
      @Resource
      private TopologyServiceCore topologyServiceCore;
  
      @Resource
      private WorkflowsBuilderService workflowBuilderService;
  
      public void processTopologyComposition(Topology topology) {
          Deque<CompositionCouple> stack = new ArrayDeque<CompositionCouple>();
          recursivelyBuildSubstitutionStack(topology, stack, "");
          // now this stack contains all the embedded topology templates
          if (!stack.isEmpty()) {
              // iterate over the stack in descending order (manage the deepest topologies at a first time).
              Iterator<CompositionCouple> compositionIterator = stack.descendingIterator();
              while (compositionIterator.hasNext()) {
                  processComposition(compositionIterator.next());
              }
              if (log.isDebugEnabled()) {
                  log.debug(String.format("Topology composition has been processed for topology <%s> substituting %d embeded topologies", topology.getId(),
                          stack.size()));
              }
  
              // std workflows are reinitialized when some composition is processed
              // TODO: find a better way to manage this
              TopologyContext topologyContext = workflowBuilderService.buildTopologyContext(topology);
              workflowBuilderService.reinitWorkflow(INSTALL, topologyContext, false);
              workflowBuilderService.reinitWorkflow(START, topologyContext, false);
              workflowBuilderService.reinitWorkflow(STOP, topologyContext, false);
              workflowBuilderService.reinitWorkflow(UNINSTALL, topologyContext, false);
              workflowBuilderService.postProcessTopologyWorkflows(topologyContext);
          }
      }
  
      /**
       * Process the composition:
       * <ul>
       * <li>remove the 'proxy' node from the parent.
       * <li>merge the child topology nodes into the parent nodes.
       * <li>
       * </ul>
       * 
       * @param compositionCouple
       */
      private void processComposition(CompositionCouple compositionCouple) {
          // first of all, remove the proxy node from the parent
          NodeTemplate proxyNodeTemplate = compositionCouple.parent.getNodeTemplates().remove(compositionCouple.nodeName);
          // properties of the proxy are used to feed the property values of child node that use get_input
          for (NodeTemplate childNodeTemplate : compositionCouple.child.getNodeTemplates().values()) {
              for (Entry<String, AbstractPropertyValue> propertyEntry : childNodeTemplate.getProperties().entrySet()) {
                  AbstractPropertyValue pValue = propertyEntry.getValue();
                  if (isGetInput(pValue)) {
                      String inputName = ((FunctionPropertyValue) pValue).getTemplateName();
                      propertyEntry.setValue(proxyNodeTemplate.getProperties().get(inputName));
                  }
              }
              for (Entry<String, Capability> capabilityEntry : childNodeTemplate.getCapabilities().entrySet()) {
                  if (capabilityEntry.getValue().getProperties() != null) {
                      for (Entry<String, AbstractPropertyValue> propertyEntry : capabilityEntry.getValue().getProperties().entrySet()) {
                          AbstractPropertyValue pValue = propertyEntry.getValue();
                          if (isGetInput(pValue)) {
                              String inputName = ((FunctionPropertyValue) pValue).getTemplateName();
                              propertyEntry.setValue(proxyNodeTemplate.getProperties().get(inputName));
                          }
                      }
                  }
              }
          }
          // all relations from the proxy must now start from the corresponding node
          if (proxyNodeTemplate.getRelationships() != null) {
              for (Entry<String, RelationshipTemplate> e : proxyNodeTemplate.getRelationships().entrySet()) {
                  String relationShipKey = e.getKey();
                  RelationshipTemplate proxyRelationShip = e.getValue();
                  String requirementName = proxyRelationShip.getRequirementName();
                  SubstitutionTarget substitutionTarget = compositionCouple.child.getSubstitutionMapping().getRequirements().get(requirementName);
                  NodeTemplate nodeTemplate = compositionCouple.child.getNodeTemplates().get(substitutionTarget.getNodeTemplateName());
                  if (nodeTemplate.getRelationships() == null) {
                      Map<String, RelationshipTemplate> relationships = Maps.newHashMap();
                      nodeTemplate.setRelationships(relationships);
                  }
                  nodeTemplate.getRelationships().put(relationShipKey, proxyRelationShip);
                  proxyRelationShip.setRequirementName(substitutionTarget.getTargetId());
              }
          }
          // all relations that target the proxy must be redirected to the corresponding child node
          for (NodeTemplate otherNodes : compositionCouple.parent.getNodeTemplates().values()) {
              if (otherNodes.getRelationships() != null) {
                  for (RelationshipTemplate relationshipTemplate : otherNodes.getRelationships().values()) {
                      if (relationshipTemplate.getTarget().equals(compositionCouple.nodeName)) {
                          SubstitutionTarget st = compositionCouple.child.getSubstitutionMapping().getCapabilities()
                                  .get(relationshipTemplate.getTargetedCapabilityName());
                          relationshipTemplate.setTarget(st.getNodeTemplateName());
                          relationshipTemplate.setTargetedCapabilityName(st.getTargetId());
                      }
                  }
              }
          }
          if (compositionCouple.parent.getOutputAttributes() != null) {
              Set<String> outputAttributes = compositionCouple.parent.getOutputAttributes().remove(compositionCouple.nodeName);
              if (outputAttributes != null) {
                  for (String proxyAttributeName : outputAttributes) {
                      sustituteGetAttribute(compositionCouple.child, compositionCouple.parent, proxyAttributeName);
                  }
              }
          }
          // the parent itself expose stuffs, we eventually need to replace substitution targets
          if (compositionCouple.parent.getSubstitutionMapping() != null) {
              if (compositionCouple.parent.getSubstitutionMapping().getCapabilities() != null) {
                  for (Entry<String, SubstitutionTarget> substitutionCapabilityEntry : compositionCouple.parent.getSubstitutionMapping().getCapabilities()
                          .entrySet()) {
                      if (substitutionCapabilityEntry.getValue().getNodeTemplateName().equals(compositionCouple.nodeName)) {
                          String targetCapability = substitutionCapabilityEntry.getValue().getTargetId();
                          // just substitute the substitution target
                          substitutionCapabilityEntry.setValue(compositionCouple.child.getSubstitutionMapping().getCapabilities().get(targetCapability));
                      }
                  }
              }
              if (compositionCouple.parent.getSubstitutionMapping().getRequirements() != null) {
                  for (Entry<String, SubstitutionTarget> e : compositionCouple.parent.getSubstitutionMapping().getRequirements().entrySet()) {
                      if (e.getValue().getNodeTemplateName().equals(compositionCouple.nodeName)) {
                          String targetCapability = e.getValue().getTargetId();
                          // just substitute the substitution target
                          e.setValue(compositionCouple.child.getSubstitutionMapping().getRequirements().get(targetCapability));
                      }
                  }
              }
          }
          // merge each child nodes into the parent
          compositionCouple.parent.getNodeTemplates().putAll(compositionCouple.child.getNodeTemplates());
      }
  
      /**
       * Ugly code : since we don't name outputs in alien topology, we are not able to determine if an output is related to a property, to an attribute or to a
       * capability property. This is done in the same order than alien4cloud.topology.TopologyServiceCore.updateSubstitutionType(Topology) processes substitution
       * outputs.
       */
      private void sustituteGetAttribute(Topology child, Topology parent, String proxyAttributeName) {
          if (child.getOutputAttributes() != null) {
              for (Entry<String, Set<String>> oae : child.getOutputAttributes().entrySet()) {
                  String nodeName = oae.getKey();
                  for (String oa : oae.getValue()) {
                      if (oa.equals(proxyAttributeName)) {
                          // ok the proxy attribute name matches the embedded node attribute
                          Map<String, Set<String>> parentOas = parent.getOutputAttributes();
                          if (parentOas == null) {
                              parentOas = Maps.newHashMap();
                              parent.setOutputAttributes(parentOas);
                          }
                          Set<String> parentNodeOas = parentOas.get(nodeName);
                          if (parentNodeOas == null) {
                              parentNodeOas = Sets.newHashSet();
                              parentOas.put(nodeName, parentNodeOas);
                          }
                          parentNodeOas.add(proxyAttributeName);
                          return;
                      }
                  }
              }
          }
          if (child.getOutputProperties() != null) {
              for (Entry<String, Set<String>> ope : child.getOutputProperties().entrySet()) {
                  String nodeName = ope.getKey();
                  for (String op : ope.getValue()) {
                      if (op.equals(proxyAttributeName)) {
                          // ok the proxy attribute name matches the embedded node property
                          Map<String, Set<String>> parentOps = parent.getOutputProperties();
                          if (parentOps == null) {
                              parentOps = Maps.newHashMap();
                              parent.setOutputProperties(parentOps);
                          }
                          Set<String> parentNodeOps = parentOps.get(nodeName);
                          if (parentNodeOps == null) {
                              parentNodeOps = Sets.newHashSet();
                              parentOps.put(nodeName, parentNodeOps);
                          }
                          parentNodeOps.add(proxyAttributeName);
                          return;
                      }
                  }
              }
          }
          if (child.getOutputCapabilityProperties() != null) {
              for (Entry<String, Map<String, Set<String>>> ocpe : child.getOutputCapabilityProperties().entrySet()) {
                  String nodeName = ocpe.getKey();
                  for (Entry<String, Set<String>> cpes : ocpe.getValue().entrySet()) {
                      String embededCapabilityName = cpes.getKey();
                      for (String op : cpes.getValue()) {
                          if (op.equals(proxyAttributeName)) {
                              // ok the embedded output capability property matches the proxy type output attribute
                              Map<String, Map<String, Set<String>>> parentOcps = parent.getOutputCapabilityProperties();
                              if (parentOcps == null) {
                                  parentOcps = Maps.newHashMap();
                                  parent.setOutputCapabilityProperties(parentOcps);
                              }
                              Map<String, Set<String>> parentNodeOcps = parentOcps.get(nodeName);
                              if (parentNodeOcps == null) {
                                  parentNodeOcps = Maps.newHashMap();
                                  parentOcps.put(nodeName, parentNodeOcps);
                              }
                              Set<String> parentCapabilityOps = parentNodeOcps.get(embededCapabilityName);
                              if (parentCapabilityOps == null) {
                                  parentCapabilityOps = Sets.newHashSet();
                                  parentNodeOcps.put(embededCapabilityName, parentCapabilityOps);
                              }
                              parentCapabilityOps.add(proxyAttributeName);
                              return;
                          }
                      }
                  }
              }
          }
      }
  
      /**
       * Deeply explore this topology to detect if some type must be substituted by the corresponding topology template content and feed the {@link Deque}. <br>
       * BTW, rename the nodes by prefixing all the node names.
       */
      private void recursivelyBuildSubstitutionStack(Topology topology, Deque<CompositionCouple> stack, String prefix) {
          if (topology == null || topology.getNodeTemplates() == null || topology.getNodeTemplates().isEmpty()) {
              return;
          }
          for (Entry<String, NodeTemplate> nodeEntry : topology.getNodeTemplates().entrySet()) {
              String nodeName = nodeEntry.getKey();
              String type = nodeEntry.getValue().getType();
              // FIXME use tosca context, beware of child topologies (dependencies to use ? conflicts ?)
              NodeType nodeType = csarRepoSearchService.getRequiredElementInDependencies(NodeType.class, type, topology.getDependencies());
              if (nodeType.getSubstitutionTopologyId() != null) {
                  // this node type is a proxy for a topology template
                  Topology child = topologyServiceCore.getOrFail(nodeType.getSubstitutionTopologyId());
                  CompositionCouple couple = new CompositionCouple(topology, child, nodeName, nodeName + "_");
                  renameNodes(couple);
                  stack.offer(couple);
                  recursivelyBuildSubstitutionStack(child, stack, nodeName + "_");
              }
          }
      }
  
      private void renameNodes(CompositionCouple compositionCouple) {
          Topology topology = compositionCouple.child;
          String[] nodeNames = new String[topology.getNodeTemplates().size()];
          nodeNames = topology.getNodeTemplates().keySet().toArray(nodeNames);
          for (String nodeName : nodeNames) {
              String newName = ensureNodeNameIsUnique(topology.getNodeTemplates().keySet(), compositionCouple.nodeNamePrefix + nodeName, 0);
              renameNodeTemplate(topology, nodeName, newName);
          }
      }
  
      // TODO ALIEN-2589: move elsewhere
      @Deprecated
      public static String ensureNodeNameIsUnique(Set<String> keys, String prefix, int suffixeNumber) {
          String name = (suffixeNumber > 0) ? prefix + suffixeNumber : prefix;
          if (keys.contains(name)) {
              return ensureNodeNameIsUnique(keys, prefix, ++suffixeNumber);
          } else {
              return name;
          }
      }
  
      private void renameNodeTemplate(Topology topology, String oldName, String newName) {
          // if the prefixed name is already used by another node ?
          // quite improbable but ...
          if (topology.getNodeTemplates().containsKey(newName)) {
              throw new AlreadyExistException(String.format("A node with name '%s' already exists in this topology", newName));
          }
          NodeTemplate nodeTemplate = topology.getNodeTemplates().remove(oldName);
          // manage relationships that target this node
          for (NodeTemplate otherNodes : topology.getNodeTemplates().values()) {
              if (otherNodes.getRelationships() == null || otherNodes.getRelationships().isEmpty()) {
                  continue;
              }
              for (RelationshipTemplate relationshipTemplate : otherNodes.getRelationships().values()) {
                  if (relationshipTemplate.getTarget().equals(oldName)) {
                      relationshipTemplate.setTarget(newName);
                  }
              }
          }
          // all output stuffs
          MapUtil.replaceKey(topology.getOutputProperties(), oldName, newName);
          MapUtil.replaceKey(topology.getOutputCapabilityProperties(), oldName, newName);
          MapUtil.replaceKey(topology.getOutputAttributes(), oldName, newName);
          // group members must be updated
          if (topology.getGroups() != null) {
              for (NodeGroup nodeGroup : topology.getGroups().values()) {
                  Set<String> members = nodeGroup.getMembers();
                  if (members != null && members.remove(oldName)) {
                      members.add(newName);
                  }
              }
          }
          // substitutions
          if (topology.getSubstitutionMapping() != null) {
              renameNodeTemplateInSubstitutionTargets(topology.getSubstitutionMapping().getCapabilities(), oldName, newName);
              renameNodeTemplateInSubstitutionTargets(topology.getSubstitutionMapping().getRequirements(), oldName, newName);
          }
          // finally the node itself
          topology.getNodeTemplates().put(newName, nodeTemplate);
      }
  
      private void renameNodeTemplateInSubstitutionTargets(Map<String, SubstitutionTarget> substitutionTargets, String oldName, String newName) {
          if (substitutionTargets != null) {
              for (SubstitutionTarget s : substitutionTargets.values()) {
                  if (s.getNodeTemplateName().equals(oldName)) {
                      s.setNodeTemplateName(newName);
                  }
              }
          }
      }
  
      /**
       * Deeply explore composition in order to detect cyclic reference: if a descendant references the mainTopologyId.
       */
      public void recursivelyDetectTopologyCompositionCyclicReference(String mainTopologyId, String substitutionTopologyId) {
          Topology child = topologyServiceCore.getOrFail(substitutionTopologyId);
          if (child == null || child.getNodeTemplates() == null || child.getNodeTemplates().isEmpty()) {
              return;
          }
          for (Entry<String, NodeTemplate> nodeEntry : child.getNodeTemplates().entrySet()) {
              String type = nodeEntry.getValue().getType();
              NodeType nodeType = csarRepoSearchService.getElementInDependencies(NodeType.class, type, child.getDependencies());
              if (nodeType.getSubstitutionTopologyId() != null) {
                  if (nodeType.getSubstitutionTopologyId().equals(mainTopologyId)) {
                      throw new CyclicReferenceException("Cyclic reference : a topology template can not reference itself (even indirectly)");
                  }
                  recursivelyDetectTopologyCompositionCyclicReference(mainTopologyId, nodeType.getSubstitutionTopologyId());
              }
          }
      }
  
      private static class CompositionCouple {
          /** The topology that embeds another one. */
          private final Topology parent;
  
          /** The topology template that will substitute the type. */
          private final Topology child;
  
          /** The node name referencing the child in the parent. */
          private final String nodeName;
  
          /** The prefix that will be used to rename nodes. */
          private final String nodeNamePrefix;
  
          public CompositionCouple(Topology parent, Topology child, String nodeName, String nodeNamePrefix) {
              super();
              this.parent = parent;
              this.child = child;
              this.nodeName = nodeName;
              this.nodeNamePrefix = nodeNamePrefix;
          }
      }
  
  }
  