/************************************************************************* * Copyright 2013-2014 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. ************************************************************************/ package com.eucalyptus.cloudformation.template.dependencies; import com.eucalyptus.cloudformation.CloudFormationException; import com.eucalyptus.cloudformation.ValidationErrorException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.collect.Table; import com.google.common.collect.TreeBasedTable; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; public class DependencyManager { private Set<String> nodes = Sets.newLinkedHashSet(); private Table<String, String, Integer> edgeTable = TreeBasedTable.create(); public Set<String> getNodes() { return nodes; } public Collection<String> getDependentNodes(String independentNode) { return edgeTable.row(independentNode).keySet(); } public Collection<String> getReverseDependentNodes(String independentNode) { return edgeTable.column(independentNode).keySet(); } public synchronized void addNode(String node) { nodes.add(node); } public synchronized boolean containsNode(String node) { return nodes.contains(node); } public synchronized void addDependency(String dependentNode, String independentNode) throws NoSuchElementException { if (!nodes.contains(dependentNode)) throw new NoSuchElementException(dependentNode); if (!nodes.contains(independentNode)) throw new NoSuchElementException(independentNode); edgeTable.put(independentNode, dependentNode, 1); // An edge from A to B means B depends on A. (i.e. can start with A) } public synchronized List<String> dependencyList() throws CyclicDependencyException { LinkedList<String> sortedNodes = Lists.newLinkedList(); Set<String> unmarkedNodes = Sets.newTreeSet(nodes); Set<String> temporarilyMarkedNodes = Sets.newLinkedHashSet(); // this also represents the current path... Set<String> permanentlyMarkedNodes = Sets.newHashSet(); while (!unmarkedNodes.isEmpty()) { String currentNode = unmarkedNodes.iterator().next(); visitNode(currentNode, unmarkedNodes, temporarilyMarkedNodes, permanentlyMarkedNodes, sortedNodes); } return sortedNodes; } private void visitNode(String currentNode, Set<String> unmarkedNodes, Set<String> temporarilyMarkedNodes, Set<String> permanentlyMarkedNodes, LinkedList<String> sortedNodes) throws CyclicDependencyException { if (temporarilyMarkedNodes.contains(currentNode)) { throw new CyclicDependencyException(subListFrom(Lists.newArrayList(temporarilyMarkedNodes), currentNode).toString()); } if (unmarkedNodes.contains(currentNode)) { unmarkedNodes.remove(currentNode); temporarilyMarkedNodes.add(currentNode); for (String adjacentNode: edgeTable.row(currentNode).keySet()) { visitNode(adjacentNode, unmarkedNodes, temporarilyMarkedNodes, permanentlyMarkedNodes, sortedNodes); } temporarilyMarkedNodes.remove(currentNode); permanentlyMarkedNodes.add(currentNode); sortedNodes.addFirst(currentNode); } } public String toJson() throws CloudFormationException { try { ObjectMapper mapper = new ObjectMapper(); String nodesStr = mapper.writeValueAsString(nodes); Map<String,List<String>> dependencies = convertToMap(edgeTable); String dependenciesStr = mapper.writeValueAsString(dependencies); ObjectNode objectNode = mapper.createObjectNode(); objectNode.put("nodes", nodesStr); objectNode.put("dependencies", dependenciesStr); return objectNode.toString(); } catch (JsonProcessingException e) { throw new ValidationErrorException(e.getMessage()); } } private Map<String,List<String>> convertToMap(Table<String, String, Integer> table) { Map<String, List<String>> map = Maps.newLinkedHashMap(); for (String row:table.rowKeySet()) { map.put(row, Lists.newArrayList(table.row(row).keySet())); } return map; } public static DependencyManager fromJson(String json) throws CloudFormationException { if (json == null) return new DependencyManager(); try { ObjectMapper mapper = new ObjectMapper(); JsonNode jsonNode = mapper.readTree(json); String nodeStr = jsonNode.get("nodes").asText(); String dependenciesStr = jsonNode.get("dependencies").asText(); ArrayList<String> nodes = mapper.readValue(nodeStr, new TypeReference<ArrayList<String>>(){}); Map<String, List<String>> dependencies = mapper.readValue(dependenciesStr, new TypeReference<LinkedHashMap<String, List<String>>>(){}); DependencyManager dependencyManager = new DependencyManager(); for (String node:nodes) { dependencyManager.addNode(node); } for (String row: dependencies.keySet()) { for (String column: dependencies.get(row)) { dependencyManager.addDependency(column, row); } } return dependencyManager; } catch (IOException e) { throw new ValidationErrorException(e.getMessage()); } } private List<String> subListFrom(List<String> list, String element) { int index = list.indexOf(element); if (index == -1) return list; return list.subList(index, list.size()); } }