package com.tinkerpop.blueprints.util.wrappers.id; import com.tinkerpop.blueprints.Edge; import com.tinkerpop.blueprints.Element; import com.tinkerpop.blueprints.Features; import com.tinkerpop.blueprints.GraphQuery; import com.tinkerpop.blueprints.Index; import com.tinkerpop.blueprints.IndexableGraph; import com.tinkerpop.blueprints.KeyIndexableGraph; import com.tinkerpop.blueprints.Parameter; import com.tinkerpop.blueprints.TransactionalGraph; import com.tinkerpop.blueprints.Vertex; import com.tinkerpop.blueprints.util.ExceptionFactory; import com.tinkerpop.blueprints.util.StringFactory; import com.tinkerpop.blueprints.util.wrappers.WrappedGraphQuery; import com.tinkerpop.blueprints.util.wrappers.WrapperGraph; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.UUID; import java.util.logging.Logger; /** * A Graph implementation which wraps another Graph implementation, * enabling custom element IDs even for those graphs which don't otherwise support them. * * The base Graph must be an instance of KeyIndexableGraph. * It *may* be an instance of IndexableGraph, in which case its indices will be wrapped appropriately. * It *may* be an instance of TransactionalGraph, in which case transaction operations will be passed through. * For those graphs which support vertex indices but not edge indices (or vice versa), * you may configure IdGraph to use custom IDs only for vertices or only for edges. * * @author Joshua Shinavier (http://fortytwo.net) */ public class IdGraph<T extends KeyIndexableGraph> implements KeyIndexableGraph, WrapperGraph<T>, IndexableGraph, TransactionalGraph { private static final Logger LOGGER = Logger.getLogger(IdGraph.class.getName()); // Note: using "__id" instead of "_id" avoids collision with Rexster's "_id" public static final String ID = "__id"; private final T baseGraph; private IdFactory vertexIdFactory; private IdFactory edgeIdFactory; private final Features features; private final boolean supportVertexIds, supportEdgeIds; private boolean uniqueIds = true; /** * Adds custom ID functionality to the given graph, * supporting both custom vertex IDs and custom edge IDs. * * @param baseGraph the base graph which does not necessarily support custom IDs */ public IdGraph(final T baseGraph) { this(baseGraph, true, true); } /** * Adds custom ID functionality to the given graph, * supporting either custom vertex IDs, custom edge IDs, or both. * * @param baseGraph the base graph which does not necessarily support custom IDs * @param supportVertexIds whether to support custom vertex IDs * @param supportEdgeIds whether to support custom edge IDs */ public IdGraph(final T baseGraph, final boolean supportVertexIds, final boolean supportEdgeIds) { this.baseGraph = baseGraph; this.features = this.baseGraph.getFeatures().copyFeatures(); features.isWrapper = true; features.ignoresSuppliedIds = false; this.supportVertexIds = supportVertexIds; this.supportEdgeIds = supportEdgeIds; if (!supportVertexIds && !supportEdgeIds) { throw new IllegalArgumentException("if neither custom vertex IDs nor custom edge IDs are supported, IdGraph can't help you!"); } createIndices(); vertexIdFactory = new DefaultIdFactory(); edgeIdFactory = new DefaultIdFactory(); } /** * @param idFactory a factory for new vertex IDs. * When vertices are created using null IDs, the actual IDs are chosen based on this factory. */ public void setVertexIdFactory(final IdFactory idFactory) { vertexIdFactory = idFactory; } /** * @param idFactory a factory for new edge IDs. * When edges are created using null IDs, the actual IDs are chosen based on this factory. */ public void setEdgeIdFactory(final IdFactory idFactory) { edgeIdFactory = idFactory; } /** * @return the factory for new vertex IDs. * When vertices are created using null IDs, the actual IDs are chosen based on this factory. */ public IdFactory getVertexIdFactory() { return vertexIdFactory; } /** * return the factory for new edge IDs. * When edges are created using null IDs, the actual IDs are chosen based on this factory. */ public IdFactory getEdgeIdFactory() { return edgeIdFactory; } public Features getFeatures() { return features; } public Vertex addVertex(final Object id) { if (uniqueIds && null != id && null != getVertex(id)) { throw new IllegalArgumentException("vertex with given id already exists: '" + id + "'"); } final Vertex base = baseGraph.addVertex(null); if (supportVertexIds) { Object v = null == id ? vertexIdFactory.createId() : id; if (null != v) { base.setProperty(ID, v); } } return new IdVertex(base, this); } public Vertex getVertex(final Object id) { if (null == id) { throw new IllegalArgumentException("vertex identifier cannot be null"); } if (supportVertexIds) { final Iterable<Vertex> i = baseGraph.getVertices(ID, id); final Iterator<Vertex> iter = i.iterator(); if (!iter.hasNext()) { return null; } else { Vertex v = iter.next(); if (iter.hasNext()) { LOGGER.warning("multiple vertices exist with id '" + id + "'. Arbitarily choosing " + v); } return new IdVertex(v, this); } } else { Vertex base = baseGraph.getVertex(id); return null == base ? null : new IdVertex(base, this); } } public void removeVertex(final Vertex vertex) { verifyNativeElement(vertex); baseGraph.removeVertex(((IdVertex) vertex).getBaseVertex()); } public Iterable<Vertex> getVertices() { return new IdVertexIterable(baseGraph.getVertices(), this); } public Iterable<Vertex> getVertices(final String key, final Object value) { if (supportVertexIds && key.equals(ID)) { throw new IllegalArgumentException("index key " + ID + " is reserved by IdGraph"); } else { return new IdVertexIterable(baseGraph.getVertices(key, value), this); } } public Edge addEdge(final Object id, final Vertex outVertex, final Vertex inVertex, final String label) { if (uniqueIds && null != id && null != getEdge(id)) { throw new IllegalArgumentException("edge with given id already exists: " + id); } verifyNativeElement(outVertex); verifyNativeElement(inVertex); Edge base = baseGraph.addEdge(null, ((IdVertex) outVertex).getBaseVertex(), ((IdVertex) inVertex).getBaseVertex(), label); if (supportEdgeIds) { Object v = null == id ? edgeIdFactory.createId() : id; if (null != v) { base.setProperty(ID, v); } } return new IdEdge(base, this); } public Edge getEdge(final Object id) { if (null == id) { throw new IllegalArgumentException("edge identifier cannot be null"); } if (supportEdgeIds) { Iterable<Edge> i = baseGraph.getEdges(ID, id); Iterator<Edge> iter = i.iterator(); if (!iter.hasNext()) { return null; } else { Edge e = iter.next(); if (iter.hasNext()) { LOGGER.warning("multiple edges exist with id '" + id + "'. Arbitarily choosing " + e); } return new IdEdge(e, this); } } else { Edge base = baseGraph.getEdge(id); return null == base ? null : new IdEdge(base, this); } } public void removeEdge(final Edge edge) { verifyNativeElement(edge); baseGraph.removeEdge(((IdEdge) edge).getBaseEdge()); } public Iterable<Edge> getEdges() { return new IdEdgeIterable(baseGraph.getEdges(), this); } public Iterable<Edge> getEdges(final String key, final Object value) { if (supportEdgeIds && key.equals(ID)) { throw new IllegalArgumentException("index key " + ID + " is reserved by IdGraph"); } else { return new IdEdgeIterable(baseGraph.getEdges(key, value), this); } } // Note: this is a no-op if the base graph is not an instance of TransactionalGraph public void stopTransaction(Conclusion conclusion) { if (Conclusion.SUCCESS == conclusion) commit(); else rollback(); } public void rollback() { if (this.baseGraph instanceof TransactionalGraph) { ((TransactionalGraph) baseGraph).rollback(); } } public void commit() { if (this.baseGraph instanceof TransactionalGraph) { ((TransactionalGraph) baseGraph).commit(); } } public void shutdown() { baseGraph.shutdown(); } public String toString() { return StringFactory.graphString(this, this.baseGraph.toString()); } public <T extends Element> void dropKeyIndex(final String key, final Class<T> elementClass) { if (elementClass == null) throw ExceptionFactory.classForElementCannotBeNull(); boolean v = isVertexClass(elementClass); boolean supported = ((v && supportVertexIds) || (!v && supportEdgeIds)); if (supported && key.equals(ID)) { throw new IllegalArgumentException("index key " + ID + " is reserved by IdGraph"); } else { baseGraph.dropKeyIndex(key, elementClass); } } public <T extends Element> void createKeyIndex(final String key, final Class<T> elementClass, final Parameter... indexParameters) { if (elementClass == null) throw ExceptionFactory.classForElementCannotBeNull(); boolean v = isVertexClass(elementClass); boolean supported = ((v && supportVertexIds) || (!v && supportEdgeIds)); if (supported && key.equals(ID)) { throw new IllegalArgumentException("index key " + ID + " is reserved by IdGraph"); } else { baseGraph.createKeyIndex(key, elementClass, indexParameters); } } public <T extends Element> Set<String> getIndexedKeys(final Class<T> elementClass) { if (elementClass == null) throw ExceptionFactory.classForElementCannotBeNull(); boolean v = isVertexClass(elementClass); boolean supported = ((v && supportVertexIds) || (!v && supportEdgeIds)); if (supported) { Set<String> keys = new HashSet<String>(); keys.addAll(baseGraph.getIndexedKeys(elementClass)); keys.remove(ID); return keys; } else { return baseGraph.getIndexedKeys(elementClass); } } public T getBaseGraph() { return this.baseGraph; } public void enforceUniqueIds(boolean enforceUniqueIds) { this.uniqueIds = enforceUniqueIds; } public <T extends Element> Index<T> createIndex(final String indexName, final Class<T> indexClass, final Parameter... indexParameters) { verifyBaseGraphIsIndexableGraph(); return isVertexClass(indexClass) ? (Index<T>) new IdVertexIndex((Index<Vertex>) ((IndexableGraph) baseGraph).createIndex(indexName, indexClass, indexParameters), this) : (Index<T>) new IdEdgeIndex((Index<Edge>) ((IndexableGraph) baseGraph).createIndex(indexName, indexClass, indexParameters), this); } public <T extends Element> Index<T> getIndex(final String indexName, final Class<T> indexClass) { verifyBaseGraphIsIndexableGraph(); if (isVertexClass(indexClass)) { Index<Vertex> baseIndex = (Index<Vertex>) ((IndexableGraph) baseGraph).getIndex(indexName, indexClass); return null == baseIndex ? null : (Index<T>) new IdVertexIndex(baseIndex, this); } else { Index<Edge> baseIndex = (Index<Edge>) ((IndexableGraph) baseGraph).getIndex(indexName, indexClass); return null == baseIndex ? null : (Index<T>) new IdEdgeIndex(baseIndex, this); } } public Iterable<Index<? extends Element>> getIndices() { throw new UnsupportedOperationException("sorry, you currently can't get a list of indexes through IdGraph"); } public void dropIndex(final String indexName) { verifyBaseGraphIsIndexableGraph(); ((IndexableGraph) baseGraph).dropIndex(indexName); } public GraphQuery query() { final IdGraph idGraph = this; return new WrappedGraphQuery(this.baseGraph.query()) { @Override public Iterable<Edge> edges() { return new IdEdgeIterable(this.query.edges(), idGraph); } @Override public Iterable<Vertex> vertices() { return new IdVertexIterable(this.query.vertices(), idGraph); } }; } public boolean getSupportVertexIds() { return supportVertexIds; } public boolean getSupportEdgeIds() { return supportEdgeIds; } /** * A factory for IDs of newly-created vertices and edges (where an ID is not otherwise specified). */ public static interface IdFactory { Object createId(); } private static class DefaultIdFactory implements IdFactory { public Object createId() { return UUID.randomUUID().toString(); } } private void verifyBaseGraphIsIndexableGraph() { if (!(baseGraph instanceof IndexableGraph)) { throw new IllegalStateException("base graph is not an indexable graph"); } } private boolean isVertexClass(final Class c) { return Vertex.class.isAssignableFrom(c); } private void createIndices() { if (supportVertexIds && !baseGraph.getIndexedKeys(Vertex.class).contains(ID)) { baseGraph.createKeyIndex(ID, Vertex.class); } if (supportEdgeIds && !baseGraph.getIndexedKeys(Edge.class).contains(ID)) { baseGraph.createKeyIndex(ID, Edge.class); } } private static void verifyNativeElement(final Element e) { if (!(e instanceof IdElement)) { throw new IllegalArgumentException("given element was not created in this graph"); } } }