Simplifying graph, adding GraphOutput, and graph modification methods.

* Simplifying graph by removing pre-* nodes from default
* Adding GraphOutput interface to output graph to whatever you want
* Adding GraphOutputDot to dump graph to graphviz dot file format
* Adding new Graph.insert(Edge,Node) to allow for arbitrary graph
  modification.
* Changing Graph.getPath() to return NodePath instead of List<Node> to
  give some routines to get an Edge on the path via an index.
* Modifying test case to show Graph.insert(Edge,Node) in use, along with
  new NodePath Graph.getPath() behavior.

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@1132 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Joakim Erdfelt 2009-12-07 22:39:41 +00:00
parent 3e91339db5
commit 02c44adede
8 changed files with 397 additions and 91 deletions

View File

@ -62,15 +62,11 @@ public class AppLifeCycle extends Graph
// Private string constants defined to avoid typos on repeatedly used strings
private static final String NODE_UNDEPLOYED = "undeployed";
private static final String NODE_PRE_DEPLOYING = "pre-deploying";
private static final String NODE_DEPLOYING = "deploying";
private static final String NODE_DEPLOYED = "deployed";
private static final String NODE_PRE_STARTING = "pre-starting";
private static final String NODE_STARTING = "starting";
private static final String NODE_STARTED = "started";
private static final String NODE_PRE_STOPPING = "pre-stopping";
private static final String NODE_STOPPING = "stopping";
private static final String NODE_PRE_UNDEPLOYING = "pre-undeploying";
private static final String NODE_UNDEPLOYING = "undeploying";
private Map<Node, List<Binding>> lifecyclebindings = new HashMap<Node, List<Binding>>();
@ -79,23 +75,19 @@ public class AppLifeCycle extends Graph
// Define Default Graph
// undeployed -> deployed
addEdge(NODE_UNDEPLOYED,NODE_PRE_DEPLOYING);
addEdge(NODE_PRE_DEPLOYING,NODE_DEPLOYING);
addEdge(NODE_UNDEPLOYED,NODE_DEPLOYING);
addEdge(NODE_DEPLOYING,NODE_DEPLOYED);
// deployed -> started
addEdge(NODE_DEPLOYED,NODE_PRE_STARTING);
addEdge(NODE_PRE_STARTING,NODE_STARTING);
addEdge(NODE_DEPLOYED,NODE_STARTING);
addEdge(NODE_STARTING,NODE_STARTED);
// started -> deployed
addEdge(NODE_STARTED,NODE_PRE_STOPPING);
addEdge(NODE_PRE_STOPPING,NODE_STOPPING);
addEdge(NODE_STARTED,NODE_STOPPING);
addEdge(NODE_STOPPING,NODE_DEPLOYED);
// deployed -> undeployed
addEdge(NODE_DEPLOYED,NODE_PRE_UNDEPLOYING);
addEdge(NODE_PRE_UNDEPLOYING,NODE_UNDEPLOYING);
addEdge(NODE_DEPLOYED,NODE_UNDEPLOYING);
addEdge(NODE_UNDEPLOYING,NODE_UNDEPLOYED);
}

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.deploy.bindings.StandardStarter;
import org.eclipse.jetty.deploy.bindings.StandardStopper;
import org.eclipse.jetty.deploy.bindings.StandardUndeployer;
import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.deploy.graph.NodePath;
import org.eclipse.jetty.deploy.support.ConfigurationManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
@ -440,7 +441,7 @@ public class DeploymentManager extends AbstractLifeCycle
{
Node destinationNode = lifecycle.getNodeByName(nodeName);
// Compute lifecycle steps
List<Node> path = lifecycle.findPath(appentry.lifecyleNode,destinationNode);
NodePath path = lifecycle.getPath(appentry.lifecyleNode,destinationNode);
if (path.isEmpty())
{
// nothing to do. already there.

View File

@ -15,15 +15,12 @@
// ========================================================================
package org.eclipse.jetty.deploy.graph;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.Stack;
/**
* Basic directed graph implementation
@ -174,51 +171,6 @@ public class Graph
}
}
private class NodePath implements Iterable<Node>
{
private Stack<Node> path;
public NodePath()
{
path = new Stack<Node>();
}
public void add(Node node)
{
path.push(node);
}
public NodePath forkPath()
{
NodePath ep = new NodePath();
for (Node node : this)
{
ep.add(node);
}
return ep;
}
public Collection<Node> getCollection()
{
return path;
}
public Iterator<Node> iterator()
{
return path.iterator();
}
public Node lastNode()
{
return path.peek();
}
public int length()
{
return path.size();
}
}
private Set<Node> nodes = new HashSet<Node>();
private Set<Edge> edges = new HashSet<Edge>();
@ -252,6 +204,11 @@ public class Graph
addNode(toNode);
}
addEdge(fromNode,toNode);
}
private void addEdge(Node fromNode, Node toNode)
{
Edge edge = new Edge(fromNode,toNode);
addEdge(edge);
}
@ -261,6 +218,55 @@ public class Graph
this.nodes.add(node);
}
public void writeGraph(GraphOutput output) throws IOException
{
output.write(this);
}
/**
* Convenience method for {@link #insertNode(Edge, Node)}
*
* @param edge
* the edge to split and insert a node into
* @param nodeName
* the name of the node to insert along the edge
*/
public void insertNode(Edge edge, String nodeName)
{
Node node;
try
{
node = getNodeByName(nodeName);
}
catch (NodeNotFoundException e)
{
node = new Node(nodeName);
}
insertNode(edge,node);
}
/**
* Insert an arbitrary node on an existing edge.
*
* @param edge
* the edge to split and insert a node into
* @param node
* the node to insert along the edge
*/
public void insertNode(Edge edge, Node node)
{
// Remove existing edge
removeEdge(edge);
// Ensure node is added
addNode(node);
// Add start edge
addEdge(edge.getFrom(),node);
// Add second edge
addEdge(node,edge.getTo());
}
/**
* Find all edges that are connected to the specific node, both as an outgoing {@link Edge#getFrom()} or incoming
* {@link Edge#getTo()} end point.
@ -306,6 +312,27 @@ public class Graph
return fromedges;
}
/**
* Convenience method for {@link #getPath(Node, Node)}
*
* @param nodeNameOrigin
* the name of the node to the path origin.
* @param nodeNameDest
* the name of the node to the path destination.
* @return the path to take
*/
public NodePath getPath(String nodeNameOrigin, String nodeNameDest)
{
if (nodeNameOrigin.equals(nodeNameDest))
{
return new NodePath();
}
Node from = getNodeByName(nodeNameOrigin);
Node to = getNodeByName(nodeNameDest);
return getPath(from,to);
}
/**
* Using BFS (Breadth First Search) return the path from a any arbitrary node to any other.
*
@ -315,21 +342,18 @@ public class Graph
* the node to
* @return the path to take
*/
public List<Node> findPath(Node from, Node to)
public NodePath getPath(Node from, Node to)
{
if (from == to)
{
return Collections.emptyList();
return new NodePath();
}
// Perform a Breadth First Search (BFS) of the tree.
EdgeSearch search = new EdgeSearch(from);
search.breadthFirst(to);
NodePath nodepath = search.getShortestPath();
List<Node> path = new ArrayList<Node>();
path.addAll(nodepath.getCollection());
return path;
return search.getShortestPath();
}
public Set<Edge> getEdges()
@ -337,6 +361,15 @@ public class Graph
return edges;
}
/**
* Get the Node by Name.
*
* @param name
* the name to lookup
* @return the node if found
* @throws NodeNotFoundException
* if node not found
*/
public Node getNodeByName(String name)
{
for (Node node : nodes)

View File

@ -0,0 +1,28 @@
// ========================================================================
// Copyright (c) Webtide LLC
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.apache.org/licenses/LICENSE-2.0.txt
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.deploy.graph;
import java.io.IOException;
/**
* GraphOutput for writing in-memory graph to the a stream of some sort.
*
* Useful for outputting the graph in other formats.
*/
public interface GraphOutput
{
void write(Graph graph) throws IOException;
}

View File

@ -0,0 +1,160 @@
// ========================================================================
// Copyright (c) Webtide LLC
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.apache.org/licenses/LICENSE-2.0.txt
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.deploy.graph;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import org.eclipse.jetty.util.IO;
/**
* Output the Graph in GraphViz Dot format.
*/
public class GraphOutputDot implements GraphOutput
{
private File outputFile;
public GraphOutputDot(File outputFile)
{
this.outputFile = outputFile;
}
public void write(Graph graph) throws IOException
{
FileWriter writer = null;
PrintWriter out = null;
try
{
writer = new FileWriter(outputFile);
out = new PrintWriter(writer);
out.println("// Autogenerated by " + this.getClass().getName());
out.println("digraph Graf {");
writeGraphDefaults(out);
writeNodeDefaults(out);
writeEdgeDefaults(out);
for (Node node : graph.getNodes())
{
writeNode(out,node);
}
for (Edge edge : graph.getEdges())
{
writeEdge(out,edge);
}
out.println("}");
}
finally
{
IO.close(out);
IO.close(writer);
}
}
private void writeEdge(PrintWriter out, Edge edge)
{
out.println();
out.println(" // Edge");
out.printf(" \"%s\" -> \"%s\" [%n",toId(edge.getFrom()),toId(edge.getTo()));
out.printf(" arrowtail=none,%n");
out.printf(" arrowhead=normal%n");
out.println(" ];");
}
private void writeNode(PrintWriter out, Node node)
{
out.println();
out.println(" // Node");
out.printf(" \"%s\" [%n",toId(node));
out.printf(" label=\"%s\",%n",node.getName());
if (node.getName().endsWith("ed"))
{
out.printf(" color=\"#ddddff\",%n");
out.printf(" style=filled,%n");
}
out.printf(" shape=box%n");
out.println(" ];");
}
private CharSequence toId(Node node)
{
StringBuilder buf = new StringBuilder();
for (char c : node.getName().toCharArray())
{
if (Character.isLetter(c))
{
buf.append(c);
continue;
}
if (Character.isDigit(c))
{
buf.append(c);
continue;
}
if ((c == ' ') || (c == '-') || (c == '_'))
{
buf.append(c);
continue;
}
}
return buf;
}
private void writeEdgeDefaults(PrintWriter out)
{
out.println();
out.println(" // Edge Defaults ");
out.println(" edge [");
out.println(" arrowsize=\"0.8\",");
out.println(" fontsize=\"11\"");
out.println(" ];");
}
private void writeGraphDefaults(PrintWriter out)
{
out.println();
out.println(" // Graph Defaults ");
out.println(" graph [");
out.println(" bgcolor=\"#ffffff\",");
out.println(" fontname=\"Helvetica\",");
out.println(" fontsize=\"11\",");
out.println(" label=\"Graph\",");
out.println(" labeljust=\"l\",");
out.println(" rankdir=\"TD\"");
out.println(" ];");
}
private void writeNodeDefaults(PrintWriter out)
{
out.println();
out.println(" // Node Defaults ");
out.println(" node [");
out.println(" fontname=\"Helvetica\",");
out.println(" fontsize=\"11\",");
out.println(" shap=\"box\"");
out.println(" ];");
}
}

View File

@ -0,0 +1,94 @@
// ========================================================================
// Copyright (c) Webtide LLC
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.apache.org/licenses/LICENSE-2.0.txt
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.deploy.graph;
import java.util.Collection;
import java.util.Iterator;
import java.util.Stack;
public class NodePath implements Iterable<Node>
{
private Stack<Node> path;
public NodePath()
{
path = new Stack<Node>();
}
public void add(Node node)
{
path.push(node);
}
public NodePath forkPath()
{
NodePath ep = new NodePath();
for (Node node : this)
{
ep.add(node);
}
return ep;
}
public Collection<Node> getCollection()
{
return path;
}
public Iterator<Node> iterator()
{
return path.iterator();
}
public Node lastNode()
{
return path.peek();
}
public int length()
{
return path.size();
}
public boolean isEmpty()
{
return path.isEmpty();
}
public Edge getEdge(int index)
{
if (index < 0)
{
throw new IndexOutOfBoundsException("Unable to use a negative index " + index);
}
int upperBound = path.size() - 1;
if (index > upperBound)
{
throw new IndexOutOfBoundsException("Index " + index + " not within range (0 - " + upperBound + ")");
}
Node from = path.get(index);
Node to = path.get(index + 1);
return new Edge(from,to); // TODO: if edge has more details than from/to node, then this will need to be persisted within the NodePath as well.
}
public Node getNode(int index)
{
return path.get(index);
}
}

View File

@ -15,10 +15,15 @@
// ========================================================================
package org.eclipse.jetty.deploy;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.deploy.graph.GraphOutputDot;
import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.deploy.graph.NodePath;
import org.eclipse.jetty.deploy.test.MavenTestingUtils;
import org.junit.Assert;
import org.junit.Test;
@ -36,11 +41,11 @@ public class AppLifeCycleTest
{
Node fromNode = lifecycle.getNodeByName(from);
Node toNode = lifecycle.getNodeByName(to);
List<Node> actual = lifecycle.findPath(fromNode,toNode);
NodePath actual = lifecycle.getPath(fromNode,toNode);
String msg = "LifeCycle path from " + from + " to " + to;
Assert.assertNotNull(msg + " should never be null",actual);
if (expected.size() != actual.size())
if (expected.size() != actual.length())
{
System.out.println();
System.out.printf("/* from '%s' -> '%s' */%n",from,to);
@ -55,12 +60,12 @@ public class AppLifeCycleTest
System.out.println(path.getName());
}
Assert.assertEquals(msg + " / count",expected.size(),actual.size());
Assert.assertEquals(msg + " / count",expected.size(),actual.length());
}
for (int i = 0, n = expected.size(); i < n; i++)
{
Assert.assertEquals(msg + "[" + i + "]",expected.get(i),actual.get(i).getName());
Assert.assertEquals(msg + "[" + i + "]",expected.get(i),actual.getNode(i).getName());
}
}
@ -81,7 +86,6 @@ public class AppLifeCycleTest
{
List<String> expected = new ArrayList<String>();
expected.add("deployed");
expected.add("pre-starting");
expected.add("starting");
expected.add("started");
assertPath("deployed","started",expected);
@ -92,7 +96,6 @@ public class AppLifeCycleTest
{
List<String> expected = new ArrayList<String>();
expected.add("deployed");
expected.add("pre-undeploying");
expected.add("undeploying");
expected.add("undeployed");
assertPath("deployed","undeployed",expected);
@ -103,7 +106,6 @@ public class AppLifeCycleTest
{
List<String> expected = new ArrayList<String>();
expected.add("started");
expected.add("pre-stopping");
expected.add("stopping");
expected.add("deployed");
assertPath("started","deployed",expected);
@ -120,10 +122,8 @@ public class AppLifeCycleTest
{
List<String> expected = new ArrayList<String>();
expected.add("started");
expected.add("pre-stopping");
expected.add("stopping");
expected.add("deployed");
expected.add("pre-undeploying");
expected.add("undeploying");
expected.add("undeployed");
assertPath("started","undeployed",expected);
@ -134,7 +134,6 @@ public class AppLifeCycleTest
{
List<String> expected = new ArrayList<String>();
expected.add("undeployed");
expected.add("pre-deploying");
expected.add("deploying");
expected.add("deployed");
assertPath("undeployed","deployed",expected);
@ -145,10 +144,8 @@ public class AppLifeCycleTest
{
List<String> expected = new ArrayList<String>();
expected.add("undeployed");
expected.add("pre-deploying");
expected.add("deploying");
expected.add("deployed");
expected.add("pre-starting");
expected.add("starting");
expected.add("started");
assertPath("undeployed","started",expected);
@ -163,17 +160,24 @@ public class AppLifeCycleTest
/**
* Request multiple lifecycle paths with a single lifecycle instance. Just to ensure that there is no state
* maintained between {@link AppLifeCycle#findPath(Node, Node)} requests.
*
* @throws IOException
*/
@Test
public void testFindPathMultiple()
public void testFindPathMultiple() throws IOException
{
AppLifeCycle lifecycle = new AppLifeCycle();
List<String> expected = new ArrayList<String>();
lifecycle.removeEdge("deployed","pre-starting");
lifecycle.addEdge("deployed","staging");
lifecycle.addEdge("staging","staged");
lifecycle.addEdge("staged","pre-starting");
File outputDir = MavenTestingUtils.getTargetTestingDir(this.getClass().getName() + ".testFindPathMultiple");
outputDir.mkdirs();
// Modify graph to add new 'staging' -> 'staged' between 'deployed' and 'started'
lifecycle.writeGraph(new GraphOutputDot(new File(outputDir,"multiple-1.dot"))); // before change
lifecycle.insertNode(lifecycle.getPath("deployed","started").getEdge(0),"staging");
lifecycle.writeGraph(new GraphOutputDot(new File(outputDir,"multiple-2.dot"))); // after first change
lifecycle.insertNode(lifecycle.getPath("staging","started").getEdge(0),"staged");
lifecycle.writeGraph(new GraphOutputDot(new File(outputDir,"multiple-3.dot"))); // after second change
// Deployed -> Deployed
expected.clear();
@ -189,13 +193,10 @@ public class AppLifeCycleTest
// Staged -> Undeployed
expected.clear();
expected.add("staged");
expected.add("pre-starting");
expected.add("starting");
expected.add("started");
expected.add("pre-stopping");
expected.add("stopping");
expected.add("deployed");
expected.add("pre-undeploying");
expected.add("undeploying");
expected.add("undeployed");
assertPath(lifecycle,"staged","undeployed",expected);
@ -203,12 +204,10 @@ public class AppLifeCycleTest
// Undeployed -> Started
expected.clear();
expected.add("undeployed");
expected.add("pre-deploying");
expected.add("deploying");
expected.add("deployed");
expected.add("staging");
expected.add("staged");
expected.add("pre-starting");
expected.add("starting");
expected.add("started");
assertPath(lifecycle,"undeployed","started",expected);

View File

@ -49,7 +49,6 @@ public class DeploymentManagerLifeCyclePathTest
// Setup Expectations.
List<String> expected = new ArrayList<String>();
// SHOULD NOT SEE THIS NODE VISITED - expected.add("undeployed");
expected.add("pre-deploying");
expected.add("deploying");
expected.add("deployed");