This is an automated email from the ASF dual-hosted git repository.

martin_s pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/archiva-components.git


The following commit(s) were added to refs/heads/master by this push:
     new d9378f8  Adding graph component
d9378f8 is described below

commit d9378f8a774e6b60257645968041d4fa4ddfc890
Author: Martin Stockhammer <[email protected]>
AuthorDate: Tue Nov 12 21:35:11 2019 +0100

    Adding graph component
---
 graph/pom.xml                                      |  44 ++
 .../apache/archiva/components/graph/api/Edge.java  |  80 ++++
 .../apache/archiva/components/graph/api/Graph.java |  50 ++
 .../components/graph/api/TraversalStatus.java      | 107 +++++
 .../archiva/components/graph/api/Vertex.java       |  70 +++
 .../archiva/components/graph/api/VisitError.java   |  38 ++
 .../archiva/components/graph/base/BaseEdge.java    |  97 ++++
 .../components/graph/base/DirectedGraph.java       |  91 ++++
 .../archiva/components/graph/base/SimpleGraph.java |  66 +++
 .../components/graph/base/SimpleVertex.java        | 112 +++++
 .../archiva/components/graph/util/Traversal.java   | 202 +++++++++
 .../components/graph/base/BaseEdgeTest.java        | 108 +++++
 .../components/graph/base/SimpleGraphTest.java     |  70 +++
 .../components/graph/base/SimpleVertexTest.java    | 112 +++++
 .../components/graph/util/TraversalTest.java       | 505 +++++++++++++++++++++
 graph/src/test/resources/log4j2-test.xml           |  36 ++
 pom.xml                                            |   1 +
 17 files changed, 1789 insertions(+)

diff --git a/graph/pom.xml b/graph/pom.xml
new file mode 100644
index 0000000..803b981
--- /dev/null
+++ b/graph/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <parent>
+    <artifactId>archiva-components</artifactId>
+    <groupId>org.apache.archiva.components</groupId>
+    <version>3.0-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>archiva-components-graph</artifactId>
+  <name>Archiva Components :: Graph</name>
+  <description>Simple implementation of graph structure and utility class for 
traversal.</description>
+
+
+  <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+  </dependencies>
+
+
+</project>
\ No newline at end of file
diff --git 
a/graph/src/main/java/org/apache/archiva/components/graph/api/Edge.java 
b/graph/src/main/java/org/apache/archiva/components/graph/api/Edge.java
new file mode 100644
index 0000000..5254ec8
--- /dev/null
+++ b/graph/src/main/java/org/apache/archiva/components/graph/api/Edge.java
@@ -0,0 +1,80 @@
+package org.apache.archiva.components.graph.api;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * The edge links a source vertex to a destination vertex.
+ * A edge instance is always part of a certain graph instance.
+ *
+ * @param <V> The vertex implementation.
+ */
+public interface Edge<V extends Vertex<V>> {
+
+    /**
+     * Returns the identifier of this edge. The id must be unique for given 
graph.
+     *
+     * @return the identifier of this edge
+     */
+    String getId();
+
+    /**
+     * Returns the label of this edge. The label must not be unique.
+     * If the label was not set, it should return an empty string.
+     * @return the label of this edge, or a empty string
+     */
+    String getLabel();
+
+    /**
+     * Sets the label of this edge to the given string.
+     * @param label the label string, that must not be <code>null</code>
+     * @throws NullPointerException if the label parameter is <code>null</code>
+     */
+    void setLabel(String label) throws NullPointerException;
+
+    /**
+     * Returns the graph where this edge was created.
+     *
+     * @return the graph instance
+     */
+    Graph<V> getGraph();
+
+    /**
+     * Returns the vertex, that is on the source end of this edge.
+     * @return the source vertex
+     */
+    V getSource();
+
+    /**
+     * Returns the vertex, that is on the destination end of this edge.
+     * @return the destination vertex
+     */
+    V getDestination();
+
+    /**
+     * Returns the weight of this edge. For standard graph implementations the 
default should be 1.0, but
+     * graph implementations may decide to set another default.
+     *
+     * @return the weight of this edge
+     */
+    double getWeight();
+
+
+
+
+}
diff --git 
a/graph/src/main/java/org/apache/archiva/components/graph/api/Graph.java 
b/graph/src/main/java/org/apache/archiva/components/graph/api/Graph.java
new file mode 100644
index 0000000..13cdb90
--- /dev/null
+++ b/graph/src/main/java/org/apache/archiva/components/graph/api/Graph.java
@@ -0,0 +1,50 @@
+package org.apache.archiva.components.graph.api;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.components.graph.base.SimpleVertex;
+
+import java.util.Set;
+
+/**
+ *
+ * A graph is the container for vertices. The vertices are connected by edges. 
Each vertex and
+ * edge instance is coupled to the graph where it was created.
+ *
+ * @param <V>
+ */
+public interface Graph<V extends Vertex<V>> {
+
+    V addVertex(String label);
+
+    void removeVertex(V vertex);
+
+    Edge<V> addEdge(String label, V inVertex, V outVertex);
+
+    void removeEdge(Edge<SimpleVertex> edge);
+
+    V getVertex(String id);
+
+    Edge<V> getEdge(String id);
+
+    Set<V> getVertices();
+
+
+
+}
diff --git 
a/graph/src/main/java/org/apache/archiva/components/graph/api/TraversalStatus.java
 
b/graph/src/main/java/org/apache/archiva/components/graph/api/TraversalStatus.java
new file mode 100644
index 0000000..eb589ce
--- /dev/null
+++ 
b/graph/src/main/java/org/apache/archiva/components/graph/api/TraversalStatus.java
@@ -0,0 +1,107 @@
+package org.apache.archiva.components.graph.api;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class gives information about the status of the graph traversal.
+ * It holds the errors encountered and information about detected cycles.
+ *
+ * @param <V>
+ */
+public class TraversalStatus<V extends Vertex<V>> {
+
+    private int cycles = 0;
+    private List<VisitError<V>> errorList;
+    private List<Vertex<V>> cycleVertices;
+
+    public TraversalStatus() {
+
+    }
+
+    /**
+     * Returns the list of errors
+     * @return a list of errors encountered while executing the consumer 
function on vertices
+     */
+    public List<VisitError<V>> getErrorList() {
+        return errorList;
+    }
+
+    /**
+     * Returns true, if there were errors while running the consumer function.
+     * @return true, if errors occured on the consumer function, otherwise 
false
+     */
+    public boolean hasErrors() {
+        return errorList!=null && errorList.size()>0;
+    }
+
+    /**
+     * Add the given error to the list.
+     *
+     * @param vertex the vertex, where the error occured
+     * @param e the exception
+     */
+    public void addError(V vertex, Throwable e) {
+        if (errorList==null) {
+            errorList = new ArrayList<>();
+        }
+        errorList.add(new VisitError(vertex, e));
+    }
+
+    /**
+     * Add another cycle to the counter
+     * @param vertex
+     */
+    public void registerCycle(Vertex<V> vertex) {
+        cycles++;
+        if (cycleVertices==null) {
+            cycleVertices = new ArrayList<>();
+        }
+        cycleVertices.add(vertex);
+    }
+
+    /**
+     * Returns true, if the traversal encountered a cycle.
+     * @return true, if cycle was encountered, otherwise false
+     */
+    public boolean hasCycles() {
+        return cycles>0;
+    }
+
+    /**
+     * Returns the number of detected cycles.
+     *
+     * @return
+     */
+    public int getCycleCount() {
+        return cycles;
+    }
+
+    /**
+     * Returns the vertices, where cycles were detected. The list may contain
+     * duplicated entries, if cycles where detected on different paths.
+     *
+     * @return the list of vertices where cycles were detected
+     */
+    public List<Vertex<V>> getCycleVertices() {
+        return cycleVertices;
+    }
+}
diff --git 
a/graph/src/main/java/org/apache/archiva/components/graph/api/Vertex.java 
b/graph/src/main/java/org/apache/archiva/components/graph/api/Vertex.java
new file mode 100644
index 0000000..d3937f3
--- /dev/null
+++ b/graph/src/main/java/org/apache/archiva/components/graph/api/Vertex.java
@@ -0,0 +1,70 @@
+package org.apache.archiva.components.graph.api;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.List;
+
+/**
+ * The vertex is a node in a graph structure. The vertex may have connections 
to and from
+ * other vertices (edges).
+ *
+ * @param <V> The vertex implementation
+ */
+public interface Vertex<V extends Vertex<V>> {
+
+    /**
+     * Returns the identifier of this vertex. The identifier is unique for a 
given graph.
+     * @return the identifier
+     */
+    String getId();
+
+    /**
+     * Returns the label of this vertex. The label is a name and must not be 
unique.
+     * If the label was not set it should return a empty string.
+     * @return the label of the vertex or a empty string
+     */
+    String getLabel();
+
+    /**
+     * Sets the label of the vertex
+     * @param label sets the label string, must not be <code>null</code>
+     * @throws NullPointerException if the given label is <code>null</code>
+     */
+    void setLabel(String label) throws NullPointerException;
+
+    /**
+     * Returns the graph where this vertex was created.
+     * @return the graph instance
+     */
+    Graph<V> getGraph();
+
+    /**
+     * Returns all edges that connect to other nodes
+     * @return the edges where this vertex is the source
+     */
+    List<Edge<V>> getOutEdges();
+
+    /**
+     * Returns all edges that connect other nodes to this vertex
+     * @return the edges where this vertex is the destination
+     */
+    List<Edge<V>> getInEdges();
+
+
+}
diff --git 
a/graph/src/main/java/org/apache/archiva/components/graph/api/VisitError.java 
b/graph/src/main/java/org/apache/archiva/components/graph/api/VisitError.java
new file mode 100644
index 0000000..41f1c09
--- /dev/null
+++ 
b/graph/src/main/java/org/apache/archiva/components/graph/api/VisitError.java
@@ -0,0 +1,38 @@
+package org.apache.archiva.components.graph.api;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+public class VisitError<V extends Vertex<V>> {
+
+    private final Throwable exception;
+    private final V vertex;
+
+    public VisitError(V vertex, Throwable exception) {
+        this.exception = exception;
+        this.vertex = vertex;
+    }
+
+    public Throwable getException() {
+        return exception;
+    }
+
+    public V getVertex() {
+        return vertex;
+    }
+}
diff --git 
a/graph/src/main/java/org/apache/archiva/components/graph/base/BaseEdge.java 
b/graph/src/main/java/org/apache/archiva/components/graph/base/BaseEdge.java
new file mode 100644
index 0000000..bdf274e
--- /dev/null
+++ b/graph/src/main/java/org/apache/archiva/components/graph/base/BaseEdge.java
@@ -0,0 +1,97 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+import org.apache.archiva.components.graph.api.Graph;
+import org.apache.archiva.components.graph.api.Vertex;
+
+public class BaseEdge<V extends Vertex<V>> implements Edge<V>, 
Comparable<BaseEdge<V>> {
+
+    private final String id;
+    private String label;
+    private double weight = 1.0;
+    private V sourceVertex;
+    private V destinationVertex;
+    private final Graph<V> graph;
+
+    public BaseEdge(Graph<V> graph, String id, V sourceVertex, V 
destinationVertex) {
+        this.id = id;
+        this.graph = graph;
+        this.sourceVertex = sourceVertex;
+        this.destinationVertex = destinationVertex;
+    }
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    public void setWeight(double weight) {
+        this.weight = weight;
+    }
+
+    @Override
+    public Graph<V> getGraph() {
+        return graph;
+    }
+
+    @Override
+    public V getSource() {
+        return sourceVertex;
+    }
+
+    @Override
+    public V getDestination() {
+        return destinationVertex;
+    }
+
+    @Override
+    public double getWeight() {
+        return weight;
+    }
+
+    @Override
+    public int hashCode() {
+        return this.id.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return this.id+"|"+this.label+": "+ sourceVertex.toString() + " -> " + 
destinationVertex.toString();
+    }
+
+    @Override
+    public int compareTo(BaseEdge<V> o) {
+        if (this.label!=null || o.getLabel()!=null) {
+            return this.label.compareTo(o.getLabel());
+        } else {
+            return this.id.compareTo(o.getId());
+        }
+    }
+}
diff --git 
a/graph/src/main/java/org/apache/archiva/components/graph/base/DirectedGraph.java
 
b/graph/src/main/java/org/apache/archiva/components/graph/base/DirectedGraph.java
new file mode 100644
index 0000000..eb946b1
--- /dev/null
+++ 
b/graph/src/main/java/org/apache/archiva/components/graph/base/DirectedGraph.java
@@ -0,0 +1,91 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+import org.apache.archiva.components.graph.api.Graph;
+import org.apache.archiva.components.graph.api.Vertex;
+
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+
+/**
+ * Abstract implementation of a directed graph
+ * @param <V> The vertex implementation.
+ */
+public abstract class DirectedGraph<V extends Vertex<V>> implements Graph<V> {
+
+
+    protected TreeMap<String, V> vertices = new TreeMap<>();
+    protected TreeMap<String, Edge<V>> edges = new TreeMap<>();
+
+    public V addVertex(String label) {
+        V vtx = createNewVertex();
+        vtx.setLabel(label);
+        this.vertices.put(vtx.getId(), vtx);
+        return vtx;
+    }
+
+    /**
+     * Subclasses that implement this method must return a new vertex instance 
for
+     * each call. The vertex should not be connected with any edges.
+     *
+     * @return A new vertex instance.
+     */
+    abstract V createNewVertex();
+
+    /**
+     * Subclasses that implement this method must return a new instance of the 
edge with
+     * the given source and destination vertices. The vertices must be linked 
by the new
+     * edge.
+     *
+     * @param sourceVertex the source vertex
+     * @param destinationVertex the destination vertex
+     * @return A new edge instance.
+     */
+    abstract Edge<V> createNewEdge(V sourceVertex, V destinationVertex);
+
+
+
+    @Override
+    public Edge<V> addEdge(String label, V sourceVertex, V destinationVertex) {
+        Edge<V> edge = createNewEdge(sourceVertex, destinationVertex);
+        edge.setLabel(label);
+        this.edges.put(edge.getId(), edge);
+        return edge;
+    }
+
+    @Override
+    public V getVertex(String id) {
+        return vertices.get(id);
+    }
+
+    @Override
+    public Edge<V> getEdge(String id) {
+        return edges.get(id);
+    }
+
+    @Override
+    public Set<V> getVertices() {
+        return vertices.values().stream().collect(Collectors.toSet());
+    }
+
+}
diff --git 
a/graph/src/main/java/org/apache/archiva/components/graph/base/SimpleGraph.java 
b/graph/src/main/java/org/apache/archiva/components/graph/base/SimpleGraph.java
new file mode 100644
index 0000000..9c51115
--- /dev/null
+++ 
b/graph/src/main/java/org/apache/archiva/components/graph/base/SimpleGraph.java
@@ -0,0 +1,66 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+
+import java.util.UUID;
+
+/**
+ * Simple directed graph implementation that uses UUIDs as unique identifiers 
for
+ * each vertex and edge.
+ *
+ *
+ */
+public class SimpleGraph extends DirectedGraph<SimpleVertex> {
+
+    @Override
+    SimpleVertex createNewVertex() {
+        return new SimpleVertex(this, UUID.randomUUID().toString());
+    }
+
+    @Override
+    Edge<SimpleVertex> createNewEdge(SimpleVertex sourceVertex, SimpleVertex 
destinationVertex) {
+        BaseEdge edge = new BaseEdge<>(this, UUID.randomUUID().toString(), 
sourceVertex, destinationVertex);
+        addEdgeToVertex(sourceVertex, edge);
+        addEdgeToVertex(destinationVertex, edge);
+        return edge;
+    }
+
+    private void addEdgeToVertex(SimpleVertex vertex, Edge<SimpleVertex> edge) 
{
+        vertex.addEdge(edge);
+    }
+
+    @Override
+    public void removeEdge(Edge<SimpleVertex> edge) {
+        edges.remove(edge);
+        edge.getSource().removeEdge(edge);
+        edge.getDestination().removeEdge(edge);
+    }
+
+    @Override
+    public void removeVertex(SimpleVertex vertex) {
+        for (Edge<SimpleVertex> edge : vertex.getInEdges()) {
+            removeEdge(edge);
+        }
+        for (Edge<SimpleVertex> edge : vertex.getOutEdges()) {
+            removeEdge(edge);
+        }
+    }
+}
diff --git 
a/graph/src/main/java/org/apache/archiva/components/graph/base/SimpleVertex.java
 
b/graph/src/main/java/org/apache/archiva/components/graph/base/SimpleVertex.java
new file mode 100644
index 0000000..4cd3aa2
--- /dev/null
+++ 
b/graph/src/main/java/org/apache/archiva/components/graph/base/SimpleVertex.java
@@ -0,0 +1,112 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+import org.apache.archiva.components.graph.api.Graph;
+import org.apache.archiva.components.graph.api.Vertex;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple vertex implementation. The hash value is based on the id.
+ * Comparation is by label, if exists, otherwise by id.
+ */
+public class SimpleVertex implements Vertex<SimpleVertex>, 
Comparable<SimpleVertex>
+{
+
+    private final String id;
+    private String label;
+    private final Graph<SimpleVertex> graph;
+    private List<Edge<SimpleVertex>> outEdges = new ArrayList<>();
+    private List<Edge<SimpleVertex>> inEdges = new ArrayList<>();
+
+    SimpleVertex(Graph<SimpleVertex> graph, String id) {
+        this.id = id;
+        this.graph = graph;
+    }
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public String getLabel() {
+        return label;
+    }
+
+    @Override
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    @Override
+    public Graph<SimpleVertex> getGraph() {
+        return graph;
+    }
+
+    @Override
+    public List<Edge<SimpleVertex>> getOutEdges() {
+        return outEdges;
+    }
+
+    @Override
+    public List<Edge<SimpleVertex>> getInEdges() {
+        return inEdges;
+    }
+
+    protected void addEdge(Edge<SimpleVertex> edge) {
+        if (edge.getSource() == this) {
+            outEdges.add(edge);
+        } else if (edge.getDestination() == this) {
+            inEdges.add(edge);
+        } else {
+            throw new IllegalArgumentException("The given edge does not 
contain this vertex");
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return this.id.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return this.id+"|"+this.label;
+    }
+
+    public void removeEdge(Edge<SimpleVertex> edge) {
+        if (edge.getDestination()==this) {
+            inEdges.remove(edge);
+        } else if (edge.getSource()==this) {
+            outEdges.remove(edge);
+        }
+    }
+
+    @Override
+    public int compareTo(SimpleVertex o) {
+        if (label!=null && o.getLabel()!=null) {
+            return this.label.compareTo(o.getLabel());
+        } else {
+            return this.id.compareTo(o.getId());
+        }
+    }
+}
diff --git 
a/graph/src/main/java/org/apache/archiva/components/graph/util/Traversal.java 
b/graph/src/main/java/org/apache/archiva/components/graph/util/Traversal.java
new file mode 100644
index 0000000..e80f26a
--- /dev/null
+++ 
b/graph/src/main/java/org/apache/archiva/components/graph/util/Traversal.java
@@ -0,0 +1,202 @@
+package org.apache.archiva.components.graph.util;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+import org.apache.archiva.components.graph.api.TraversalStatus;
+import org.apache.archiva.components.graph.api.Vertex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Utility class for graph traversal.
+ */
+public class Traversal {
+
+    private static final Logger log = LoggerFactory.getLogger(Traversal.class);
+
+    /**
+     * Traverses the graph starting at the start node and using a depth first 
algorithm.
+     * Each node will only be consumed once.
+     * The consumer function is applied for each visited node and must return 
True, if the
+     * traversal should continue.
+     * If the directed flag is set to true, only outgoing edges are used for 
traversal from one one
+     * to the other, otherwise, incoming edges are used too.
+     *
+     * @param start           the start node
+     * @param consumer        The consumer function. The function must return 
<code>true</code>, if the traversal should
+     *                        continue, otherwise <code>false</code>
+     * @param directed        If true, only outgoing edges are used to 
navigate to neighbours, otherwise incoming and outgoing
+     *                        edges are used.
+     * @param continueOnError If true, the traversal continues, even if the 
consumer function threw an error
+     * @param <V>
+     * @return The traversal status
+     */
+    @SuppressWarnings("Duplicates")
+    public static <V extends Vertex<V>> TraversalStatus<V> depthFirst(final V 
start, final Function<V, Boolean> consumer,
+                                                                      final 
boolean directed, final boolean continueOnError) {
+        TraversalStatus<V> status = new TraversalStatus<>();
+        Set<V> visited = new LinkedHashSet<>();
+        Stack<V> stack = new Stack<>();
+        stack.push(start);
+        while (!stack.isEmpty()) {
+            V vertex = stack.pop();
+            if (!visited.contains(vertex)) {
+                Boolean continueTraversal = Boolean.TRUE;
+                try {
+                    continueTraversal = consumer.apply(vertex);
+                } catch (Throwable e) {
+                    log.debug("Error during visit. Vertex: {}, Message: {}", 
vertex, e.getMessage());
+                    status.addError(vertex, e);
+                    if (!continueOnError) {
+                        break;
+                    }
+                }
+                visited.add(vertex);
+                log.debug("Visited: " + vertex);
+                if (!continueTraversal) {
+                    break;
+                }
+                // We traverse using the order of the edges. This is a stack, 
so the elements that
+                // should be visited first are pushed last on the stack.
+                final List<Edge<V>> outEdges = vertex.getOutEdges();
+                final int outEdgeMaxIdx = vertex.getOutEdges().size() - 1;
+                for (int i = outEdgeMaxIdx; i >= 0; i--) {
+                    try {
+                        Edge<V> v = outEdges.get(i);
+                        log.debug("Directed destination: " + 
v.getDestination());
+                        stack.push(v.getDestination());
+                    } catch (IndexOutOfBoundsException e) {
+                        log.warn("Modification of graph during traversal of 
output edges: " + vertex + " Index: " + i);
+                    }
+                }
+                if (!directed) {
+                    final List<Edge<V>> inEdges = vertex.getInEdges();
+                    final int inEdgeMaxIdx = vertex.getOutEdges().size() - 1;
+                    for (int i = inEdgeMaxIdx; i >= 0; i--) {
+                        try {
+                            Edge<V> v = inEdges.get(i);
+                            log.debug("Undirected source: " + v.getSource());
+                            stack.push(v.getSource());
+                        } catch (IndexOutOfBoundsException e) {
+                            log.warn("Modification of graph during traversal 
of input edges: " + vertex + " Index: " + i);
+                        }
+                    }
+                }
+            } else {
+                status.registerCycle(vertex);
+            }
+        }
+        return status;
+    }
+
+    /**
+     * Same as {@link #depthFirst(Vertex, Function, boolean, boolean)} but 
sets the <code>continueOnError</code> parameter to <code>true</code>.
+     */
+    public static <V extends Vertex<V>> TraversalStatus<V> depthFirst(V start, 
Function<V, Boolean> consumer,
+                                                                      boolean 
directed) {
+        return depthFirst(start, consumer, directed, true);
+    }
+
+    /**
+     * Same as {@link #depthFirst(Vertex, Function, boolean, boolean)} but 
sets the <code>continueOnError</code>
+     * parameter to <code>true</code> and <code>directed</code> parameter to 
<code>true</code>.
+     */
+    public static <V extends Vertex<V>> TraversalStatus<V> depthFirst(V start, 
Function<V, Boolean> consumer) {
+        return depthFirst(start, consumer, true, true);
+    }
+
+    /**
+     * Traverses the graph starting at the start node and using a breadth 
first algorithm.
+     * Each node will only be consumed once.
+     * The consumer function is applied for each visited node and must return 
True, if the
+     * traversal should continue.
+     * If the directed flag is set to true, only outgoing edges are used for 
traversal from one one
+     * to the other, otherwise, incoming edges are used too.
+     *
+     * @param start           the start node
+     * @param consumer        The consumer function. The function must return 
<code>true</code>, if the traversal should
+     *                        continue, otherwise <code>false</code>
+     * @param directed        If true, only outgoing edges are used to 
navigate to neighbours, otherwise incoming and outgoing
+     *                        edges are used.
+     * @param continueOnError If true, the traversal continues, even if the 
consumer function threw an error
+     * @param <V>
+     * @return The traversal status
+     */
+    @SuppressWarnings("Duplicates")
+    public static <V extends Vertex<V>> TraversalStatus<V> breadthFirst(final 
V start, final Function<V, Boolean> consumer,
+                                                                        final 
boolean directed, final boolean continueOnError) {
+        final TraversalStatus<V> status = new TraversalStatus<>();
+        final Set<V> visited = new LinkedHashSet<>();
+        final Queue<V> queue = new LinkedList<>();
+        queue.add(start);
+        while (!queue.isEmpty()) {
+            V vertex = queue.poll();
+            if (!visited.contains(vertex)) {
+                Boolean continueTraversal = Boolean.TRUE;
+                try {
+                    continueTraversal = consumer.apply(vertex);
+                } catch (Throwable e) {
+                    log.debug("Error during visit. Vertex: {}, Message: {}", 
vertex, e.getMessage());
+                    status.addError(vertex, e);
+                    if (!continueOnError) {
+                        break;
+                    }
+                }
+                visited.add(vertex);
+                log.debug("Visited: " + vertex);
+                if (!continueTraversal) {
+                    break;
+                }
+                for (Edge<V> v : vertex.getOutEdges()) {
+                    queue.add(v.getDestination());
+                }
+                if (!directed) {
+                    for (Edge<V> v : vertex.getInEdges()) {
+                        queue.add(v.getSource());
+                    }
+                }
+            } else {
+                status.registerCycle(vertex);
+            }
+        }
+        return status;
+    }
+
+    /**
+     * Same as {@link #breadthFirst(Vertex, Function, boolean, boolean)} but 
sets <code>continueOnError</code>  to <code>true</code>.
+     */
+    public static <V extends Vertex<V>> TraversalStatus<V> breadthFirst(final 
V start, final Function<V, Boolean> consumer,
+                                                                        final 
boolean directed) {
+        return breadthFirst(start, consumer, directed, true);
+    }
+
+    /**
+     * Same as {@link #breadthFirst(Vertex, Function, boolean, boolean)} but 
sets <code>continueOnError</code> to <code>true</code>
+     * and <code>directed</code> to <code>true</code>.
+     */
+    public static <V extends Vertex<V>> TraversalStatus<V> breadthFirst(final 
V start, final Function<V, Boolean> consumer) {
+        return breadthFirst(start, consumer, true, true);
+    }
+
+}
diff --git 
a/graph/src/test/java/org/apache/archiva/components/graph/base/BaseEdgeTest.java
 
b/graph/src/test/java/org/apache/archiva/components/graph/base/BaseEdgeTest.java
new file mode 100644
index 0000000..22f2e3d
--- /dev/null
+++ 
b/graph/src/test/java/org/apache/archiva/components/graph/base/BaseEdgeTest.java
@@ -0,0 +1,108 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.components.graph.api.Graph;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class BaseEdgeTest {
+
+
+    private Graph<SimpleVertex> graph = new SimpleGraph();
+
+    @Test
+    void getId() {
+        SimpleVertex vtx1 = new SimpleVertex(graph, "vtx1");
+        SimpleVertex vtx2 = new SimpleVertex(graph, "vtx2");
+
+        BaseEdge<SimpleVertex> edge = new BaseEdge<>(graph, "edge01", vtx1, 
vtx2);
+
+        assertNotNull(edge.getId());
+        assertEquals("edge01", edge.getId());
+
+
+    }
+
+    @Test
+    void getLabel() {
+        SimpleVertex vtx1 = new SimpleVertex(graph, "vtx1");
+        SimpleVertex vtx2 = new SimpleVertex(graph, "vtx2");
+
+        BaseEdge<SimpleVertex> edge = new BaseEdge<>(graph, "edge01", vtx1, 
vtx2);
+
+        assertNull(edge.getLabel());
+
+        edge.setLabel("edgelabel01");
+
+        assertEquals("edgelabel01", edge.getLabel());
+
+        edge.setLabel("Another label with exotic characters äÄö~§");
+        assertEquals("Another label with exotic characters äÄö~§", 
edge.getLabel());
+
+    }
+
+    @Test
+    void setWeight() {
+        SimpleVertex vtx1 = new SimpleVertex(graph, "vtx1");
+        SimpleVertex vtx2 = new SimpleVertex(graph, "vtx2");
+
+        BaseEdge<SimpleVertex> edge = new BaseEdge<>(graph, "edge01", vtx1, 
vtx2);
+
+        assertEquals(1.0, edge.getWeight());
+
+        edge.setWeight(1.5);
+        assertEquals(1.5, edge.getWeight());
+
+
+
+    }
+
+    @Test
+    void getGraph() {
+        SimpleVertex vtx1 = new SimpleVertex(graph, "vtx1");
+        SimpleVertex vtx2 = new SimpleVertex(graph, "vtx2");
+
+        BaseEdge<SimpleVertex> edge = new BaseEdge<>(graph, "edge01", vtx1, 
vtx2);
+        assertNotNull(edge.getGraph());
+        assertTrue(edge.getGraph() == graph);
+    }
+
+    @Test
+    void getInVertex() {
+        SimpleVertex vtx1 = new SimpleVertex(graph, "vtx1");
+        SimpleVertex vtx2 = new SimpleVertex(graph, "vtx2");
+
+        BaseEdge<SimpleVertex> edge = new BaseEdge<>(graph, "edge01", vtx1, 
vtx2);
+
+        assertEquals(vtx1, edge.getSource());
+    }
+
+    @Test
+    void getOutVertex() {
+        SimpleVertex vtx1 = new SimpleVertex(graph, "vtx1");
+        SimpleVertex vtx2 = new SimpleVertex(graph, "vtx2");
+
+        BaseEdge<SimpleVertex> edge = new BaseEdge<>(graph, "edge01", vtx1, 
vtx2);
+
+        assertEquals(vtx2, edge.getDestination());
+    }
+
+}
\ No newline at end of file
diff --git 
a/graph/src/test/java/org/apache/archiva/components/graph/base/SimpleGraphTest.java
 
b/graph/src/test/java/org/apache/archiva/components/graph/base/SimpleGraphTest.java
new file mode 100644
index 0000000..6455528
--- /dev/null
+++ 
b/graph/src/test/java/org/apache/archiva/components/graph/base/SimpleGraphTest.java
@@ -0,0 +1,70 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SimpleGraphTest {
+
+    @Test
+    void createNewVertex() {
+        SimpleGraph graph = new SimpleGraph();
+        SimpleVertex vtx = graph.createNewVertex();
+        assertNotNull(vtx);
+        assertNotNull(vtx.getId());
+        assertNotNull(java.util.UUID.fromString(vtx.getId()));
+    }
+
+    @Test
+    void createNewEdge() {
+        SimpleGraph graph = new SimpleGraph();
+        SimpleVertex vtx1 = graph.createNewVertex();
+        SimpleVertex vtx2 = graph.createNewVertex();
+        Edge<SimpleVertex> edge = graph.createNewEdge(vtx1, vtx2);
+        assertNotNull(edge);
+        assertNotNull(edge.getId());
+        assertNotNull(java.util.UUID.fromString(edge.getId()));
+        assertEquals(vtx1, edge.getSource());
+        assertEquals(vtx2, edge.getDestination());
+    }
+
+
+    @Test
+    void removeEdge() {
+        SimpleGraph graph = new SimpleGraph();
+        SimpleVertex vtx1 = graph.createNewVertex();
+        SimpleVertex vtx2 = graph.createNewVertex();
+        Edge<SimpleVertex> edge = graph.createNewEdge(vtx1, vtx2);
+
+        graph.removeEdge(edge);
+
+        assertEquals(0, vtx1.getInEdges().size());
+        assertEquals(0, vtx2.getInEdges().size());
+        assertEquals(0, vtx1.getOutEdges().size());
+        assertEquals(0, vtx2.getOutEdges().size());
+
+    }
+
+    @Test
+    void removeVertex() {
+    }
+}
\ No newline at end of file
diff --git 
a/graph/src/test/java/org/apache/archiva/components/graph/base/SimpleVertexTest.java
 
b/graph/src/test/java/org/apache/archiva/components/graph/base/SimpleVertexTest.java
new file mode 100644
index 0000000..e468e48
--- /dev/null
+++ 
b/graph/src/test/java/org/apache/archiva/components/graph/base/SimpleVertexTest.java
@@ -0,0 +1,112 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+import org.apache.archiva.components.graph.api.Graph;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SimpleVertexTest {
+
+    private Graph<SimpleVertex> graph = new SimpleGraph();
+
+    @Test
+
+    void getId() {
+        SimpleVertex vtx = new SimpleVertex(graph, "testid001");
+        assertNotNull(vtx.getId());
+        assertEquals("testid001", vtx.getId());
+    }
+
+    @Test
+    void getLabel() {
+        SimpleVertex vtx = new SimpleVertex(graph, "testid001");
+        assertNull(vtx.getLabel());
+        vtx.setLabel("test");
+        assertEquals("test", vtx.getLabel());
+
+        vtx = new SimpleVertex(graph, "testid001");
+        vtx.setLabel("Another label with more characters üy@~ ---");
+        assertNotNull(vtx.getLabel());
+        assertEquals("Another label with more characters üy@~ ---", 
vtx.getLabel());
+
+    }
+
+
+    @Test
+    void getGraph() {
+        SimpleVertex vtx = new SimpleVertex(graph, "test");
+        assertNotNull(vtx.getGraph());
+        assertTrue(vtx.getGraph() == graph);
+    }
+
+    @Test
+    void getInOutEdges() {
+        SimpleVertex vtx1 = new SimpleVertex(graph, "test1");
+        SimpleVertex vtx2 = new SimpleVertex(graph, "test2");
+        vtx1.addEdge(new BaseEdge<>(graph, "edge11_1to2", vtx1, vtx2));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge12_1to2", vtx1, vtx2));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge13_1to2", vtx1, vtx2));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge14_1to2", vtx1, vtx2));
+        vtx2.addEdge(new BaseEdge<>(graph, "edge21_1to2", vtx1, vtx2));
+        vtx2.addEdge(new BaseEdge<>(graph, "edge22_1to2", vtx1, vtx2));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge11_2to1", vtx2, vtx1));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge12_2to1", vtx2, vtx1));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge13_2to1", vtx2, vtx1));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge14_2to1", vtx2, vtx1));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge15_2to1", vtx2, vtx1));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge16_2to1", vtx2, vtx1));
+        vtx2.addEdge(new BaseEdge<>(graph, "edge21_2to1", vtx2, vtx1));
+        assertEquals(6, vtx1.getInEdges().size());
+        assertEquals(2, vtx2.getInEdges().size());
+        assertEquals(4, vtx1.getOutEdges().size());
+        assertEquals(1, vtx2.getOutEdges().size());
+    }
+
+
+
+    @Test
+    void removeEdge() {
+        SimpleVertex vtx1 = new SimpleVertex(graph, "test1");
+        SimpleVertex vtx2 = new SimpleVertex(graph, "test2");
+        vtx1.addEdge(new BaseEdge<>(graph, "edge11_1to2", vtx1, vtx2));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge12_1to2", vtx1, vtx2));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge13_1to2", vtx1, vtx2));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge14_1to2", vtx1, vtx2));
+        vtx2.addEdge(new BaseEdge<>(graph, "edge21_1to2", vtx1, vtx2));
+        vtx2.addEdge(new BaseEdge<>(graph, "edge22_1to2", vtx1, vtx2));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge11_2to1", vtx2, vtx1));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge12_2to1", vtx2, vtx1));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge13_2to1", vtx2, vtx1));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge14_2to1", vtx2, vtx1));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge15_2to1", vtx2, vtx1));
+        vtx1.addEdge(new BaseEdge<>(graph, "edge16_2to1", vtx2, vtx1));
+        vtx2.addEdge(new BaseEdge<>(graph, "edge21_2to1", vtx2, vtx1));
+        Edge<SimpleVertex> edge = vtx1.getOutEdges().get(0);
+        vtx1.removeEdge(edge);
+        edge = vtx1.getInEdges().get(0);
+        vtx1.removeEdge(edge);
+        assertEquals(5, vtx1.getInEdges().size());
+        assertEquals(2, vtx2.getInEdges().size());
+        assertEquals(3, vtx1.getOutEdges().size());
+        assertEquals(1, vtx2.getOutEdges().size());
+    }
+}
\ No newline at end of file
diff --git 
a/graph/src/test/java/org/apache/archiva/components/graph/util/TraversalTest.java
 
b/graph/src/test/java/org/apache/archiva/components/graph/util/TraversalTest.java
new file mode 100644
index 0000000..cbc26f1
--- /dev/null
+++ 
b/graph/src/test/java/org/apache/archiva/components/graph/util/TraversalTest.java
@@ -0,0 +1,505 @@
+package org.apache.archiva.components.graph.util;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.components.graph.api.TraversalStatus;
+import org.apache.archiva.components.graph.base.SimpleGraph;
+import org.apache.archiva.components.graph.base.SimpleVertex;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TraversalTest {
+
+    private static final Logger log = 
LoggerFactory.getLogger(TraversalTest.class);
+
+    @Test
+    void depthFirstWithoutCycleAndWithoutError() {
+        SimpleGraph graph = new SimpleGraph();
+        SimpleVertex vtx0 = graph.addVertex("root");
+        SimpleVertex vtx1 = graph.addVertex("vertex1");
+        SimpleVertex vtx11 = graph.addVertex("vertex11");
+        SimpleVertex vtx12 = graph.addVertex("vertex12");
+        SimpleVertex vtx2 = graph.addVertex("vertex2");
+        SimpleVertex vtx21 = graph.addVertex("vertex21");
+        SimpleVertex vtx211 = graph.addVertex("vertex211");
+        SimpleVertex vtx212 = graph.addVertex("vertex212");
+
+        SimpleVertex vtx22 = graph.addVertex("vertex22");
+        SimpleVertex vtx221 = graph.addVertex("vertex221");
+        SimpleVertex vtx222 = graph.addVertex("vertex222");
+        SimpleVertex vtx223 = graph.addVertex("vertex223");
+
+        SimpleVertex vtx3 = graph.addVertex("vertex3");
+        SimpleVertex vtx31 = graph.addVertex("vertex31");
+        SimpleVertex vtx32 = graph.addVertex("vertex32");
+        SimpleVertex vtx33 = graph.addVertex("vertex33");
+
+        graph.addEdge("root->1", vtx0, vtx1);
+        graph.addEdge("root->2", vtx0, vtx2);
+        graph.addEdge("root->3", vtx0, vtx3);
+
+        graph.addEdge("1->11", vtx1, vtx11);
+        graph.addEdge("1->12", vtx1, vtx12);
+
+        graph.addEdge("2->21", vtx2, vtx21);
+        graph.addEdge("2->22", vtx2, vtx22);
+
+        graph.addEdge("21->211", vtx21, vtx211);
+        graph.addEdge("21->212", vtx21, vtx212);
+
+        graph.addEdge("22->221", vtx22, vtx221);
+        graph.addEdge("22->222", vtx22, vtx222);
+        graph.addEdge("22->223", vtx22, vtx223);
+
+        graph.addEdge("3->31", vtx3, vtx31);
+        graph.addEdge("3->32", vtx3, vtx32);
+        graph.addEdge("3->33", vtx3, vtx33);
+
+        List<SimpleVertex> visitedNodes = new ArrayList<>();
+        TraversalStatus<SimpleVertex> status = Traversal.depthFirst(vtx0, v -> 
{
+            log.debug("Visiting vertex " + v);
+            if (visitedNodes.contains(v)) {
+                throw new RuntimeException("Double visit of vertex: " + v);
+            }
+            visitedNodes.add(v);
+            return true;
+        }, true);
+
+        assertEquals(vtx0, visitedNodes.get(0));
+        assertEquals(vtx1, visitedNodes.get(1));
+        assertEquals(vtx11, visitedNodes.get(2));
+        assertEquals(vtx12, visitedNodes.get(3));
+        assertEquals(vtx2, visitedNodes.get(4));
+        assertEquals(vtx21, visitedNodes.get(5));
+        assertEquals(vtx211, visitedNodes.get(6));
+        assertEquals(vtx212, visitedNodes.get(7));
+        assertEquals(vtx22, visitedNodes.get(8));
+        assertEquals(vtx221, visitedNodes.get(9));
+        assertEquals(vtx222, visitedNodes.get(10));
+        assertEquals(vtx223, visitedNodes.get(11));
+        assertEquals(vtx3, visitedNodes.get(12));
+        assertEquals(vtx31, visitedNodes.get(13));
+        assertEquals(vtx32, visitedNodes.get(14));
+        assertEquals(vtx33, visitedNodes.get(15));
+
+        assertFalse(status.hasCycles());
+        assertFalse(status.hasErrors());
+    }
+
+    @Test
+    void depthFirstWithCycleAndWithoutError() {
+        SimpleGraph graph = new SimpleGraph();
+        SimpleVertex vtx0 = graph.addVertex("root");
+        SimpleVertex vtx1 = graph.addVertex("vertex1");
+        SimpleVertex vtx11 = graph.addVertex("vertex11");
+        SimpleVertex vtx12 = graph.addVertex("vertex12");
+        SimpleVertex vtx2 = graph.addVertex("vertex2");
+        SimpleVertex vtx21 = graph.addVertex("vertex21");
+        SimpleVertex vtx211 = graph.addVertex("vertex211");
+        SimpleVertex vtx212 = graph.addVertex("vertex212");
+
+        SimpleVertex vtx22 = graph.addVertex("vertex22");
+        SimpleVertex vtx221 = graph.addVertex("vertex221");
+        SimpleVertex vtx222 = graph.addVertex("vertex222");
+        SimpleVertex vtx223 = graph.addVertex("vertex223");
+
+        SimpleVertex vtx3 = graph.addVertex("vertex3");
+        SimpleVertex vtx31 = graph.addVertex("vertex31");
+        SimpleVertex vtx32 = graph.addVertex("vertex32");
+        SimpleVertex vtx33 = graph.addVertex("vertex33");
+
+        graph.addEdge("root->1", vtx0, vtx1);
+        graph.addEdge("root->2", vtx0, vtx2);
+        graph.addEdge("root->3", vtx0, vtx3);
+
+        graph.addEdge("1->11", vtx1, vtx11);
+        graph.addEdge("1->12", vtx1, vtx12);
+
+        graph.addEdge("2->21", vtx2, vtx21);
+        graph.addEdge("2->22", vtx2, vtx22);
+        graph.addEdge("22->2", vtx22, vtx2);
+
+        graph.addEdge("21->211", vtx21, vtx211);
+        graph.addEdge("21->212", vtx21, vtx212);
+
+        graph.addEdge("22->221", vtx22, vtx221);
+        graph.addEdge("22->222", vtx22, vtx222);
+        graph.addEdge("22->223", vtx22, vtx223);
+        graph.addEdge("223->root", vtx223, vtx0);
+
+        graph.addEdge("3->31", vtx3, vtx31);
+        graph.addEdge("3->32", vtx3, vtx32);
+        graph.addEdge("3->33", vtx3, vtx33);
+
+        List<SimpleVertex> visitedNodes = new ArrayList<>();
+        TraversalStatus<SimpleVertex> status = Traversal.depthFirst(vtx0, v -> 
{
+            log.debug("Visiting vertex " + v);
+            if (visitedNodes.contains(v)) {
+                throw new RuntimeException("Double visit of vertex: " + v);
+            }
+            visitedNodes.add(v);
+            return true;
+        }, true);
+
+        assertEquals(vtx0, visitedNodes.get(0));
+        assertEquals(vtx1, visitedNodes.get(1));
+        assertEquals(vtx11, visitedNodes.get(2));
+        assertEquals(vtx12, visitedNodes.get(3));
+        assertEquals(vtx2, visitedNodes.get(4));
+        assertEquals(vtx21, visitedNodes.get(5));
+        assertEquals(vtx211, visitedNodes.get(6));
+        assertEquals(vtx212, visitedNodes.get(7));
+        assertEquals(vtx22, visitedNodes.get(8));
+        assertEquals(vtx221, visitedNodes.get(9));
+        assertEquals(vtx222, visitedNodes.get(10));
+        assertEquals(vtx223, visitedNodes.get(11));
+        assertEquals(vtx3, visitedNodes.get(12));
+        assertEquals(vtx31, visitedNodes.get(13));
+        assertEquals(vtx32, visitedNodes.get(14));
+        assertEquals(vtx33, visitedNodes.get(15));
+
+        assertTrue(status.hasCycles());
+        assertEquals(2, status.getCycleCount());
+        assertFalse(status.hasErrors());
+
+    }
+
+    @Test
+    void depthFirstWithCycleAndWithError() {
+        SimpleGraph graph = new SimpleGraph();
+        SimpleVertex vtx0 = graph.addVertex("root");
+        SimpleVertex vtx1 = graph.addVertex("vertex1");
+        SimpleVertex vtx11 = graph.addVertex("vertex11");
+        SimpleVertex vtx12 = graph.addVertex("vertex12");
+        SimpleVertex vtx2 = graph.addVertex("vertex2");
+        SimpleVertex vtx21 = graph.addVertex("vertex21");
+        SimpleVertex vtx211 = graph.addVertex("vertex211");
+        SimpleVertex vtx212 = graph.addVertex("vertex212");
+
+        SimpleVertex vtx22 = graph.addVertex("vertex22");
+        SimpleVertex vtx221 = graph.addVertex("vertex221");
+        SimpleVertex vtx222 = graph.addVertex("vertex222");
+        SimpleVertex vtx223 = graph.addVertex("vertex223");
+
+        SimpleVertex vtx3 = graph.addVertex("vertex3");
+        SimpleVertex vtx31 = graph.addVertex("vertex31");
+        SimpleVertex vtx32 = graph.addVertex("vertex32");
+        SimpleVertex vtx33 = graph.addVertex("vertex33");
+
+        graph.addEdge("root->1", vtx0, vtx1);
+        graph.addEdge("root->2", vtx0, vtx2);
+        graph.addEdge("root->3", vtx0, vtx3);
+
+        graph.addEdge("1->11", vtx1, vtx11);
+        graph.addEdge("1->12", vtx1, vtx12);
+
+        graph.addEdge("2->21", vtx2, vtx21);
+        graph.addEdge("2->22", vtx2, vtx22);
+        graph.addEdge("22->2", vtx22, vtx2);
+
+        graph.addEdge("21->211", vtx21, vtx211);
+        graph.addEdge("21->212", vtx21, vtx212);
+
+        graph.addEdge("22->221", vtx22, vtx221);
+        graph.addEdge("22->222", vtx22, vtx222);
+        graph.addEdge("22->223", vtx22, vtx223);
+        graph.addEdge("223->root", vtx223, vtx0);
+
+        graph.addEdge("3->31", vtx3, vtx31);
+        graph.addEdge("3->32", vtx3, vtx32);
+        graph.addEdge("3->33", vtx3, vtx33);
+
+        List<SimpleVertex> visitedNodes = new ArrayList<>();
+        TraversalStatus<SimpleVertex> status = Traversal.depthFirst(vtx0, v -> 
{
+            log.debug("Visiting vertex " + v);
+            if (visitedNodes.contains(v)) {
+                throw new RuntimeException("Double visit of vertex: " + v);
+            }
+            visitedNodes.add(v);
+            if (v == vtx223 || v == vtx33 || v == vtx212) {
+                throw new RuntimeException("Error for node " + v);
+            }
+            return true;
+        }, true);
+
+        assertEquals(vtx0, visitedNodes.get(0));
+        assertEquals(vtx1, visitedNodes.get(1));
+        assertEquals(vtx11, visitedNodes.get(2));
+        assertEquals(vtx12, visitedNodes.get(3));
+        assertEquals(vtx2, visitedNodes.get(4));
+        assertEquals(vtx21, visitedNodes.get(5));
+        assertEquals(vtx211, visitedNodes.get(6));
+        assertEquals(vtx212, visitedNodes.get(7));
+        assertEquals(vtx22, visitedNodes.get(8));
+        assertEquals(vtx221, visitedNodes.get(9));
+        assertEquals(vtx222, visitedNodes.get(10));
+        assertEquals(vtx223, visitedNodes.get(11));
+        assertEquals(vtx3, visitedNodes.get(12));
+        assertEquals(vtx31, visitedNodes.get(13));
+        assertEquals(vtx32, visitedNodes.get(14));
+        assertEquals(vtx33, visitedNodes.get(15));
+
+        assertTrue(status.hasCycles());
+        assertEquals(2, status.getCycleCount());
+        assertTrue(status.hasErrors());
+        assertEquals(3, status.getErrorList().size());
+        
assertTrue(status.getErrorList().get(0).getException().getMessage().startsWith("Error
 for node"));
+
+    }
+
+    @Test
+    void breadthFirstWithoutCyclesAndWithoutError() {
+        SimpleGraph graph = new SimpleGraph();
+        SimpleVertex vtx0 = graph.addVertex("root");
+        SimpleVertex vtx1 = graph.addVertex("vertex1");
+        SimpleVertex vtx11 = graph.addVertex("vertex11");
+        SimpleVertex vtx12 = graph.addVertex("vertex12");
+        SimpleVertex vtx2 = graph.addVertex("vertex2");
+        SimpleVertex vtx21 = graph.addVertex("vertex21");
+        SimpleVertex vtx211 = graph.addVertex("vertex211");
+        SimpleVertex vtx212 = graph.addVertex("vertex212");
+
+        SimpleVertex vtx22 = graph.addVertex("vertex22");
+        SimpleVertex vtx221 = graph.addVertex("vertex221");
+        SimpleVertex vtx222 = graph.addVertex("vertex222");
+        SimpleVertex vtx223 = graph.addVertex("vertex223");
+
+        SimpleVertex vtx3 = graph.addVertex("vertex3");
+        SimpleVertex vtx31 = graph.addVertex("vertex31");
+        SimpleVertex vtx32 = graph.addVertex("vertex32");
+        SimpleVertex vtx33 = graph.addVertex("vertex33");
+
+        graph.addEdge("root->1", vtx0, vtx1);
+        graph.addEdge("root->2", vtx0, vtx2);
+        graph.addEdge("root->3", vtx0, vtx3);
+
+        graph.addEdge("1->11", vtx1, vtx11);
+        graph.addEdge("1->12", vtx1, vtx12);
+
+        graph.addEdge("2->21", vtx2, vtx21);
+        graph.addEdge("2->22", vtx2, vtx22);
+
+        graph.addEdge("21->211", vtx21, vtx211);
+        graph.addEdge("21->212", vtx21, vtx212);
+
+        graph.addEdge("22->221", vtx22, vtx221);
+        graph.addEdge("22->222", vtx22, vtx222);
+        graph.addEdge("22->223", vtx22, vtx223);
+
+        graph.addEdge("3->31", vtx3, vtx31);
+        graph.addEdge("3->32", vtx3, vtx32);
+        graph.addEdge("3->33", vtx3, vtx33);
+
+        List<SimpleVertex> visitedNodes = new ArrayList<>();
+        TraversalStatus<SimpleVertex> status = Traversal.breadthFirst(vtx0, v 
-> {
+            log.debug("Visiting vertex " + v);
+            if (visitedNodes.contains(v)) {
+                throw new RuntimeException("Double visit of vertex: " + v);
+            }
+            visitedNodes.add(v);
+            return true;
+        }, true);
+
+        assertEquals(vtx0, visitedNodes.get(0));
+        assertEquals(vtx1, visitedNodes.get(1));
+        assertEquals(vtx2, visitedNodes.get(2));
+        assertEquals(vtx3, visitedNodes.get(3));
+        assertEquals(vtx11, visitedNodes.get(4));
+        assertEquals(vtx12, visitedNodes.get(5));
+        assertEquals(vtx21, visitedNodes.get(6));
+        assertEquals(vtx22, visitedNodes.get(7));
+        assertEquals(vtx31, visitedNodes.get(8));
+        assertEquals(vtx32, visitedNodes.get(9));
+        assertEquals(vtx33, visitedNodes.get(10));
+        assertEquals(vtx211, visitedNodes.get(11));
+        assertEquals(vtx212, visitedNodes.get(12));
+        assertEquals(vtx221, visitedNodes.get(13));
+        assertEquals(vtx222, visitedNodes.get(14));
+        assertEquals(vtx223, visitedNodes.get(15));
+
+        assertFalse(status.hasCycles());
+        assertFalse(status.hasErrors());
+    }
+
+    @Test
+    void breadthFirstWithCyclesAndWithoutError() {
+        SimpleGraph graph = new SimpleGraph();
+        SimpleVertex vtx0 = graph.addVertex("root");
+        SimpleVertex vtx1 = graph.addVertex("vertex1");
+        SimpleVertex vtx11 = graph.addVertex("vertex11");
+        SimpleVertex vtx12 = graph.addVertex("vertex12");
+        SimpleVertex vtx2 = graph.addVertex("vertex2");
+        SimpleVertex vtx21 = graph.addVertex("vertex21");
+        SimpleVertex vtx211 = graph.addVertex("vertex211");
+        SimpleVertex vtx212 = graph.addVertex("vertex212");
+
+        SimpleVertex vtx22 = graph.addVertex("vertex22");
+        SimpleVertex vtx221 = graph.addVertex("vertex221");
+        SimpleVertex vtx222 = graph.addVertex("vertex222");
+        SimpleVertex vtx223 = graph.addVertex("vertex223");
+
+        SimpleVertex vtx3 = graph.addVertex("vertex3");
+        SimpleVertex vtx31 = graph.addVertex("vertex31");
+        SimpleVertex vtx32 = graph.addVertex("vertex32");
+        SimpleVertex vtx33 = graph.addVertex("vertex33");
+
+        graph.addEdge("root->1", vtx0, vtx1);
+        graph.addEdge("root->2", vtx0, vtx2);
+        graph.addEdge("root->3", vtx0, vtx3);
+
+        graph.addEdge("1->11", vtx1, vtx11);
+        graph.addEdge("1->12", vtx1, vtx12);
+
+        graph.addEdge("2->21", vtx2, vtx21);
+        graph.addEdge("2->22", vtx2, vtx22);
+
+        graph.addEdge("21->211", vtx21, vtx211);
+        graph.addEdge("21->212", vtx21, vtx212);
+        graph.addEdge("211->21", vtx211, vtx21);
+
+        graph.addEdge("22->221", vtx22, vtx221);
+        graph.addEdge("22->222", vtx22, vtx222);
+        graph.addEdge("22->223", vtx22, vtx223);
+        graph.addEdge("223->root", vtx223, vtx0);
+
+        graph.addEdge("3->31", vtx3, vtx31);
+        graph.addEdge("3->32", vtx3, vtx32);
+        graph.addEdge("3->33", vtx3, vtx33);
+
+        List<SimpleVertex> visitedNodes = new ArrayList<>();
+        TraversalStatus<SimpleVertex> status = Traversal.breadthFirst(vtx0, v 
-> {
+            log.debug("Visiting vertex " + v);
+            if (visitedNodes.contains(v)) {
+                throw new RuntimeException("Double visit of vertex: " + v);
+            }
+            visitedNodes.add(v);
+            return true;
+        }, true);
+
+        assertEquals(vtx0, visitedNodes.get(0));
+        assertEquals(vtx1, visitedNodes.get(1));
+        assertEquals(vtx2, visitedNodes.get(2));
+        assertEquals(vtx3, visitedNodes.get(3));
+        assertEquals(vtx11, visitedNodes.get(4));
+        assertEquals(vtx12, visitedNodes.get(5));
+        assertEquals(vtx21, visitedNodes.get(6));
+        assertEquals(vtx22, visitedNodes.get(7));
+        assertEquals(vtx31, visitedNodes.get(8));
+        assertEquals(vtx32, visitedNodes.get(9));
+        assertEquals(vtx33, visitedNodes.get(10));
+        assertEquals(vtx211, visitedNodes.get(11));
+        assertEquals(vtx212, visitedNodes.get(12));
+        assertEquals(vtx221, visitedNodes.get(13));
+        assertEquals(vtx222, visitedNodes.get(14));
+        assertEquals(vtx223, visitedNodes.get(15));
+
+        assertTrue(status.hasCycles());
+        assertEquals(2, status.getCycleCount());
+        assertFalse(status.hasErrors());
+    }
+
+    @Test
+    void breadthFirstWithCyclesAndWithError() {
+        SimpleGraph graph = new SimpleGraph();
+        SimpleVertex vtx0 = graph.addVertex("root");
+        SimpleVertex vtx1 = graph.addVertex("vertex1");
+        SimpleVertex vtx11 = graph.addVertex("vertex11");
+        SimpleVertex vtx12 = graph.addVertex("vertex12");
+        SimpleVertex vtx2 = graph.addVertex("vertex2");
+        SimpleVertex vtx21 = graph.addVertex("vertex21");
+        SimpleVertex vtx211 = graph.addVertex("vertex211");
+        SimpleVertex vtx212 = graph.addVertex("vertex212");
+
+        SimpleVertex vtx22 = graph.addVertex("vertex22");
+        SimpleVertex vtx221 = graph.addVertex("vertex221");
+        SimpleVertex vtx222 = graph.addVertex("vertex222");
+        SimpleVertex vtx223 = graph.addVertex("vertex223");
+
+        SimpleVertex vtx3 = graph.addVertex("vertex3");
+        SimpleVertex vtx31 = graph.addVertex("vertex31");
+        SimpleVertex vtx32 = graph.addVertex("vertex32");
+        SimpleVertex vtx33 = graph.addVertex("vertex33");
+
+        graph.addEdge("root->1", vtx0, vtx1);
+        graph.addEdge("root->2", vtx0, vtx2);
+        graph.addEdge("root->3", vtx0, vtx3);
+
+        graph.addEdge("1->11", vtx1, vtx11);
+        graph.addEdge("1->12", vtx1, vtx12);
+
+        graph.addEdge("2->21", vtx2, vtx21);
+        graph.addEdge("2->22", vtx2, vtx22);
+
+        graph.addEdge("21->211", vtx21, vtx211);
+        graph.addEdge("21->212", vtx21, vtx212);
+        graph.addEdge("211->21", vtx211, vtx21);
+
+        graph.addEdge("22->221", vtx22, vtx221);
+        graph.addEdge("22->222", vtx22, vtx222);
+        graph.addEdge("22->223", vtx22, vtx223);
+        graph.addEdge("223->root", vtx223, vtx0);
+
+        graph.addEdge("3->31", vtx3, vtx31);
+        graph.addEdge("3->32", vtx3, vtx32);
+        graph.addEdge("3->33", vtx3, vtx33);
+
+        List<SimpleVertex> visitedNodes = new ArrayList<>();
+        TraversalStatus<SimpleVertex> status = Traversal.breadthFirst(vtx0, v 
-> {
+            log.debug("Visiting vertex " + v);
+            if (visitedNodes.contains(v)) {
+                throw new RuntimeException("Double visit of vertex: " + v);
+            }
+            visitedNodes.add(v);
+            if (v == vtx212 || v == vtx31 || v == vtx21) {
+                throw new RuntimeException("Error on node: " + v);
+            }
+            return true;
+        }, true);
+
+        assertEquals(vtx0, visitedNodes.get(0));
+        assertEquals(vtx1, visitedNodes.get(1));
+        assertEquals(vtx2, visitedNodes.get(2));
+        assertEquals(vtx3, visitedNodes.get(3));
+        assertEquals(vtx11, visitedNodes.get(4));
+        assertEquals(vtx12, visitedNodes.get(5));
+        assertEquals(vtx21, visitedNodes.get(6));
+        assertEquals(vtx22, visitedNodes.get(7));
+        assertEquals(vtx31, visitedNodes.get(8));
+        assertEquals(vtx32, visitedNodes.get(9));
+        assertEquals(vtx33, visitedNodes.get(10));
+        assertEquals(vtx211, visitedNodes.get(11));
+        assertEquals(vtx212, visitedNodes.get(12));
+        assertEquals(vtx221, visitedNodes.get(13));
+        assertEquals(vtx222, visitedNodes.get(14));
+        assertEquals(vtx223, visitedNodes.get(15));
+
+        assertTrue(status.hasCycles());
+        assertEquals(2, status.getCycleCount());
+        assertTrue(status.hasErrors());
+        assertEquals(3, status.getErrorList().size());
+        
assertTrue(status.getErrorList().get(0).getException().getMessage().startsWith("Error
 on node"));
+    }
+
+}
\ No newline at end of file
diff --git a/graph/src/test/resources/log4j2-test.xml 
b/graph/src/test/resources/log4j2-test.xml
new file mode 100644
index 0000000..4ed4e77
--- /dev/null
+++ b/graph/src/test/resources/log4j2-test.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<configuration>
+  <appenders>
+    <Console name="console" target="SYSTEM_OUT">
+      <PatternLayout pattern="[%t] %-5p %c %x - %m%n"/>
+    </Console>
+  </appenders>
+  <loggers>
+    <logger name="org.apache.archiva" level="info"/>
+    <logger name="org.apache.archiva.components.graph" level="info" />
+
+    <root level="error" includeLocation="true">
+      <appender-ref ref="console"/>
+    </root>
+  </loggers>
+</configuration>
+
+
diff --git a/pom.xml b/pom.xml
index a91b450..9ebe160 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,6 +47,7 @@
   <modules>
     <module>expression-evaluator</module>
     <module>spring-apacheds</module>
+    <module>graph</module>
   </modules> 
 
   <scm>

Reply via email to