package dgm.graphs; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.tinkerpop.blueprints.Direction; import com.tinkerpop.blueprints.Edge; import com.tinkerpop.blueprints.TransactionalGraph; import com.tinkerpop.blueprints.Vertex; import dgm.*; import dgm.exceptions.DegraphmalizerException; import dgm.trees.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import static com.tinkerpop.blueprints.TransactionalGraph.Conclusion.FAILURE; import static com.tinkerpop.blueprints.TransactionalGraph.Conclusion.SUCCESS; import static dgm.GraphUtilities.*; public class BlueprintsSubgraphManager implements SubgraphManager { private final Logger log = LoggerFactory.getLogger(BlueprintsSubgraphManager.class); private final ObjectMapper om; private final TransactionalGraph graph; public BlueprintsSubgraphManager(ObjectMapper om, TransactionalGraph graph) { this.graph = graph; this.om = om; } @Override public final void commitSubgraph(ID id, Subgraph sg) throws DegraphmalizerException { if(id.version() == 0) throw new IllegalArgumentException("Subgraph must have version > 0"); if(detectSelfLoops(id, sg)) throw new IllegalArgumentException("Cannot have self-loops in subgraph (ie. some of your edges point to the id your are committing this Subgraph under.)"); if(detectNonSymbolicTargets(sg)) throw new IllegalArgumentException("All edges must link to an identified with version==0"); boolean success = false; try { // create a list of all elements owned by any version of this subgraph final Pair<List<Vertex>, List<Edge>> elementsToDelete = findOwnedElements(id); List<Vertex> verticesToDelete = elementsToDelete.a; List<Edge> edgesToDelete = elementsToDelete.b; // do stuff needed for central vertex... final Vertex center = createOrUpdateCentralVertex(id, sg); // ...and for the edges final Pair<List<Vertex>, List<Edge>> nextVersionElts = createOrUpdateEdges(id, sg); // now make sure everything we touched is not deleted verticesToDelete.remove(center); verticesToDelete.removeAll(nextVersionElts.a); edgesToDelete.removeAll(nextVersionElts.b); List<Vertex> danglingVertices = findDanglingVertices(id, edgesToDelete); verticesToDelete.addAll(danglingVertices); removeGraphElements(id, verticesToDelete, edgesToDelete); // commit changes to graph success = true; graph.stopTransaction(TransactionalGraph.Conclusion.SUCCESS); } finally { // rollback if something failed if(! success) graph.stopTransaction(TransactionalGraph.Conclusion.FAILURE); } } // TODO it is probably better to ignore all versions in a subgraph (ie. call getSymbolic on all edges.other()) private boolean detectNonSymbolicTargets(Subgraph sg) { for(Subgraph.Edge e : sg.edges()) if(e.other().version() != 0) return true; return false; } private boolean detectSelfLoops(ID center, Subgraph sg) { for(Subgraph.Edge e : sg.edges()) if(onlyVersionDiffers(center, e.other())) return true; return false; } public List<ID> findVertexIDsAffectedByDelete(final ID id) { final Pair<List<Vertex>, List<Edge>> elementsToDelete = findOwnedElements(id); List<Vertex> verticesToDelete = elementsToDelete.a; List<Edge> edgesToDelete = elementsToDelete.b; List<Vertex> danglingVertices = findDanglingVertices(id, edgesToDelete); verticesToDelete.addAll(danglingVertices); return ImmutableList.copyOf(Lists.transform(verticesToDelete, new Function<Vertex, ID>() { @Override public ID apply(Vertex input) { return GraphUtilities.getID(om, input); } })); } @Override public void deleteSubgraph(final ID id) throws DegraphmalizerException { boolean success = false; try { // create a list of all elements owned by any version of this subgraph final Pair<List<Vertex>, List<Edge>> elementsToDelete = findOwnedElements(id); List<Vertex> verticesToDelete = elementsToDelete.a; List<Edge> edgesToDelete = elementsToDelete.b; List<Vertex> danglingVertices = findDanglingVertices(id, edgesToDelete); verticesToDelete.addAll(danglingVertices); removeGraphElements(id, verticesToDelete, edgesToDelete); // commit changes to graph success = true; } finally { // commit or rollback if something failed graph.stopTransaction(success ? SUCCESS : FAILURE); } } private List<Vertex> findDanglingVertices(ID id, List<Edge> edgesToDelete) { final List<Vertex> danglingVertices = new ArrayList<Vertex>(); // handle the edges we can delete: We check if they connect vertices that can be deleted too. for(Edge e: edgesToDelete) { // it is possible that a vertex turned symbolic and we are the last edge pointing to it, then remove it final Vertex v = e.getVertex(directionOppositeTo(getEdgeID(om, e), id)); if(canDeleteVertex(v, id, edgesToDelete)) danglingVertices.add(v); } return danglingVertices; } private void removeGraphElements(ID id, List<Vertex> verticesToDelete, List<Edge> edgesToDelete) { // Remove the edges for (Edge e: edgesToDelete) graph.removeEdge(e); // Remove the vertices for (final Vertex v: verticesToDelete) { if (canDeleteVertex(v, id, edgesToDelete)) graph.removeVertex(v); else GraphUtilities.makeSymbolic(om, v); } } private Pair<List<Vertex>, List<Edge>> findOwnedElements(ID id) { // create a list of all elements owned by any version of this subgraph final List<Vertex> vertexList = new ArrayList<Vertex>(); for (Vertex v : findOwnedVertices(om, graph, id)) vertexList.add(v); final List<Edge> edgeList = new ArrayList<Edge>(); for (Edge e : findOwnedEdges(om, graph, id)) edgeList.add(e); return new Pair<List<Vertex>, List<Edge>>(vertexList, edgeList); } /** * A vertex can only be deleted if all edges pointed to it are owned by us and are about to be deleted also. * Unless the vertex is not symbolic */ private boolean canDeleteVertex(Vertex v, ID owner, List<Edge> edgesToDelete) { for(Edge e: v.getEdges(Direction.BOTH)) { // if there is one edge pointing to this vertex that isn't ours, we must keep the vertex if(!onlyVersionDiffers(getOwner(om, e), owner)) return false; // and all the other edges must be marked for deletion too! if(!edgesToDelete.contains(e)) return false; //if this vertex is not symbolic and not owned by this subgraph, we don't delete it. if(! isSymbolic(om, v)) return false; } // only then can this vertex be removed return true; } private Vertex createOrUpdateCentralVertex(ID id, Subgraph sg) throws DegraphmalizerException { // find vertex, doesn't care about version Vertex center = resolveVertex(om, graph, id); if (center == null) { log.trace("Couldn't find central vertex with id {}, create new vertex", id); center = createVertex(om, graph, id); log.trace("Created central vertex"); } // it is actually fine to commit an older version, version management is completely done by ES if (log.isWarnEnabled()) { final ID cid = getID(om, center); if (isOlder(id, cid)) log.warn("Commit version < current version", id, cid); } setProperties(center, sg.properties()); //if the 'center' vertex has edges, then the edge id should be updated. updateEdgeIds(center, id); // update the identifier (to the latest version) setID(om, center, id); setOwner(om, center, id); return center; } private void updateEdgeIds(Vertex center, ID id) throws DegraphmalizerException { for(Edge edge: center.getEdges(Direction.BOTH)) setEdgeId(om, updateEdgeId(edge, id), edge); } private EdgeID updateEdgeId(Edge edge, ID id) throws DegraphmalizerException { final EdgeID edgeID = getEdgeID(om, edge); if (onlyVersionDiffers(edgeID.head(), id)) return new EdgeID(edgeID.tail(), edgeID.label(), id); if (onlyVersionDiffers(edgeID.tail(), id)) return new EdgeID(id, edgeID.label(), edgeID.head()); throw new IllegalArgumentException("id:" + id + " is not part of edge id:" + edgeID); } /** * This method iterates over the edges declared in the subgraph. * if The edge does not exist in the graph yet, create the target (symbolic) Vertex and the edge. * If it does exist check if the existing is owned by this subgraph. if not so: Error Error Error!!! * If so: update the properties of that edge. * @return A pair of lists that contain all Edges and All vertices that will be part of the new subgraph. */ private Pair<List<Vertex>, List<Edge>> createOrUpdateEdges(ID id, Subgraph sg) { List<Vertex> vertexList = new ArrayList<Vertex>(); List<Edge> edgeList = new ArrayList<Edge>(); for (Subgraph.Edge e : sg.edges()) { final EdgeID edgeId = Subgraphs.edgeID(id, e); Edge edge = findEdge(om, graph, edgeId); if (edge != null) //check if this edge belongs to this subgraph. edgeConsistencyCheck(id, edgeId, edge); else { final Pair<Edge, Vertex> pair = createEdgeAndVertex(id, edgeId); vertexList.add(pair.b); edge = pair.a; } // claim edge setOwner(om, edge, id); setProperties(edge, e.properties()); edgeList.add(edge); } return new Pair<List<Vertex>, List<Edge>>(vertexList, edgeList); } private Pair<Edge, Vertex> createEdgeAndVertex(ID centralVertex, EdgeID edgeId) { final ID other = getOppositeId(edgeId, centralVertex); // we either resolve the symbolic vertex, or create one to represent it Vertex v = resolveVertex(om, graph, other); if (v == null) v = createVertex(om, graph, other); final Edge edge = createEdge(om, graph, createOppositeId(edgeId, centralVertex, getID(om, v))); return new Pair<Edge,Vertex>(edge, v); } /** * Check if a given edge belongs to this subgraph * * TODO: if we don't bother with the version component of the owner we can skip all this. */ private void edgeConsistencyCheck(ID centralVertex, EdgeID edgeId, Edge edge) { final ID owner = getOwner(om, edge); if (!onlyVersionDiffers(owner, centralVertex)) throw new RuntimeException("Edge " + edgeId + " is already owned by " + owner); if (centralVertex.version() < owner.version()) throw new RuntimeException("Committing an older version of a subgraph is not allowed (old = " + owner + ", new = " + centralVertex + ")"); } }