├── .gitignore ├── README.md ├── src ├── assembly │ ├── distribution.xml │ └── standalone.xml ├── main │ └── java │ │ └── com │ │ ├── tinkerpop │ │ ├── blueprints │ │ │ ├── TimeAwareFilter.java │ │ │ ├── TimeAwareEdge.java │ │ │ ├── TimeAwareVertex.java │ │ │ ├── WorkingSet.java │ │ │ ├── TimeAwareGraph.java │ │ │ └── TimeAwareElement.java │ │ └── rexster │ │ │ └── config │ │ │ └── FluxGraphConfiguration.java │ │ └── jnj │ │ └── fluxgraph │ │ ├── FluxTimeIterable.java │ │ ├── FluxIterable.java │ │ ├── FluxEdge.java │ │ ├── ImmutableFluxGraph.java │ │ ├── FluxIndex.java │ │ ├── FluxVertex.java │ │ ├── FluxElement.java │ │ ├── FluxUtil.java │ │ └── FluxGraph.java └── test │ └── java │ └── com │ └── jnj │ └── fluxgraph │ ├── FluxBenchmarkTestSuite.java │ └── FluxGraphTest.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fluxgraph 2 | ========= 3 | 4 | A temporal graph database on top of Datomic -------------------------------------------------------------------------------- /src/assembly/distribution.xml: -------------------------------------------------------------------------------- 1 | 2 | distribution 3 | 4 | zip 5 | 6 | 7 | 8 | src 9 | 10 | 11 | target/${project.artifactId}-${project.version}-standalone/lib 12 | lib 13 | 14 | 15 | 16 | 17 | pom.xml 18 | src 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/com/tinkerpop/blueprints/TimeAwareFilter.java: -------------------------------------------------------------------------------- 1 | package com.tinkerpop.blueprints; 2 | 3 | /** 4 | * Filters for time aware elements 5 | * 6 | * @author Davy Suvee (http://datablend.be) 7 | */ 8 | public interface TimeAwareFilter { 9 | 10 | /** 11 | * Returns the time aware element if a particular condition, implemented by the filter holds. 12 | * 13 | * @param timeAwareElement the time aware element to filter 14 | * @return the original input or null in case the element does not satisfy the particular condition 15 | */ 16 | public TimeAwareElement filter(TimeAwareElement timeAwareElement); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/tinkerpop/blueprints/TimeAwareEdge.java: -------------------------------------------------------------------------------- 1 | package com.tinkerpop.blueprints; 2 | 3 | /** 4 | * @author Davy Suvee (http://datablend.be) 5 | */ 6 | public interface TimeAwareEdge extends Edge, TimeAwareElement { 7 | 8 | @Override 9 | public TimeAwareEdge getPreviousVersion(); 10 | 11 | @Override 12 | public Iterable getPreviousVersions(); 13 | 14 | @Override 15 | public Iterable getPreviousVersions(TimeAwareFilter timeAwareFilter); 16 | 17 | @Override 18 | public TimeAwareEdge getNextVersion(); 19 | 20 | @Override 21 | public Iterable getNextVersions(); 22 | 23 | @Override 24 | public Iterable getNextVersions(TimeAwareFilter timeAwareFilter); 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/tinkerpop/blueprints/TimeAwareVertex.java: -------------------------------------------------------------------------------- 1 | package com.tinkerpop.blueprints; 2 | 3 | /** 4 | * @author Davy Suvee (http://datablend.be) 5 | */ 6 | public interface TimeAwareVertex extends Vertex, TimeAwareElement { 7 | 8 | @Override 9 | public TimeAwareVertex getPreviousVersion(); 10 | 11 | @Override 12 | public Iterable getPreviousVersions(); 13 | 14 | @Override 15 | public Iterable getPreviousVersions(TimeAwareFilter timeAwareFilter); 16 | 17 | @Override 18 | public TimeAwareVertex getNextVersion(); 19 | 20 | @Override 21 | public Iterable getNextVersions(); 22 | 23 | @Override 24 | public Iterable getNextVersions(TimeAwareFilter timeAwareFilter); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/assembly/standalone.xml: -------------------------------------------------------------------------------- 1 | 2 | standalone 3 | 4 | dir 5 | 6 | false 7 | 8 | 9 | 10 | target/*.jar 11 | /lib 12 | 13 | 14 | 15 | 16 | 17 | /lib 18 | false 19 | compile 20 | 21 | 22 | /lib 23 | false 24 | provided 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/com/tinkerpop/blueprints/WorkingSet.java: -------------------------------------------------------------------------------- 1 | package com.tinkerpop.blueprints; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | /** 7 | * Container used to defined the working set of vertices and edges 8 | * @author Davy Suvee (http://datablend.be) 9 | */ 10 | public class WorkingSet { 11 | 12 | public Set vertexIdList = new HashSet(); 13 | public Set edgeIdList = new HashSet(); 14 | 15 | public WorkingSet() { 16 | } 17 | 18 | public void addVertex(Vertex vertex) { 19 | vertexIdList.add(vertex.getId()); 20 | } 21 | 22 | public void addEdge(Edge edge) { 23 | edgeIdList.add(edge.getId()); 24 | } 25 | 26 | public Iterable getVertices() { 27 | return vertexIdList; 28 | } 29 | 30 | public Iterable getEdges() { 31 | return edgeIdList; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/tinkerpop/rexster/config/FluxGraphConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.tinkerpop.rexster.config; 2 | 3 | import com.jnj.fluxgraph.FluxGraph; 4 | import com.tinkerpop.blueprints.Graph; 5 | import com.tinkerpop.rexster.Tokens; 6 | import org.apache.commons.configuration.Configuration; 7 | 8 | /** 9 | * Rexster configuration for FluxGraph. Accepts configuration in rexster.xml as follows: 10 | * 11 | * 12 | * 13 | * fluxgraphexample 14 | * com.tinkerpop.rexster.config.FluxGraphConfiguration 15 | * datomic:free://localhost:4334/flux 16 | * 17 | * 18 | * 19 | * To deploy copy the FluxGraph jar (with dependencies) to the Rexster ext directory. Ensure that the FluxGraph 20 | * is running. 21 | * 22 | * @author Stephen Mallette (http://stephen.genoprime.com) 23 | */ 24 | public class FluxGraphConfiguration implements GraphConfiguration { 25 | 26 | @Override 27 | public Graph configureGraphInstance(Configuration properties) throws GraphConfigurationException { 28 | final String graphFile = properties.getString(Tokens.REXSTER_GRAPH_LOCATION); 29 | 30 | if (graphFile == null || graphFile.length() == 0) { 31 | throw new GraphConfigurationException("Check graph configuration. Missing or empty configuration element: " + Tokens.REXSTER_GRAPH_LOCATION); 32 | } 33 | 34 | try { 35 | 36 | return new FluxGraph(graphFile); 37 | 38 | } catch (Exception ex) { 39 | throw new GraphConfigurationException(ex); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/tinkerpop/blueprints/TimeAwareGraph.java: -------------------------------------------------------------------------------- 1 | package com.tinkerpop.blueprints; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * A time aware graph supports an explicit notion of time for all graph elements (vertices and edges). Each element has a specific scope in time and allows for time-scoped iteration 7 | * where a user can retrieve previous or next versions of each element 8 | * 9 | * @author Davy Suvee (http://datablend.be) 10 | */ 11 | public interface TimeAwareGraph extends Graph { 12 | 13 | /** 14 | * Sets the time scope of the graph on a specific date 15 | * @param time the time at which the scope should be placed 16 | */ 17 | public void setCheckpointTime(Date time); 18 | 19 | /** 20 | * Sets the time scope at which new elements (vertices and edges) need to be added 21 | * @param time the time at which the transaction scope should be placed 22 | */ 23 | public void setTransactionTime(Date time); 24 | 25 | /** 26 | * Calculates the difference for a particular working set of vertices and edges at two different points in time 27 | * @param workingSet the working set of vertices and edges to calculate the difference for 28 | * @param date1 the first point in time 29 | * @param date2 the second point in time 30 | * @return an (immutable) graph representing the difference in working set 31 | */ 32 | public Graph difference(WorkingSet workingSet, Date date1, Date date2); 33 | 34 | /** 35 | * Calculates the difference for a particular graph element (vertex or edge) 36 | * Difference is only calculated for the elements with the same id. In case the their id's differs, an exception should be thrown 37 | * @param element1 the first element 38 | * @param element2 the second element 39 | * @return an (immutable) graph representing the difference between the two graph elements 40 | */ 41 | public Graph difference(TimeAwareElement element1, TimeAwareElement element2); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/jnj/fluxgraph/FluxBenchmarkTestSuite.java: -------------------------------------------------------------------------------- 1 | package com.jnj.fluxgraph; 2 | 3 | import com.tinkerpop.blueprints.*; 4 | import com.tinkerpop.blueprints.impls.GraphTest; 5 | import com.tinkerpop.blueprints.util.io.graphml.GraphMLReader; 6 | 7 | /** 8 | * @author Davy Suvee (http://datablend.be) 9 | */ 10 | public class FluxBenchmarkTestSuite extends TestSuite { 11 | 12 | private static final int TOTAL_RUNS = 10; 13 | 14 | public FluxBenchmarkTestSuite() { 15 | } 16 | 17 | public FluxBenchmarkTestSuite(final GraphTest graphTest) { 18 | super(graphTest); 19 | } 20 | 21 | public void testDatomicGraph() throws Exception { 22 | double totalTime = 0.0d; 23 | Graph graph = graphTest.generateGraph(); 24 | GraphMLReader.inputGraph(graph, GraphMLReader.class.getResourceAsStream("graph-example-2.xml")); 25 | graph.shutdown(); 26 | 27 | for (int i = 0; i < TOTAL_RUNS; i++) { 28 | graph = graphTest.generateGraph(); 29 | this.stopWatch(); 30 | int counter = 0; 31 | CloseableIterable vv = (CloseableIterable) graph.getVertices(); 32 | for (final Vertex vertex : vv) { 33 | counter++; 34 | CloseableIterable ee = (CloseableIterable) vertex.getEdges(Direction.OUT); 35 | for (final Edge edge : ee) { 36 | counter++; 37 | final Vertex vertex2 = edge.getVertex(Direction.IN); 38 | counter++; 39 | CloseableIterable ee2 = (CloseableIterable) vertex2.getEdges(Direction.OUT); 40 | for (final Edge edge2 : ee2) { 41 | counter++; 42 | final Vertex vertex3 = edge2.getVertex(Direction.IN); 43 | counter++; 44 | CloseableIterable ee3 = (CloseableIterable) vertex3.getEdges(Direction.OUT); 45 | for (final Edge edge3 : ee3) { 46 | counter++; 47 | edge3.getVertex(Direction.OUT); 48 | counter++; 49 | } 50 | ee3.close(); 51 | } 52 | ee2.close(); 53 | } 54 | ee.close(); 55 | } 56 | vv.close(); 57 | double currentTime = this.stopWatch(); 58 | totalTime = totalTime + currentTime; 59 | BaseTest.printPerformance(graph.toString(), counter, "FluxGraph elements touched (run=" + i + ")", currentTime); 60 | graph.shutdown(); 61 | } 62 | BaseTest.printPerformance("FluxGraph", 1, "FluxGraph experiment average", totalTime / (double) TOTAL_RUNS); 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/com/tinkerpop/blueprints/TimeAwareElement.java: -------------------------------------------------------------------------------- 1 | package com.tinkerpop.blueprints; 2 | 3 | import org.joda.time.Interval; 4 | 5 | /** 6 | * A TimeAwareElement is the base interface for time-aware elements (i.e. time-aware vertices and edges). 7 | * It extends the base blueprints Element interface with time-based operations 8 | * 9 | * @author Davy Suvee (http://datablend.be) 10 | */ 11 | public interface TimeAwareElement extends Element { 12 | 13 | /** 14 | * An identifier that specifies the time-scope in which it exists 15 | * 16 | * @return the time identifier of the element 17 | */ 18 | public Object getTimeId(); 19 | 20 | /** 21 | * Returns true if this element instance is the current version 22 | * 23 | * @return true if this element instance is the current version 24 | */ 25 | public boolean isCurrentVersion(); 26 | 27 | /** 28 | * Returns true if this element no longer exists (it is still the current version, but no longer an active element in the graph) 29 | * 30 | * @return true if this element instance is deleted 31 | */ 32 | public boolean isDeleted(); 33 | 34 | /** 35 | * Returns the previous version of this element 36 | * 37 | * @return the previous (time-aware) version of this element 38 | */ 39 | public TimeAwareElement getPreviousVersion(); 40 | 41 | /** 42 | * Returns the previous versions of this element 43 | * 44 | * @return an iterable of previous versions 45 | */ 46 | public Iterable getPreviousVersions(); 47 | 48 | /** 49 | * Returns the previous versions of this element that satisfy a particular condition implemented as a filter 50 | * 51 | * @param timeAwareFilter the particular time aware filter 52 | * @return an iterable of previous versions 53 | */ 54 | public Iterable getPreviousVersions(TimeAwareFilter timeAwareFilter); 55 | 56 | /** 57 | * Returns the next version of this element 58 | * 59 | * @return the next (time-aware) version of this element 60 | */ 61 | public TimeAwareElement getNextVersion(); 62 | 63 | /** 64 | * Returns the next versions of this element 65 | * 66 | * @return an iterable of previous versions 67 | */ 68 | public Iterable getNextVersions(); 69 | 70 | /** 71 | * Returns the next versions of this element that satisfy a particular condition implemented as a filter 72 | * 73 | * @param timeAwareFilter the particular time aware filter 74 | * @return an iterable of previous versions 75 | */ 76 | public Iterable getNextVersions(TimeAwareFilter timeAwareFilter); 77 | 78 | /** 79 | * Returns the time interval in which this version of this node is scoped 80 | * 81 | * @return the joda time interval 82 | */ 83 | public Interval getTimeInterval(); 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/jnj/fluxgraph/FluxTimeIterable.java: -------------------------------------------------------------------------------- 1 | package com.jnj.fluxgraph; 2 | 3 | import com.tinkerpop.blueprints.*; 4 | 5 | import java.util.Iterator; 6 | 7 | /** 8 | * @author Davy Suvee (http://datablend.be) 9 | */ 10 | public class FluxTimeIterable implements CloseableIterable { 11 | 12 | private TimeAwareElement timeAwareElement; 13 | private boolean forward; 14 | private TimeAwareFilter timeAwareFilter; 15 | 16 | public FluxTimeIterable(TimeAwareElement timeAwareElement, boolean forward) { 17 | this.timeAwareElement = timeAwareElement; 18 | this.forward = forward; 19 | } 20 | 21 | public FluxTimeIterable(TimeAwareElement timeAwareElement, boolean forward, TimeAwareFilter timeAwareFilter) { 22 | this.timeAwareElement = timeAwareElement; 23 | this.forward = forward; 24 | this.timeAwareFilter = timeAwareFilter; 25 | } 26 | 27 | @Override 28 | public void close() { 29 | } 30 | 31 | @Override 32 | public Iterator iterator() { 33 | if (forward) { 34 | return new ForwardTimeIterator(); 35 | } 36 | else { 37 | return new BackwardTimeIterator(); 38 | } 39 | } 40 | 41 | private class BackwardTimeIterator extends TimeIterator { 42 | @Override 43 | protected FluxElement getNext(TimeAwareElement element) { 44 | FluxElement found = (FluxElement)element.getPreviousVersion(); 45 | if (found != null && timeAwareFilter != null) { 46 | if (timeAwareFilter.filter(found) != null) { 47 | return found; 48 | } 49 | else { 50 | return getNext(found); 51 | } 52 | } 53 | return found; 54 | } 55 | } 56 | 57 | private class ForwardTimeIterator extends TimeIterator { 58 | @Override 59 | protected FluxElement getNext(TimeAwareElement element) { 60 | FluxElement found = (FluxElement)element.getNextVersion(); 61 | if (found != null && timeAwareFilter != null) { 62 | if (timeAwareFilter.filter(found) != null) { 63 | return found; 64 | } 65 | else { 66 | return getNext(found); 67 | } 68 | } 69 | return found; 70 | } 71 | } 72 | 73 | // Iterator for time aware iteration 74 | private abstract class TimeIterator implements Iterator { 75 | 76 | @Override 77 | public boolean hasNext() { 78 | return hasNext(getNext(timeAwareElement)); 79 | } 80 | 81 | protected abstract FluxElement getNext(TimeAwareElement element); 82 | 83 | @Override 84 | public TimeAwareElement next() { 85 | TimeAwareElement next = getNext(timeAwareElement); 86 | if (next != null) { 87 | timeAwareElement = next; 88 | return next; 89 | } 90 | else { 91 | return null; 92 | } 93 | } 94 | 95 | private boolean hasNext(FluxElement element) { 96 | // We do have a next version 97 | return element != null; 98 | } 99 | 100 | @Override 101 | public void remove() { 102 | throw new UnsupportedOperationException(); 103 | } 104 | 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/com/jnj/fluxgraph/FluxGraphTest.java: -------------------------------------------------------------------------------- 1 | package com.jnj.fluxgraph; 2 | 3 | import com.tinkerpop.blueprints.*; 4 | import com.tinkerpop.blueprints.impls.GraphTest; 5 | import com.tinkerpop.blueprints.util.io.gml.GMLReaderTestSuite; 6 | import com.tinkerpop.blueprints.util.io.graphml.GraphMLReaderTestSuite; 7 | import com.tinkerpop.blueprints.util.io.graphson.GraphSONReaderTestSuite; 8 | 9 | import java.lang.reflect.Method; 10 | import java.util.UUID; 11 | 12 | /** 13 | * Test suite for Datomic graph implementation. 14 | * 15 | * @author Davy Suvee (http://datablend.be) 16 | */ 17 | public class FluxGraphTest extends GraphTest { 18 | 19 | private FluxGraph currentGraph; 20 | 21 | /*public void testDatomicBenchmarkTestSuite() throws Exception { 22 | this.stopWatch(); 23 | doTestSuite(new FluxBenchmarkTestSuite(this)); 24 | printTestPerformance("FluxBenchmarkTestSuite", this.stopWatch()); 25 | }*/ 26 | 27 | public void testVertexTestSuite() throws Exception { 28 | this.stopWatch(); 29 | doTestSuite(new VertexTestSuite(this)); 30 | printTestPerformance("VertexTestSuite", this.stopWatch()); 31 | } 32 | 33 | public void testEdgeTestSuite() throws Exception { 34 | this.stopWatch(); 35 | doTestSuite(new EdgeTestSuite(this)); 36 | printTestPerformance("EdgeTestSuite", this.stopWatch()); 37 | } 38 | 39 | public void testGraphTestSuite() throws Exception { 40 | this.stopWatch(); 41 | doTestSuite(new GraphTestSuite(this)); 42 | printTestPerformance("GraphTestSuite", this.stopWatch()); 43 | } 44 | 45 | public void testQueryTestSuite() throws Exception { 46 | this.stopWatch(); 47 | doTestSuite(new QueryTestSuite(this)); 48 | printTestPerformance("QueryTestSuite", this.stopWatch()); 49 | } 50 | 51 | public void testKeyIndexableGraphTestSuite() throws Exception { 52 | this.stopWatch(); 53 | doTestSuite(new KeyIndexableGraphTestSuite(this)); 54 | printTestPerformance("KeyIndexableGraphTestSuite", this.stopWatch()); 55 | } 56 | 57 | public void testGraphMLReaderTestSuite() throws Exception { 58 | this.stopWatch(); 59 | doTestSuite(new GraphMLReaderTestSuite(this)); 60 | printTestPerformance("GraphMLReaderTestSuite", this.stopWatch()); 61 | } 62 | 63 | public void testGraphSONReaderTestSuite() throws Exception { 64 | this.stopWatch(); 65 | doTestSuite(new GraphSONReaderTestSuite(this)); 66 | printTestPerformance("GraphSONReaderTestSuite", this.stopWatch()); 67 | } 68 | 69 | public void testGMLReaderTestSuite() throws Exception { 70 | this.stopWatch(); 71 | doTestSuite(new GMLReaderTestSuite(this)); 72 | printTestPerformance("GMLReaderTestSuite", this.stopWatch()); 73 | } 74 | 75 | public Graph generateGraph() { 76 | this.currentGraph = new FluxGraph("datomic:mem://tinkerpop" + UUID.randomUUID()); 77 | return this.currentGraph; 78 | } 79 | 80 | public void doTestSuite(final TestSuite testSuite) throws Exception { 81 | for (Method method : testSuite.getClass().getDeclaredMethods()) { 82 | if (method.getName().startsWith("test")) { 83 | System.out.println("Testing " + method.getName() + "..."); 84 | method.invoke(testSuite); 85 | try { 86 | if (this.currentGraph != null) 87 | //this.currentGraph.clear(); 88 | this.currentGraph.shutdown(); 89 | } catch (Exception e) { 90 | } 91 | } 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/jnj/fluxgraph/FluxIterable.java: -------------------------------------------------------------------------------- 1 | package com.jnj.fluxgraph; 2 | 3 | import com.tinkerpop.blueprints.*; 4 | import datomic.Database; 5 | import datomic.Datom; 6 | 7 | import java.util.Collection; 8 | import java.util.Iterator; 9 | import java.util.List; 10 | 11 | /** 12 | * @author Davy Suvee (http://datablend.be) 13 | */ 14 | public class FluxIterable implements CloseableIterable { 15 | 16 | private Iterable datoms; 17 | private Collection> objects; 18 | private List ids; 19 | private final FluxGraph graph; 20 | private final Database database; 21 | private Class clazz; 22 | 23 | private FluxIterable(final FluxGraph graph, final Database database, final Class clazz) { 24 | this.graph = graph; 25 | this.clazz = clazz; 26 | this.database = database; 27 | } 28 | 29 | public FluxIterable(final Iterable datoms, final FluxGraph graph, final Database database, final Class clazz) { 30 | this(graph, database, clazz); 31 | this.datoms = datoms; 32 | } 33 | 34 | public FluxIterable(final Collection> objects, final FluxGraph graph, final Database database, final Class clazz) { 35 | this(graph, database, clazz); 36 | this.objects = objects; 37 | } 38 | 39 | public FluxIterable(final List ids, final FluxGraph graph, final Database database, final Class clazz) { 40 | this(graph, database, clazz); 41 | this.ids = ids; 42 | } 43 | 44 | public Iterator iterator() { 45 | if (datoms != null) { 46 | return new DatomicDatomIterator(); 47 | } 48 | else { 49 | if (objects != null) { 50 | return new DatomicQueryIterator(); 51 | } 52 | else { 53 | return new DatomicIdIterator(); 54 | } 55 | } 56 | } 57 | 58 | public void close() { 59 | } 60 | 61 | private abstract class DatomicIterator implements Iterator { 62 | 63 | protected abstract Object getNext(); 64 | 65 | public T next() { 66 | Object object = getNext(); 67 | T ret = null; 68 | if (clazz == Vertex.class) { 69 | ret = (T) new FluxVertex(graph, database, object); 70 | } else if (clazz == Edge.class) { 71 | ret = (T) new FluxEdge(graph, database, object); 72 | } else { 73 | throw new IllegalStateException(); 74 | } 75 | return ret; 76 | } 77 | 78 | public void remove() { 79 | throw new UnsupportedOperationException(); 80 | } 81 | 82 | } 83 | 84 | // Iterator for datomic datoms 85 | private class DatomicDatomIterator extends DatomicIterator { 86 | private Iterator iterator = datoms.iterator(); 87 | 88 | public boolean hasNext() { 89 | return iterator.hasNext(); 90 | } 91 | 92 | protected Object getNext() { 93 | return iterator.next().e(); 94 | } 95 | 96 | } 97 | 98 | // Iterator for datomic query results 99 | private class DatomicQueryIterator extends DatomicIterator { 100 | private Iterator> iterator = objects.iterator(); 101 | 102 | public boolean hasNext() { 103 | return iterator.hasNext(); 104 | } 105 | 106 | protected Object getNext() { 107 | return iterator.next().get(0); 108 | } 109 | 110 | } 111 | 112 | // Iterator for datomic ids 113 | private class DatomicIdIterator extends DatomicIterator { 114 | private Iterator iterator = ids.iterator(); 115 | 116 | @Override 117 | public boolean hasNext() { 118 | return iterator.hasNext(); 119 | } 120 | 121 | protected Object getNext() { 122 | return iterator.next(); 123 | } 124 | 125 | } 126 | 127 | } -------------------------------------------------------------------------------- /src/main/java/com/jnj/fluxgraph/FluxEdge.java: -------------------------------------------------------------------------------- 1 | package com.jnj.fluxgraph; 2 | 3 | import com.tinkerpop.blueprints.*; 4 | import com.tinkerpop.blueprints.util.ExceptionFactory; 5 | import com.tinkerpop.blueprints.util.StringFactory; 6 | import datomic.Database; 7 | import datomic.Util; 8 | 9 | import java.util.Set; 10 | 11 | /** 12 | * @author Davy Suvee (http://datablend.be) 13 | */ 14 | public class FluxEdge extends FluxElement implements TimeAwareEdge { 15 | 16 | public FluxEdge(final FluxGraph fluxGraph, final Database database) { 17 | super(fluxGraph, database); 18 | fluxGraph.addToTransaction(Util.map(":db/id", id, 19 | ":graph.element/type", ":graph.element.type/edge", 20 | ":db/ident", uuid)); 21 | } 22 | 23 | public FluxEdge(final FluxGraph fluxGraph, final Database database, final Object id) { 24 | super(fluxGraph, database); 25 | this.id = id; 26 | } 27 | 28 | @Override 29 | public TimeAwareEdge getPreviousVersion() { 30 | // Retrieve the previous version time id 31 | Object previousTimeId = FluxUtil.getPreviousTransaction(fluxGraph, this); 32 | if (previousTimeId != null) { 33 | // Create a new version of the edge timescoped to the previous time id 34 | return new FluxEdge(fluxGraph, fluxGraph.getRawGraph(previousTimeId), id); 35 | } 36 | return null; 37 | } 38 | 39 | @Override 40 | public TimeAwareEdge getNextVersion() { 41 | // Retrieve the next version time id 42 | Object nextTimeId = FluxUtil.getNextTransactionId(fluxGraph, this); 43 | if (nextTimeId != null) { 44 | // Create a new version of the edge timescoped to the next time id 45 | FluxEdge nextVertexVersion = new FluxEdge(fluxGraph, fluxGraph.getRawGraph(nextTimeId), id); 46 | // If no next version exists, the version of the edge is the current version (timescope with a null database) 47 | if (FluxUtil.getNextTransactionId(fluxGraph, nextVertexVersion) == null) { 48 | return new FluxEdge(fluxGraph, null, id); 49 | } 50 | else { 51 | return nextVertexVersion; 52 | } 53 | } 54 | return null; 55 | } 56 | 57 | @Override 58 | public Iterable getNextVersions() { 59 | return new FluxTimeIterable(this, true); 60 | } 61 | 62 | @Override 63 | public Iterable getPreviousVersions() { 64 | return new FluxTimeIterable(this, false); 65 | } 66 | 67 | @Override 68 | public Iterable getPreviousVersions(TimeAwareFilter timeAwareFilter) { 69 | return new FluxTimeIterable(this, false, timeAwareFilter); 70 | } 71 | 72 | @Override 73 | public Iterable getNextVersions(TimeAwareFilter timeAwareFilter) { 74 | return new FluxTimeIterable(this, true, timeAwareFilter); 75 | } 76 | 77 | @Override 78 | public TimeAwareVertex getVertex(Direction direction) throws IllegalArgumentException { 79 | if (direction.equals(Direction.OUT)) 80 | return new FluxVertex(fluxGraph, database, getDatabase().datoms(Database.EAVT, getId(), fluxGraph.GRAPH_EDGE_OUT_VERTEX).iterator().next().v()); 81 | else if (direction.equals(Direction.IN)) 82 | return new FluxVertex(fluxGraph, database, getDatabase().datoms(Database.EAVT, getId(), fluxGraph.GRAPH_EDGE_IN_VERTEX).iterator().next().v()); 83 | else 84 | throw ExceptionFactory.bothIsNotSupported(); 85 | } 86 | 87 | @Override 88 | public String getLabel() { 89 | return (String)getDatabase().datoms(Database.EAVT, getId(), fluxGraph.GRAPH_EDGE_LABEL).iterator().next().v(); 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return StringFactory.edgeString(this); 95 | } 96 | 97 | @Override 98 | public Set getFacts() { 99 | // Retrieve the facts of the edge itself 100 | Set theFacts = super.getFacts(); 101 | // Add the out and in vertex itself 102 | theFacts.add(FluxUtil.map(":db/id", getVertex(Direction.IN).getId(), ":graph.element/type", ":graph.element.type/vertex")); 103 | theFacts.add(FluxUtil.map(":db/id", getVertex(Direction.OUT).getId(), ":graph.element/type", ":graph.element.type/vertex")); 104 | return theFacts; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/jnj/fluxgraph/ImmutableFluxGraph.java: -------------------------------------------------------------------------------- 1 | package com.jnj.fluxgraph; 2 | 3 | import com.tinkerpop.blueprints.*; 4 | import datomic.*; 5 | 6 | import java.util.Date; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.Set; 10 | import java.util.concurrent.ExecutionException; 11 | 12 | /** 13 | * Created with IntelliJ IDEA. 14 | * User: dsuvee 15 | * Date: 09/09/12 16 | * Time: 16:42 17 | * To change this template use File | Settings | File Templates. 18 | */ 19 | public class ImmutableFluxGraph extends FluxGraph { 20 | 21 | private FluxGraph originGraph; 22 | 23 | public ImmutableFluxGraph(final String graphURI, FluxGraph originGraph, Set differenceFacts) { 24 | super(graphURI); 25 | this.originGraph = originGraph; 26 | // Add the additional meta model 27 | try { 28 | setupAdditionalMetaModel(); 29 | // Add the various fact that together define the difference graph 30 | for (Object differenceFact : differenceFacts) { 31 | addToTransaction(differenceFact); 32 | } 33 | transact(); 34 | } catch (ExecutionException e) { 35 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 36 | } catch (InterruptedException e) { 37 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 38 | } 39 | } 40 | 41 | public ImmutableFluxGraph(final String graphURI, final Date date) { 42 | super(graphURI); 43 | this.checkpointTime.set(date.getTime()); 44 | } 45 | 46 | @Override 47 | public TimeAwareEdge addEdge(Object id, Vertex outVertex, Vertex inVertex, String label) { 48 | throw new IllegalArgumentException("FluxGraph instance is immutable"); 49 | } 50 | 51 | @Override 52 | public void removeEdge(Edge edge) { 53 | throw new IllegalArgumentException("FluxGraph instance is immutable"); 54 | } 55 | 56 | @Override 57 | public TimeAwareVertex addVertex(Object id) { 58 | throw new IllegalArgumentException("FluxGraph instance is immutable"); 59 | } 60 | 61 | @Override 62 | public void removeVertex(Vertex vertex) { 63 | throw new IllegalArgumentException("FluxGraph instance is immutable"); 64 | } 65 | 66 | @Override 67 | public void setTransactionTime(Date transactionTime) { 68 | throw new IllegalArgumentException("FluxGraph instance is immutable"); 69 | } 70 | 71 | @Override 72 | public void clear() { 73 | throw new IllegalArgumentException("FluxGraph instance is immutable"); 74 | } 75 | 76 | protected void setupAdditionalMetaModel() throws ExecutionException, InterruptedException { 77 | // Add the attribute types contained in the origin graph 78 | // Retrieve the attributes 79 | Iterator> schemaIds = Peer.q("[:find ?id " + 80 | ":where [?id :db/valueType _] " + 81 | "[?id ?attribute ?value] ]", originGraph.getRawGraph().since(new Date(1))).iterator(); 82 | 83 | // Add the various custom attributes 84 | while (schemaIds.hasNext()) { 85 | Entity t = originGraph.getRawGraph().entity(schemaIds.next().get(0)); 86 | addToTransaction(Util.map(":db/id", Peer.tempid(":db.part/db"), 87 | ":db/ident", t.get(":db/ident"), 88 | ":db/valueType", t.get(":db/valueType"), 89 | ":db/cardinality", t.get(":db/cardinality"), 90 | ":db.install/_attribute", ":db.part/db")); 91 | } 92 | 93 | // Add the original id attribute types for both vertex and edge 94 | addToTransaction(Util.map(":db/id", Peer.tempid(":db.part/db"), 95 | ":db/ident", ":original$id.long.vertex", 96 | ":db/valueType", ":db.type/long", 97 | ":db/cardinality", ":db/cardinality", 98 | ":db.install/_attribute", ":db.part/db")); 99 | addToTransaction(Util.map(":db/id", Peer.tempid(":db.part/db"), 100 | ":db/ident", ":original$id.long.edge", 101 | ":db/valueType", ":db.type/long", 102 | ":db/cardinality", ":db/cardinality", 103 | ":db.install/_attribute", ":db.part/db")); 104 | 105 | // Transact it 106 | transact(); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | com.jnj 9 | fluxgraph 10 | 0.1 11 | Blueprints property graph implementation on top of the datomic database 12 | 13 | 14 | UTF-8 15 | 2.1.0 16 | 2.1.0 17 | 0.8.3538 18 | 2.1 19 | 20 | 21 | 22 | scm:git:git@github.com:datablend/fluxgraph.git 23 | scm:git:git@github.com:datablend/fluxgraph.git 24 | git@github.com:datablend/fluxgraph.git 25 | 26 | 27 | 28 | Davy Suvee 29 | dsuvee@its.jnj.com 30 | http://www.jnj.com 31 | 32 | 33 | Davy Suvee 34 | info@datablend.be/ 35 | http://www.datablend.be 36 | 37 | 38 | 39 | 40 | 41 | sonatype-nexus-snapshots 42 | Sonatype Nexus Snapshots 43 | https://oss.sonatype.org/content/repositories/snapshots 44 | 45 | 46 | clojars.org 47 | http://clojars.org/repo 48 | 49 | 50 | 51 | 52 | com.datomic 53 | datomic-free 54 | ${datomic-free.version} 55 | 56 | 57 | joda-time 58 | joda-time 59 | ${joda-time.version} 60 | 61 | 62 | com.tinkerpop.blueprints 63 | blueprints-core 64 | ${blueprints.version} 65 | 66 | 67 | com.tinkerpop.blueprints 68 | blueprints-test 69 | ${blueprints.version} 70 | test 71 | 72 | 73 | com.tinkerpop.rexster 74 | rexster-core 75 | ${rexster.version} 76 | 77 | 78 | 79 | ${basedir}/target 80 | ${project.artifactId}-${project.version} 81 | 82 | 83 | ${basedir}/src/main/resources 84 | 85 | 86 | 87 | 88 | 89 | ${basedir}/src/test/resources 90 | 91 | 92 | 93 | 94 | 95 | maven-compiler-plugin 96 | 2.3.2 97 | 98 | 1.6 99 | 1.6 100 | 101 | 102 | 103 | maven-assembly-plugin 104 | 105 | 106 | package 107 | 108 | assembly 109 | 110 | 111 | 112 | 113 | false 114 | 115 | src/assembly/standalone.xml 116 | src/assembly/distribution.xml 117 | 118 | ${project.artifactId}-${project.version} 119 | target 120 | target/assembly/work 121 | warn 122 | 123 | jar-with-dependencies 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /src/main/java/com/jnj/fluxgraph/FluxIndex.java: -------------------------------------------------------------------------------- 1 | package com.jnj.fluxgraph; 2 | 3 | import clojure.lang.Keyword; 4 | import com.tinkerpop.blueprints.*; 5 | import datomic.Database; 6 | import datomic.Peer; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | /** 14 | * @author Davy Suvee (http://datablend.be) 15 | * Helper class to retrieve elements. If an Datomic index exists for a particular attribute, it will be used under the covers. 16 | */ 17 | public class FluxIndex implements Index { 18 | 19 | private FluxGraph graph = null; 20 | private Class clazz = null; 21 | private String name = null; 22 | private Set indexKeys = null; 23 | private Database database; 24 | 25 | public FluxIndex(final String name, final FluxGraph g, final Database database, final Class clazz) { 26 | this.name = name; 27 | this.graph = g; 28 | this.clazz = clazz; 29 | this.database = database; 30 | } 31 | 32 | public FluxIndex(final String name, final FluxGraph g, final Database database, final Class clazz, Set indexKeys) { 33 | this.name = name; 34 | this.graph = g; 35 | this.clazz = clazz; 36 | this.indexKeys = indexKeys; 37 | this.database = database; 38 | } 39 | 40 | public String getIndexName() { 41 | return name; 42 | } 43 | 44 | public Class getIndexClass() { 45 | return clazz; 46 | } 47 | 48 | public Database getDatabase() { 49 | if (database == null) { 50 | return graph.getRawGraph(); 51 | } 52 | return database; 53 | } 54 | 55 | 56 | public void put(final String key, final Object value, final T element) { 57 | throw new UnsupportedOperationException(); 58 | } 59 | 60 | public CloseableIterable get(final String key, final Object value) { 61 | boolean matched = ((indexKeys == null) || ((indexKeys != null) && indexKeys.contains(key))); 62 | Keyword attribute = null; 63 | if ((this.getIndexClass().isAssignableFrom(FluxEdge.class)) && ("label".equals(key))) { 64 | attribute = Keyword.intern("graph.edge/label"); 65 | } 66 | else { 67 | attribute = FluxUtil.createKey(key, value.getClass(), clazz); 68 | } 69 | if (matched && FluxUtil.existingAttributeDefinition(attribute, graph)) { 70 | if (this.getIndexClass().isAssignableFrom(FluxVertex.class)) { 71 | return new FluxIterable(getElements(attribute, value, Keyword.intern("graph.element.type/vertex"), getDatabase()), graph, database, Vertex.class); 72 | } 73 | if (this.getIndexClass().isAssignableFrom(FluxEdge.class)) { 74 | return new FluxIterable(getElements(attribute, value, Keyword.intern("graph.element.type/edge"), getDatabase()), graph, database, Edge.class); 75 | } 76 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 77 | } 78 | else { 79 | if (this.getIndexClass().isAssignableFrom(FluxVertex.class)) { 80 | return new FluxIterable(new ArrayList>(), graph, database, Vertex.class); 81 | } 82 | if (this.getIndexClass().isAssignableFrom(FluxEdge.class)) { 83 | return new FluxIterable(new ArrayList>(), graph, database, Edge.class); 84 | } 85 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 86 | } 87 | } 88 | 89 | @Override 90 | public CloseableIterable query(String key, Object query) { 91 | throw new UnsupportedOperationException(); 92 | } 93 | 94 | public long count(final String key, final Object value) { 95 | boolean matched = ((indexKeys == null) || ((indexKeys != null) && indexKeys.contains(key))); 96 | Keyword attribute = null; 97 | if ((this.getIndexClass().isAssignableFrom(FluxEdge.class)) && ("label".equals(key))) { 98 | attribute = Keyword.intern("graph.edge/label"); 99 | } 100 | else { 101 | attribute = FluxUtil.createKey(key, value.getClass(), clazz); 102 | } 103 | if (matched && FluxUtil.existingAttributeDefinition(attribute, graph)) { 104 | if (this.getIndexClass().isAssignableFrom(FluxVertex.class)) { 105 | return getElements(attribute, value, Keyword.intern("graph.element.type/vertex"), getDatabase()).size(); 106 | } 107 | if (this.getIndexClass().isAssignableFrom(FluxEdge.class)) { 108 | return getElements(attribute, value, Keyword.intern("graph.element.type/edge"), getDatabase()).size(); 109 | } 110 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 111 | } 112 | else { 113 | return 0; 114 | } 115 | } 116 | 117 | public void remove(final String key, final Object value, final T element) { 118 | throw new UnsupportedOperationException(); 119 | } 120 | 121 | private Collection> getElements(Keyword attribute, Object value, Keyword type, Database database) { 122 | return Peer.q("[:find ?element " + 123 | ":in $ ?attribute ?value ?type " + 124 | ":where [?element :graph.element/type ?type] " + 125 | "[?element ?attribute ?value] ]", database, attribute, value, type); 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/com/jnj/fluxgraph/FluxVertex.java: -------------------------------------------------------------------------------- 1 | package com.jnj.fluxgraph; 2 | 3 | import com.tinkerpop.blueprints.*; 4 | import com.tinkerpop.blueprints.util.DefaultQuery; 5 | import com.tinkerpop.blueprints.util.MultiIterable; 6 | import com.tinkerpop.blueprints.util.StringFactory; 7 | import datomic.*; 8 | 9 | import java.util.*; 10 | 11 | /** 12 | * @author Davy Suvee (http://datablend.be) 13 | */ 14 | public class FluxVertex extends FluxElement implements TimeAwareVertex { 15 | 16 | protected FluxVertex(final FluxGraph fluxGraph, final Database database) { 17 | super(fluxGraph, database); 18 | fluxGraph.addToTransaction(Util.map(":db/id", id, 19 | ":graph.element/type", ":graph.element.type/vertex", 20 | ":db/ident", uuid)); 21 | } 22 | 23 | public FluxVertex(final FluxGraph fluxGraph, final Database database, final Object id) { 24 | super(fluxGraph, database); 25 | this.id = id; 26 | } 27 | 28 | @Override 29 | public TimeAwareVertex getPreviousVersion() { 30 | // Retrieve the previous version time id 31 | Object previousTimeId = FluxUtil.getPreviousTransaction(fluxGraph, this); 32 | if (previousTimeId != null) { 33 | // Create a new version of the vertex timescoped to the previous time id 34 | return new FluxVertex(fluxGraph, fluxGraph.getRawGraph(previousTimeId), id); 35 | } 36 | return null; 37 | } 38 | 39 | @Override 40 | public TimeAwareVertex getNextVersion() { 41 | // Retrieve the next version time id 42 | Object nextTimeId = FluxUtil.getNextTransactionId(fluxGraph, this); 43 | if (nextTimeId != null) { 44 | FluxVertex nextVertexVersion = new FluxVertex(fluxGraph, fluxGraph.getRawGraph(nextTimeId), id); 45 | // If no next version exists, the version of the edge is the current version (timescope with a null database) 46 | if (FluxUtil.getNextTransactionId(fluxGraph, nextVertexVersion) == null) { 47 | return new FluxVertex(fluxGraph, null, id); 48 | } 49 | else { 50 | return nextVertexVersion; 51 | } 52 | } 53 | return null; 54 | } 55 | 56 | @Override 57 | public Iterable getNextVersions() { 58 | return new FluxTimeIterable(this, true); 59 | } 60 | 61 | @Override 62 | public Iterable getPreviousVersions() { 63 | return new FluxTimeIterable(this, false); 64 | } 65 | 66 | @Override 67 | public Iterable getPreviousVersions(TimeAwareFilter timeAwareFilter) { 68 | return new FluxTimeIterable(this, false, timeAwareFilter); 69 | } 70 | 71 | @Override 72 | public Iterable getNextVersions(TimeAwareFilter timeAwareFilter) { 73 | return new FluxTimeIterable(this, true, timeAwareFilter); 74 | } 75 | 76 | @Override 77 | public Iterable getEdges(Direction direction, String... labels) { 78 | if (direction.equals(Direction.OUT)) { 79 | return this.getOutEdges(labels); 80 | } else if (direction.equals(Direction.IN)) 81 | return this.getInEdges(labels); 82 | else { 83 | return new MultiIterable(Arrays.asList(this.getInEdges(labels), this.getOutEdges(labels))); 84 | } 85 | } 86 | 87 | @Override 88 | public Iterable getVertices(Direction direction, String... labels) { 89 | if (direction.equals(Direction.OUT)) { 90 | Iterator edgesit = this.getOutEdges(labels).iterator(); 91 | List vertices = new ArrayList(); 92 | while (edgesit.hasNext()) { 93 | vertices.add(edgesit.next().getVertex(Direction.IN).getId()); 94 | } 95 | return new FluxIterable(vertices, fluxGraph, database, Vertex.class); 96 | } else if (direction.equals(Direction.IN)) { 97 | Iterator edgesit = this.getInEdges(labels).iterator(); 98 | List vertices = new ArrayList(); 99 | while (edgesit.hasNext()) { 100 | vertices.add(edgesit.next().getVertex(Direction.OUT).getId()); 101 | } 102 | return new FluxIterable(vertices, fluxGraph, database, Vertex.class); 103 | } 104 | else { 105 | Iterator outEdgesIt = this.getOutEdges(labels).iterator(); 106 | List outvertices = new ArrayList(); 107 | while (outEdgesIt.hasNext()) { 108 | outvertices.add(outEdgesIt.next().getVertex(Direction.IN).getId()); 109 | } 110 | Iterator inEdgesIt = this.getInEdges(labels).iterator(); 111 | List invertices = new ArrayList(); 112 | while (inEdgesIt.hasNext()) { 113 | invertices.add(inEdgesIt.next().getVertex(Direction.OUT).getId()); 114 | } 115 | return new MultiIterable(Arrays.>asList(new FluxIterable(outvertices, fluxGraph, database, Vertex.class), new FluxIterable(invertices, fluxGraph, database, Vertex.class))); 116 | } 117 | } 118 | 119 | @Override 120 | public String toString() { 121 | return StringFactory.vertexString(this); 122 | } 123 | 124 | @Override 125 | public Query query() { 126 | return new DefaultQuery(this); 127 | } 128 | 129 | @Override 130 | public Set getFacts() { 131 | // Retrieve the facts of the vertex itself 132 | Set theFacts = super.getFacts(); 133 | // Get the facts associated with the edges of this vertex 134 | Iterator edgesIt = getEdges(Direction.BOTH).iterator(); 135 | while (edgesIt.hasNext()) { 136 | Edge edge = edgesIt.next(); 137 | // Add the fact that the edge entity is an edge 138 | theFacts.add(FluxUtil.map(":db/id", edge.getId(), ":graph.element/type", ":graph.element.type/edge")); 139 | // Add the out and in vertex 140 | theFacts.add(FluxUtil.map(":db/id", edge.getVertex(Direction.IN).getId(), ":graph.element/type", ":graph.element.type/vertex")); 141 | theFacts.add(FluxUtil.map(":db/id", edge.getId(), ":graph.edge/inVertex", edge.getVertex(Direction.IN).getId())); 142 | theFacts.add(FluxUtil.map(":db/id", edge.getVertex(Direction.OUT).getId(), ":graph.element/type", ":graph.element.type/vertex")); 143 | theFacts.add(FluxUtil.map(":db/id", edge.getId(), ":graph.edge/outVertex", edge.getVertex(Direction.OUT).getId())); 144 | // Add the label 145 | theFacts.add(FluxUtil.map(":db/id", edge.getId(), ":graph.edge/label", edge.getLabel())); 146 | } 147 | return theFacts; 148 | } 149 | 150 | private Iterable getInEdges(final String... labels) { 151 | if (labels.length == 0) { 152 | return getInEdges(); 153 | } 154 | Collection> inEdges = Peer.q("[:find ?edge " + 155 | ":in $ ?vertex [?label ...] " + 156 | ":where [?edge :graph.edge/inVertex ?vertex] " + 157 | "[?edge :graph.edge/label ?label ] ]", getDatabase(), id, labels); 158 | return new FluxIterable(inEdges, fluxGraph, database, Edge.class); 159 | } 160 | 161 | private Iterable getInEdges() { 162 | Iterable inEdges = getDatabase().datoms(Database.AVET, fluxGraph.GRAPH_EDGE_IN_VERTEX, getId()); 163 | return new FluxIterable(inEdges, fluxGraph, database, Edge.class); 164 | } 165 | 166 | private Iterable getOutEdges(final String... labels) { 167 | if (labels.length == 0) { 168 | return getOutEdges(); 169 | } 170 | Collection> outEdges = Peer.q("[:find ?edge " + 171 | ":in $ ?vertex [?label ...] " + 172 | ":where [?edge :graph.edge/outVertex ?vertex] " + 173 | "[?edge :graph.edge/label ?label ] ]", getDatabase(), id, labels); 174 | return new FluxIterable(outEdges, fluxGraph, database, Edge.class); 175 | } 176 | 177 | private Iterable getOutEdges() { 178 | Iterable outEdges = getDatabase().datoms(Database.AVET, fluxGraph.GRAPH_EDGE_OUT_VERTEX, getId()); 179 | return new FluxIterable(outEdges, fluxGraph, database, Edge.class); 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/com/jnj/fluxgraph/FluxElement.java: -------------------------------------------------------------------------------- 1 | package com.jnj.fluxgraph; 2 | 3 | import clojure.lang.Keyword; 4 | import com.tinkerpop.blueprints.TimeAwareElement; 5 | import com.tinkerpop.blueprints.util.ExceptionFactory; 6 | import com.tinkerpop.blueprints.util.StringFactory; 7 | import datomic.Database; 8 | import datomic.Entity; 9 | import datomic.Peer; 10 | import datomic.Util; 11 | import org.joda.time.DateTime; 12 | import org.joda.time.Interval; 13 | 14 | import java.util.*; 15 | 16 | /** 17 | * @author Davy Suvee (http://datablend.be) 18 | */ 19 | public abstract class FluxElement implements TimeAwareElement { 20 | 21 | protected final Database database; 22 | protected final FluxGraph fluxGraph; 23 | protected Object uuid; 24 | protected Object id; 25 | 26 | protected FluxElement(final FluxGraph fluxGraph, final Database database) { 27 | this.database = database; 28 | this.fluxGraph = fluxGraph; 29 | // UUID used to retrieve the actual datomic id later on 30 | uuid = Keyword.intern(UUID.randomUUID().toString()); 31 | id = Peer.tempid(":graph"); 32 | } 33 | 34 | @Override 35 | public Object getId() { 36 | return id; 37 | } 38 | 39 | @Override 40 | public Object getTimeId() { 41 | return FluxUtil.getActualTimeId(getDatabase(), this); 42 | } 43 | 44 | @Override 45 | public boolean isCurrentVersion() { 46 | return database == null; 47 | } 48 | 49 | @Override 50 | public boolean isDeleted() { 51 | // An element is deleted if we can no longer find any reference to it in the current version of the graph 52 | Collection> found = (Peer.q("[:find ?id " + 53 | ":in $ ?id " + 54 | ":where [?id _ _ ] ]", getDatabase(), id)); 55 | return found.isEmpty(); 56 | } 57 | 58 | @Override 59 | public Set getPropertyKeys() { 60 | if (isDeleted()) { 61 | throw new IllegalArgumentException("It is not possible to get properties on a deleted element"); 62 | } 63 | Set finalproperties = new HashSet(); 64 | Set properties = getDatabase().entity(id).keySet(); 65 | Iterator propertiesit = properties.iterator(); 66 | while (propertiesit.hasNext()) { 67 | Keyword property = propertiesit.next(); 68 | if (!FluxUtil.isReservedKey(property.toString())) { 69 | finalproperties.add(FluxUtil.getPropertyName(property)); 70 | } 71 | } 72 | return finalproperties; 73 | } 74 | 75 | @Override 76 | public Object getProperty(final String key) { 77 | if (isDeleted()) { 78 | throw new IllegalArgumentException("It is not possible to get properties on a deleted element"); 79 | } 80 | if (!FluxUtil.isReservedKey(key)) { 81 | Set properties = getDatabase().entity(id).keySet(); 82 | Iterator propertiesit = properties.iterator(); 83 | // We need to iterate, as we don't know the exact type (although we ensured that only one attribute will have that name) 84 | while (propertiesit.hasNext()) { 85 | Keyword property = propertiesit.next(); 86 | String propertyname = FluxUtil.getPropertyName(property); 87 | if (key.equals(propertyname)) { 88 | return getDatabase().entity(id).get(property); 89 | } 90 | } 91 | // We didn't find the value 92 | return null; 93 | } 94 | else { 95 | return getDatabase().entity(id).get(key); 96 | } 97 | } 98 | 99 | @Override 100 | public void setProperty(final String key, final Object value) { 101 | validate(); 102 | if (key.equals(StringFactory.ID)) 103 | throw ExceptionFactory.propertyKeyIdIsReserved(); 104 | if (key.equals(StringFactory.LABEL)) 105 | throw new IllegalArgumentException("Property key is reserved for all nodes and edges: " + StringFactory.LABEL); 106 | if (key.equals(StringFactory.EMPTY_STRING)) 107 | throw ExceptionFactory.elementKeyCanNotBeEmpty(); 108 | // A user-defined property 109 | if (!FluxUtil.isReservedKey(key)) { 110 | // If the property does not exist yet, create the attribute if required and create the appropriate transaction 111 | if (getProperty(key) == null) { 112 | // We first need to create the new attribute on the fly 113 | FluxUtil.createAttributeDefinition(key, value.getClass(), this.getClass(), fluxGraph); 114 | fluxGraph.addToTransaction(Util.map(":db/id", id, 115 | FluxUtil.createKey(key, value.getClass(), this.getClass()), value)); 116 | } 117 | else { 118 | // Value types match, just perform an update 119 | if (getProperty(key).getClass().equals(value.getClass())) { 120 | fluxGraph.addToTransaction(Util.map(":db/id", id, 121 | FluxUtil.createKey(key, value.getClass(), this.getClass()), value)); 122 | } 123 | // Value types do not match. Retract original fact and add new one 124 | else { 125 | FluxUtil.createAttributeDefinition(key, value.getClass(), this.getClass(), fluxGraph); 126 | fluxGraph.addToTransaction(Util.list(":db/retract", id, 127 | FluxUtil.createKey(key, value.getClass(), this.getClass()), getProperty(key))); 128 | fluxGraph.addToTransaction(Util.map(":db/id", id, 129 | FluxUtil.createKey(key, value.getClass(), this.getClass()), value)); 130 | } 131 | } 132 | } 133 | // A datomic graph specific property 134 | else { 135 | fluxGraph.addToTransaction(Util.map(":db/id", id, 136 | key, value)); 137 | } 138 | fluxGraph.addTransactionInfo(this); 139 | fluxGraph.transact(); 140 | } 141 | 142 | public Interval getTimeInterval() { 143 | DateTime startTime = new DateTime(FluxUtil.getTransactionDate(fluxGraph, getTimeId())); 144 | TimeAwareElement nextElement = this.getNextVersion(); 145 | if (nextElement == null) { 146 | return new Interval(startTime, new DateTime(Long.MAX_VALUE)); 147 | } 148 | else { 149 | DateTime stopTime = new DateTime(FluxUtil.getTransactionDate(fluxGraph, nextElement.getTimeId())); 150 | return new Interval(startTime, stopTime); 151 | } 152 | } 153 | 154 | @Override 155 | public Object removeProperty(final String key) { 156 | validate(); 157 | Object oldvalue = getProperty(key); 158 | if (oldvalue != null) { 159 | if (!FluxUtil.isReservedKey(key)) { 160 | fluxGraph.addToTransaction(Util.list(":db/retract", id, 161 | FluxUtil.createKey(key, oldvalue.getClass(), this.getClass()), oldvalue)); 162 | } 163 | } 164 | fluxGraph.addTransactionInfo(this); 165 | fluxGraph.transact(); 166 | return oldvalue; 167 | } 168 | 169 | @Override 170 | public boolean equals(Object o) { 171 | if (this == o) return true; 172 | if (o == null || getClass() != o.getClass()) return false; 173 | FluxElement that = (FluxElement) o; 174 | if (id != null ? !id.equals(that.id) : that.id != null) return false; 175 | return true; 176 | } 177 | 178 | @Override 179 | public int hashCode() { 180 | return id != null ? id.hashCode() : 0; 181 | } 182 | 183 | protected Database getDatabase() { 184 | if (database == null) { 185 | return fluxGraph.getRawGraph(); 186 | } 187 | return database; 188 | } 189 | 190 | private void validate() { 191 | if (!isCurrentVersion()) { 192 | throw new IllegalArgumentException("It is not possible to set a property on a non-current version of the element"); 193 | } 194 | if (isDeleted()) { 195 | throw new IllegalArgumentException("It is not possible to set a property on a deleted element"); 196 | } 197 | } 198 | 199 | // Creates a collection containing the set of datomic facts describing this entity 200 | protected Set getFacts() { 201 | // Create the set of facts 202 | Set theFacts = new HashSet(); 203 | // Get the entity 204 | Entity entity = getDatabase().entity(id); 205 | // Add the base facts associated with this edge 206 | Set properties = entity.keySet(); 207 | Iterator propertiesIt = properties.iterator(); 208 | while (propertiesIt.hasNext()) { 209 | Keyword property = propertiesIt.next(); 210 | // Add all properties (except the ident property (is only originally used for retrieving the id of the created elements) 211 | if (!property.toString().equals(":db/ident")) { 212 | theFacts.add(FluxUtil.map(":db/id", id, property.toString(), entity.get(property).toString())); 213 | } 214 | } 215 | return theFacts; 216 | } 217 | 218 | } -------------------------------------------------------------------------------- /src/main/java/com/jnj/fluxgraph/FluxUtil.java: -------------------------------------------------------------------------------- 1 | package com.jnj.fluxgraph; 2 | 3 | import clojure.lang.Keyword; 4 | import com.tinkerpop.blueprints.TimeAwareElement; 5 | import datomic.Database; 6 | import datomic.Peer; 7 | import datomic.Util; 8 | 9 | import java.util.*; 10 | import java.util.concurrent.ExecutionException; 11 | 12 | /** 13 | * @author Davy Suvee (http://datablend.be) 14 | */ 15 | public class FluxUtil { 16 | 17 | private static final Map types; 18 | private static final String RESERVED = ":graph"; 19 | 20 | static { 21 | // Types supported by the underlying Datomic data model 22 | types = new HashMap(); 23 | types.put("java.lang.String",":db.type/string"); 24 | types.put("java.lang.Boolean",":db.type/boolean"); 25 | types.put("java.lang.Long",":db.type/long"); 26 | types.put("java.lang.Integer",":db.type/long"); 27 | types.put("java.math.BigInteger",":db.type/bigint"); 28 | types.put("java.lang.Float",":db.type/float"); 29 | types.put("java.lang.Double",":db.type/double"); 30 | types.put("java.math.BigDecimal",":db.type/bigdec"); 31 | types.put("java.util.UUID",":db.type/uuid"); 32 | types.put("java.net.URI",":db.type/uri"); 33 | } 34 | 35 | // Check whether a key is part of the reserved space 36 | public static boolean isReservedKey(final String key) { 37 | // Key specific to the graph model or the general Datomic namespace 38 | return (key.startsWith(RESERVED) || key.startsWith(":db/")); 39 | } 40 | 41 | // Retrieve the original name of a property 42 | public static String getPropertyName(final Keyword property) { 43 | if (property.toString().contains(".")) { 44 | return property.toString().substring(1, property.toString().indexOf(".")).replace("$","_"); 45 | } 46 | return null; 47 | } 48 | 49 | // Retrieve the Datomic to for the Java equivalent 50 | public static String mapJavaTypeToDatomicType(final Class clazz) { 51 | if (types.containsKey(clazz.getName())) { 52 | return types.get(clazz.getName()); 53 | } 54 | throw new IllegalArgumentException("Object type " + clazz.getName() + " not supported"); 55 | } 56 | 57 | // Create the attribute definition if it does not exist yet 58 | public static void createAttributeDefinition(final String key, final Class valueClazz, final Class elementClazz, FluxGraph graph) { 59 | if (!existingAttributeDefinition(key, valueClazz, elementClazz, graph)) { 60 | try { 61 | if (graph.getTransactionTime() == null) { 62 | graph.getConnection().transact(Util.list(Util.map(":db/id", Peer.tempid(":db.part/db"), 63 | ":db/ident", createKey(key, valueClazz, elementClazz), 64 | ":db/valueType", mapJavaTypeToDatomicType(valueClazz), 65 | ":db/cardinality", ":db.cardinality/one", 66 | ":db.install/_attribute", ":db.part/db"))).get(); 67 | } 68 | else { 69 | graph.getConnection().transact(Util.list(Util.map(":db/id", Peer.tempid(":db.part/db"), 70 | ":db/ident", createKey(key, valueClazz, elementClazz), 71 | ":db/valueType", mapJavaTypeToDatomicType(valueClazz), 72 | ":db/cardinality", ":db.cardinality/one", 73 | ":db.install/_attribute", ":db.part/db"), datomic.Util.map(":db/id", datomic.Peer.tempid(":db.part/tx"), ":db/txInstant", graph.getTransactionTime()))).get(); 74 | } 75 | } catch (InterruptedException e) { 76 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 77 | } catch (ExecutionException e) { 78 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 79 | } 80 | } 81 | } 82 | 83 | // Sets/Unsets an index for a particular attribute 84 | public static void setAttributeIndex(final String key, final Class elementClazz, FluxGraph graph, boolean index) { 85 | // For a specific key, multiple attributes could be specified in Datomic that have a different type. We need to create an index for all of them 86 | for (String type : types.keySet()) { 87 | try { 88 | if (!existingAttributeDefinition(key, Class.forName(type), elementClazz, graph)) { 89 | // Attribute of this type does not exist, create it first 90 | createAttributeDefinition(key, Class.forName(type), elementClazz, graph); 91 | } 92 | // Retrieve the attribute and index it 93 | Object attribute = getAttributeDefinition(key, Class.forName(type), elementClazz, graph); 94 | graph.getConnection().transact(Util.list(Util.map(":db/id", attribute, 95 | ":db/index", index))).get(); 96 | } catch(ClassNotFoundException e) { 97 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 98 | } catch (InterruptedException e) { 99 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 100 | } catch (ExecutionException e) { 101 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 102 | } 103 | } 104 | } 105 | 106 | // Creates an index for a particular attribute 107 | public static void createAttributeIndex(final String key, final Class elementClazz, FluxGraph graph) { 108 | setAttributeIndex(key, elementClazz, graph, true); 109 | } 110 | 111 | // Creates an index for a particular attribute 112 | public static void removeAttributeIndex(final String key, final Class elementClazz, final FluxGraph graph) { 113 | setAttributeIndex(key, elementClazz, graph, false); 114 | } 115 | 116 | // Checks whether a new attribute defintion needs to be created on the fly 117 | public static boolean existingAttributeDefinition(final String key, final Class valueClazz, final Class elementClazz, final FluxGraph graph) { 118 | int attributekeysize = Peer.q("[:find ?attribute " + 119 | ":in $ ?key " + 120 | ":where [?attribute :db/ident ?key] ]", graph.getRawGraph(), createKey(key, valueClazz, elementClazz)).size(); 121 | // Existing attribute 122 | return attributekeysize != 0; 123 | } 124 | 125 | // Retrieve the attribute definition (if it exists). Otherwise, it returns null 126 | public static Object getAttributeDefinition(final String key, final Class valueClazz, final Class elementClazz, final FluxGraph graph) { 127 | if (existingAttributeDefinition(key, valueClazz, elementClazz, graph)) { 128 | Collection> attributekeysize = Peer.q("[:find ?attribute " + 129 | ":in $ ?key " + 130 | ":where [?attribute :db/ident ?key] ]", graph.getRawGraph(), createKey(key, valueClazz, elementClazz)); 131 | return attributekeysize.iterator().next().get(0); 132 | } 133 | return null; 134 | } 135 | 136 | public static Set getIndexedAttributes(final Class elementClazz, final FluxGraph graph) { 137 | Set results = new HashSet(); 138 | Collection> indexedAttributes = Peer.q("[:find ?key " + 139 | ":in $ " + 140 | ":where [?attribute :db/ident ?key] " + 141 | "[?attribute :db/index true] ]", graph.getRawGraph()); 142 | for(List indexedAttribute : indexedAttributes) { 143 | String elementClazzName = elementClazz.getSimpleName(); 144 | if (indexedAttribute.get(0).toString().endsWith("." + elementClazzName.toLowerCase())) { 145 | results.add(getPropertyName((Keyword)indexedAttribute.get(0))); 146 | } 147 | } 148 | return results; 149 | } 150 | 151 | // Checks whether a new attribute defintion needs to be created on the fly 152 | public static boolean existingAttributeDefinition(final Keyword key, final FluxGraph graph) { 153 | int attributekeysize = Peer.q("[:find ?attribute " + 154 | ":in $ ?key " + 155 | ":where [?attribute :db/ident ?key] ]", graph.getRawGraph(), key).size(); 156 | // Existing attribute 157 | return attributekeysize != 0; 158 | } 159 | 160 | // Creates a unique key for each key-valuetype attribute (as only one attribute with the same name can be specified) 161 | public static Keyword createKey(final String key, final Class valueClazz, final Class elementClazz) { 162 | String elementType = "vertex"; 163 | if (elementClazz.isAssignableFrom(FluxEdge.class)) { 164 | elementType = "edge"; 165 | } 166 | return Keyword.intern(key.replace("_","$") + "." + mapJavaTypeToDatomicType(valueClazz).split("/")[1] + "." + elementType); 167 | } 168 | 169 | // Returns the previous transaction for a particular time aware element 170 | public static Object getPreviousTransaction(FluxGraph graph, TimeAwareElement element) { 171 | Iterator> previoustransaction = (Peer.q("[:find ?previousTransactionId " + 172 | ":in $ ?currentTransactionId ?id " + 173 | ":where [?currentTransactionId :graph.element/previousTransaction ?previousTransaction] " + 174 | "[?previousTransaction :graph.element/previousTransaction/elementId ?id] " + 175 | "[?previousTransaction :graph.element/previousTransaction/transactionId ?previousTransactionId] ]", graph.getRawGraph(), element.getTimeId(), element.getId())).iterator(); 176 | if (previoustransaction.hasNext()) { 177 | return previoustransaction.next().get(0); 178 | } 179 | return null; 180 | } 181 | 182 | // Returns the next transaction for a particular time aware element (null if the transaction id does not exist) 183 | public static Object getNextTransactionId(FluxGraph graph, TimeAwareElement element) { 184 | // Retrieve the last encountered transaction before the input transaction 185 | Iterator> nexttransaction = (Peer.q("[:find ?nextTransactionId " + 186 | ":in $ ?currenttransactionId ?id " + 187 | ":where [?nextTransactionId :graph.element/previousTransaction ?currenttransaction] " + 188 | "[?currenttransaction :graph.element/previousTransaction/elementId ?id] " + 189 | "[?currenttransaction :graph.element/previousTransaction/transactionId ?currenttransactionId] ]", graph.getRawGraph(), element.getTimeId(), element.getId())).iterator(); 190 | if (nexttransaction.hasNext()) { 191 | return nexttransaction.next().get(0); 192 | } 193 | return null; 194 | } 195 | 196 | public static Object getActualTimeId(Database database, TimeAwareElement element) { 197 | // Get the actual time id for a particular element and database value 198 | String timeRule = "[ [ (previous ?id ?tx) [?id _ _ ?tx] ] " + 199 | "[ (previous ?id ?tx) [_ :graph.element/previousTransaction/elementId ?id ?tx] ] ] ]"; 200 | Collection> alltxs = (datomic.Peer.q("[:find ?tx " + 201 | ":in $ ?id % " + 202 | ":where [previous ?id ?tx] ]", database.history(), element.getId(), timeRule)); 203 | Iterator> tx = alltxs.iterator(); 204 | Object lastTransaction = null; 205 | if (tx.hasNext()) { 206 | List transactionElement = tx.next(); 207 | lastTransaction = transactionElement.get(0); 208 | } 209 | while (tx.hasNext()) { 210 | List transactionElement = tx.next(); 211 | if ((Long)transactionElement.get(0) > (Long)lastTransaction) { 212 | lastTransaction = transactionElement.get(0); 213 | } 214 | } 215 | 216 | return lastTransaction; 217 | } 218 | 219 | // Helper method to retrieve the date associated with a particular transaction id 220 | public static Date getTransactionDate(FluxGraph graph, Object transaction) { 221 | return (Date)datomic.Peer.q("[:find ?time " + 222 | ":in $ ?tx " + 223 | ":where [?tx :db/txInstant ?time] ]", graph.getRawGraph(), transaction).iterator().next().get(0); 224 | } 225 | 226 | public static Object getIdForAttribute(FluxGraph graph, String attribute) { 227 | return Peer.q("[:find ?entity " + 228 | ":in $ ?attribute " + 229 | ":where [?entity :db/ident ?attribute] ] ", graph.getRawGraph(), Keyword.intern(attribute)).iterator().next().get(0); 230 | } 231 | 232 | // Helper method to create a mutable map (instead of an immutable map via the datomic Util.map method) 233 | public static Map map(Object... mapValues) { 234 | Map map = new HashMap(); 235 | for (int i = 0; i < mapValues.length; i = i + 2) { 236 | map.put(mapValues[i], mapValues[i+1]); 237 | } 238 | return map; 239 | } 240 | 241 | // Helper method to construct the difference (as a set of facts) between 2 sets of facts 242 | // The difference is calculated as a symmetric difference, while only maintaining the facts of the first set 243 | public static Set difference(Set facts1, Set facts2) { 244 | // Copy the set first 245 | Set difference = ((Set) ((HashSet) facts1).clone()); 246 | Iterator facts1it = facts1.iterator(); 247 | // Check which facts are exclusively part of the facts1 set 248 | while (facts1it.hasNext()) { 249 | Map fact = (Map)facts1it.next(); 250 | if (!isGraphElementTypeFact(fact) && !isDbIdentFact(fact)) { 251 | // Check whether this fact is also available in facts2. If so, remove the element 252 | if (facts2.contains(fact)) { 253 | difference.remove(fact); 254 | } 255 | } 256 | } 257 | // Return the normalized difference with newly generated temporary id 258 | normalize(difference); 259 | replaceWithTempId(difference); 260 | return difference; 261 | } 262 | 263 | // Helper method to normalize a set of facts (effectively removing facts (vertices or edges) that have no other attributes or are used as values of other facts 264 | public static void normalize(Set facts) { 265 | Iterator factsit = facts.iterator(); 266 | while (factsit.hasNext()) { 267 | Map fact = (Map)factsit.next(); 268 | // If it defines an element (vertex or edge) 269 | if (isGraphElementTypeFact(fact)) { 270 | // Get the id 271 | Object entityId = fact.get(":db/id"); 272 | boolean found = false; 273 | // Check whether we find other facts (either as id or as value itself) that refer to this entity 274 | for (Object otherFact : facts) { 275 | if (((Map)otherFact).containsValue(entityId) && !isGraphElementTypeFact((Map)otherFact) && !isDbIdentFact((Map)otherFact)) { 276 | found = true; 277 | break; 278 | } 279 | } 280 | if (!found) { 281 | factsit.remove(); 282 | } 283 | } 284 | } 285 | } 286 | 287 | // Helper method to replace actual id's with temporary id's (use for creating the graph difference) 288 | public static void replaceWithTempId(Set facts) { 289 | Set originalIdFacts = new HashSet(); 290 | for (Object fact : facts) { 291 | // Get the id of the entity 292 | Object id = ((Map)fact).get(":db/id"); 293 | // If the id is still a long, it's need to be replace with a new datomic temporary id 294 | if (id instanceof Long) { 295 | // Create a temp id 296 | Object newId = Peer.tempid(":graph"); 297 | // Add the existing id as a fact so that it can be retrieved as a property 298 | // Depending on the type of element, a different property name needs to be used in order to make it transparant at the graph level 299 | if (facts.contains(Util.map(":db/id", id, ":graph.element/type", ":graph.element.type/vertex"))) { 300 | originalIdFacts.add(Util.map(":db/id", newId, ":original$id.long.vertex", id)); 301 | } 302 | else { 303 | originalIdFacts.add(Util.map(":db/id", newId, ":original$id.long.edge", id)); 304 | } 305 | ((Map)fact).put(":db/id", newId); 306 | // Replace all facts that have this id or use this id with the newly generate temp id 307 | for (Object otherfact : facts) { 308 | Set keys = ((Map)otherfact).keySet(); 309 | for (Object key : keys) { 310 | if (((Map)otherfact).get(key).equals(id)) { 311 | ((Map)otherfact).put(key, newId); 312 | } 313 | } 314 | } 315 | } 316 | } 317 | // Add the original id facts 318 | facts.addAll(originalIdFacts); 319 | } 320 | 321 | private static boolean isGraphElementTypeFact(Map fact) { 322 | return fact.containsKey(":graph.element/type"); 323 | } 324 | 325 | private static boolean isDbIdentFact(Map fact) { 326 | return fact.containsKey(":db/ident"); 327 | } 328 | 329 | } 330 | -------------------------------------------------------------------------------- /src/main/java/com/jnj/fluxgraph/FluxGraph.java: -------------------------------------------------------------------------------- 1 | package com.jnj.fluxgraph; 2 | 3 | import com.tinkerpop.blueprints.*; 4 | import com.tinkerpop.blueprints.util.ExceptionFactory; 5 | import com.tinkerpop.blueprints.util.StringFactory; 6 | import datomic.*; 7 | 8 | import java.util.*; 9 | import java.util.concurrent.ExecutionException; 10 | 11 | /** 12 | * A Blueprints implementation of a graph on top of Datomic 13 | * 14 | * @author Davy Suvee (http://datablend.be) 15 | */ 16 | public class FluxGraph implements MetaGraph, KeyIndexableGraph, TimeAwareGraph { 17 | 18 | private final String graphURI; 19 | private final Connection connection; 20 | public static final String DATOMIC_ERROR_EXCEPTION_MESSAGE = "An error occured within the Datomic datastore"; 21 | 22 | public final Object GRAPH_ELEMENT_TYPE; 23 | public final Object GRAPH_ELEMENT_TYPE_VERTEX; 24 | public final Object GRAPH_ELEMENT_TYPE_EDGE; 25 | public final Object GRAPH_EDGE_IN_VERTEX; 26 | public final Object GRAPH_EDGE_OUT_VERTEX; 27 | public final Object GRAPH_EDGE_LABEL; 28 | 29 | private final FluxIndex vertexIndex; 30 | private final FluxIndex edgeIndex; 31 | 32 | protected final ThreadLocal tx = new ThreadLocal() { 33 | protected List initialValue() { 34 | return new ArrayList(); 35 | } 36 | }; 37 | protected final ThreadLocal checkpointTime = new ThreadLocal() { 38 | protected Long initialValue() { 39 | return null; 40 | } 41 | }; 42 | protected final ThreadLocal transactionTime = new ThreadLocal() { 43 | protected Date initialValue() { 44 | return null; 45 | } 46 | }; 47 | 48 | private static final Features FEATURES = new Features(); 49 | 50 | static { 51 | FEATURES.supportsDuplicateEdges = true; 52 | FEATURES.supportsSelfLoops = true; 53 | FEATURES.isPersistent = false; 54 | FEATURES.isRDFModel = false; 55 | FEATURES.supportsVertexIteration = true; 56 | FEATURES.supportsEdgeIteration = true; 57 | FEATURES.supportsVertexIndex = false; 58 | FEATURES.supportsEdgeIndex = false; 59 | FEATURES.ignoresSuppliedIds = true; 60 | FEATURES.supportsEdgeRetrieval = true; 61 | FEATURES.supportsVertexProperties = true; 62 | FEATURES.supportsEdgeProperties = true; 63 | FEATURES.supportsTransactions = false; 64 | FEATURES.supportsIndices = false; 65 | 66 | FEATURES.supportsSerializableObjectProperty = false; 67 | FEATURES.supportsBooleanProperty = true; 68 | FEATURES.supportsDoubleProperty = true; 69 | FEATURES.supportsFloatProperty = true; 70 | FEATURES.supportsIntegerProperty = true; 71 | FEATURES.supportsPrimitiveArrayProperty = false; 72 | FEATURES.supportsUniformListProperty = false; 73 | FEATURES.supportsMixedListProperty = false; 74 | FEATURES.supportsLongProperty = true; 75 | FEATURES.supportsMapProperty = false; 76 | FEATURES.supportsStringProperty = true; 77 | 78 | FEATURES.isWrapper = true; 79 | FEATURES.supportsKeyIndices = true; 80 | FEATURES.supportsVertexKeyIndex = true; 81 | FEATURES.supportsEdgeKeyIndex = true; 82 | FEATURES.supportsThreadedTransactions = false; 83 | } 84 | 85 | public FluxGraph(final String graphURI) { 86 | this.graphURI = graphURI; 87 | Peer.createDatabase(graphURI); 88 | // Retrieve the connection 89 | this.connection = Peer.connect(graphURI); 90 | 91 | try { 92 | // Setup the meta model for the graph 93 | if (requiresMetaModel()) { 94 | setupMetaModel(); 95 | } 96 | // Retrieve the relevant ids for the properties (for raw index access later on) 97 | GRAPH_ELEMENT_TYPE = FluxUtil.getIdForAttribute(this, "graph.element/type"); 98 | GRAPH_ELEMENT_TYPE_VERTEX = FluxUtil.getIdForAttribute(this, "graph.element.type/vertex"); 99 | GRAPH_ELEMENT_TYPE_EDGE = FluxUtil.getIdForAttribute(this, "graph.element.type/edge"); 100 | GRAPH_EDGE_IN_VERTEX = FluxUtil.getIdForAttribute(this, "graph.edge/inVertex"); 101 | GRAPH_EDGE_OUT_VERTEX = FluxUtil.getIdForAttribute(this, "graph.edge/outVertex"); 102 | GRAPH_EDGE_LABEL = FluxUtil.getIdForAttribute(this, "graph.edge/label"); 103 | } catch (ExecutionException e) { 104 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 105 | } catch (InterruptedException e) { 106 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 107 | } 108 | // Create the required indexes 109 | this.vertexIndex = new FluxIndex("vertexIndex", this, null, Vertex.class); 110 | this.edgeIndex = new FluxIndex("edgeIndex", this, null, Edge.class); 111 | } 112 | 113 | @Override 114 | public Features getFeatures() { 115 | return FEATURES; 116 | } 117 | 118 | @Override 119 | public void shutdown() { 120 | // No actions required 121 | } 122 | 123 | @Override 124 | public TimeAwareEdge getEdge(final Object id) { 125 | if (null == id) 126 | throw ExceptionFactory.edgeIdCanNotBeNull(); 127 | try { 128 | return new FluxEdge(this, this.getRawGraph(), Long.valueOf(id.toString()).longValue()); 129 | } catch (NumberFormatException e) { 130 | return null; 131 | } catch (RuntimeException re) { 132 | return null; 133 | } 134 | } 135 | 136 | @Override 137 | public Iterable getEdges() { 138 | Iterable edges = this.getRawGraph().datoms(Database.AVET, GRAPH_ELEMENT_TYPE, GRAPH_ELEMENT_TYPE_EDGE); 139 | return new FluxIterable(edges, this, this.getRawGraph(), Edge.class); 140 | } 141 | 142 | @Override 143 | public Iterable getEdges(String key, Object value) { 144 | return edgeIndex.get(key, value); 145 | } 146 | 147 | @Override 148 | public TimeAwareEdge addEdge(final Object id, final Vertex outVertex, final Vertex inVertex, final String label) { 149 | // Create the new edge 150 | final FluxEdge edge = new FluxEdge(this, null); 151 | tx.get().add(Util.map(":db/id", edge.id, 152 | ":graph.edge/label", label, 153 | ":graph.edge/inVertex", inVertex.getId(), 154 | ":graph.edge/outVertex", outVertex.getId())); 155 | 156 | // Update the transaction info of both vertices (moving up their current transaction) 157 | addTransactionInfo((TimeAwareVertex)inVertex, (TimeAwareVertex)outVertex); 158 | 159 | // Transact 160 | transact(); 161 | 162 | // Set the real id on the entity 163 | edge.id = getRawGraph().entid(edge.uuid); 164 | return edge; 165 | } 166 | 167 | @Override 168 | public void removeEdge(final Edge edge) { 169 | removeEdge(edge, true); 170 | } 171 | 172 | @Override 173 | public TimeAwareVertex addVertex(final Object id) { 174 | // Create the new vertex 175 | FluxVertex vertex = new FluxVertex(this, null); 176 | 177 | // Transact 178 | transact(); 179 | 180 | // Set the real id on the entity 181 | vertex.id = getRawGraph().entid(vertex.uuid); 182 | 183 | return vertex; 184 | } 185 | 186 | @Override 187 | public TimeAwareVertex getVertex(final Object id) { 188 | if (null == id) 189 | throw ExceptionFactory.vertexIdCanNotBeNull(); 190 | try { 191 | final Long longId = Long.valueOf(id.toString()).longValue(); 192 | return new FluxVertex(this, this.getRawGraph(), longId); 193 | } catch (NumberFormatException e) { 194 | return null; 195 | } catch (RuntimeException re) { 196 | return null; 197 | } 198 | } 199 | 200 | @Override 201 | public Iterable getVertices() { 202 | Iterable vertices = this.getRawGraph().datoms(Database.AVET, this.GRAPH_ELEMENT_TYPE, this.GRAPH_ELEMENT_TYPE_VERTEX); 203 | return new FluxIterable(vertices, this, this.getRawGraph(), Vertex.class); 204 | } 205 | 206 | @Override 207 | public Iterable getVertices(String key, Object value) { 208 | return vertexIndex.get(key, value); 209 | } 210 | 211 | @Override 212 | public void removeVertex(final Vertex vertex) { 213 | removeVertex(vertex, true); 214 | } 215 | 216 | @Override 217 | public Database getRawGraph() { 218 | if (checkpointTime.get() != null) { 219 | return getRawGraph(checkpointTime.get()); 220 | } 221 | return connection.db(); 222 | } 223 | 224 | @Override 225 | public void setCheckpointTime(Date date) { 226 | Long transaction = null; 227 | // Retrieve the transactions 228 | Iterator> tx = (Peer.q("[:find ?tx ?when " + 229 | ":where [?tx :db/txInstant ?when]]", connection.db().asOf(date))).iterator(); 230 | while (tx.hasNext()) { 231 | List txobject = tx.next(); 232 | Long transactionid = (Long)txobject.get(0); 233 | if (transaction == null) { 234 | transaction = transactionid; 235 | } 236 | else { 237 | if (transactionid > transaction) { 238 | transaction = transactionid; 239 | } 240 | } 241 | } 242 | this.checkpointTime.set(transaction); 243 | } 244 | 245 | @Override 246 | public void setTransactionTime(Date transactionTime) { 247 | this.transactionTime.set(transactionTime); 248 | } 249 | 250 | @Override 251 | public Graph difference(WorkingSet workingSet, Date date1, Date date2) { 252 | Set factsAtDate1 = new HashSet(); 253 | Set factsAtDate2 = new HashSet(); 254 | // Set graph at checkpoint date1 255 | setCheckpointTime(date1); 256 | for (Object vertex : workingSet.getVertices()) { 257 | factsAtDate1.addAll(((FluxVertex)getVertex(vertex)).getFacts()); 258 | } 259 | for (Object edge : workingSet.getEdges()) { 260 | factsAtDate1.addAll(((FluxEdge)getEdge(edge)).getFacts()); 261 | } 262 | // Set graph at checkpoint date2 263 | setCheckpointTime(date2); 264 | for (Object vertex : workingSet.getVertices()) { 265 | factsAtDate2.addAll(((FluxVertex)getVertex(vertex)).getFacts()); 266 | } 267 | for (Object edge : workingSet.getEdges()) { 268 | factsAtDate2.addAll(((FluxEdge)getEdge(edge)).getFacts()); 269 | } 270 | // Calculate the difference between the facts of both time aware elements 271 | Set difference = FluxUtil.difference(factsAtDate1, factsAtDate2); 272 | return new ImmutableFluxGraph("datomic:mem://temp" + UUID.randomUUID(), this, difference); 273 | } 274 | 275 | @Override 276 | public Graph difference(TimeAwareElement element1, TimeAwareElement element2) { 277 | // Calculate the difference between the facts of both time aware elements 278 | Set difference = FluxUtil.difference(((FluxElement) element1).getFacts(), ((FluxElement) element2).getFacts()); 279 | return new ImmutableFluxGraph("datomic:mem://temp" + UUID.randomUUID(), this, difference); 280 | } 281 | 282 | @Override 283 | public String toString() { 284 | return StringFactory.graphString(this, graphURI); 285 | } 286 | 287 | @Override 288 | public void dropKeyIndex(String key, Class elementClass) { 289 | FluxUtil.removeAttributeIndex(key, elementClass, this); 290 | } 291 | 292 | @Override 293 | public void createKeyIndex(String key, Class elementClass) { 294 | FluxUtil.createAttributeIndex(key, elementClass, this); 295 | } 296 | 297 | @Override 298 | public Set getIndexedKeys(Class elementClass) { 299 | return FluxUtil.getIndexedAttributes(elementClass, this); 300 | } 301 | 302 | public Date getTransactionTime() { 303 | return transactionTime.get(); 304 | } 305 | 306 | public void clear() { 307 | Iterator verticesit = getVertices().iterator(); 308 | while (verticesit.hasNext()) { 309 | removeVertex(verticesit.next(), false); 310 | } 311 | transact(); 312 | } 313 | 314 | public Database getRawGraph(Object transaction) { 315 | if (transaction == null) { 316 | return connection.db(); 317 | } 318 | return connection.db().asOf(transaction); 319 | } 320 | 321 | public void addToTransaction(Object o) { 322 | tx.get().add(o); 323 | } 324 | 325 | public void transact() { 326 | try { 327 | // We are adding a fact which dates back to the past. Add the required meta data on the transaction 328 | if (transactionTime.get() != null) { 329 | tx.get().add(datomic.Util.map(":db/id", datomic.Peer.tempid(":db.part/tx"), ":db/txInstant", transactionTime.get())); 330 | } 331 | connection.transact(tx.get()).get(); 332 | tx.get().clear(); 333 | } catch (InterruptedException e) { 334 | tx.get().clear(); 335 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 336 | } catch (ExecutionException e) { 337 | tx.get().clear(); 338 | throw new RuntimeException(FluxGraph.DATOMIC_ERROR_EXCEPTION_MESSAGE); 339 | } 340 | } 341 | 342 | public Connection getConnection() { 343 | return connection; 344 | } 345 | 346 | // Ensures that add-transaction-info database function is called during the transaction execution. This will setup the linked list of transactions 347 | public void addTransactionInfo(TimeAwareElement... elements) { 348 | for (TimeAwareElement element : elements) { 349 | tx.get().add(Util.list(":add-transaction-info", element.getId(), element.getTimeId())); 350 | } 351 | } 352 | 353 | private void removeEdge(final Edge edge, boolean transact) { 354 | // Retract the edge element in its totality 355 | FluxEdge theEdge = (FluxEdge)edge; 356 | tx.get().add(Util.list(":db.fn/retractEntity", theEdge.getId())); 357 | 358 | // Get the in and out vertex (as their version also needs to be updated) 359 | FluxVertex inVertex = (FluxVertex)theEdge.getVertex(Direction.IN); 360 | FluxVertex outVertex = (FluxVertex)theEdge.getVertex(Direction.OUT); 361 | 362 | // Update the transaction info of the edge and both vertices (moving up their current transaction) 363 | addTransactionInfo(theEdge, inVertex, outVertex); 364 | 365 | // We need to commit 366 | if (transact) { 367 | transact(); 368 | } 369 | } 370 | 371 | private void removeVertex(Vertex vertex, boolean transact) { 372 | // Retrieve all edges associated with this vertex and remove them one bye one 373 | Iterator edgesIt = vertex.getEdges(Direction.BOTH).iterator(); 374 | while (edgesIt.hasNext()) { 375 | removeEdge(edgesIt.next(), false); 376 | } 377 | // Retract the vertex element in its totality 378 | tx.get().add(Util.list(":db.fn/retractEntity", vertex.getId())); 379 | 380 | // Update the transaction info of the vertex 381 | addTransactionInfo((FluxVertex)vertex); 382 | 383 | // We need to commit 384 | if (transact) { 385 | transact(); 386 | } 387 | } 388 | 389 | // Helper method to check whether the meta model of the graph still needs to be setup 390 | protected boolean requiresMetaModel() { 391 | return !Peer.q("[:find ?entity " + 392 | ":in $ " + 393 | ":where [?entity :db/ident :graph.element/type] ] ", getRawGraph()).iterator().hasNext(); 394 | } 395 | 396 | // Setup of the various attribute types required for FluxGraph 397 | protected void setupMetaModel() throws ExecutionException, InterruptedException { 398 | 399 | // The graph element type attribute 400 | tx.get().add(Util.map(":db/id", Peer.tempid(":db.part/db"), 401 | ":db/ident", ":graph.element/type", 402 | ":db/valueType", ":db.type/ref", 403 | ":db/cardinality", ":db.cardinality/one", 404 | ":db/doc", "A graph element type", 405 | ":db/index", true, 406 | ":db.install/_attribute", ":db.part/db")); 407 | 408 | // The graph vertex element type 409 | tx.get().add(Util.map(":db/id", Peer.tempid(":db.part/user"), 410 | ":db/ident", ":graph.element.type/vertex")); 411 | 412 | // The graph edge element type 413 | tx.get().add(Util.map(":db/id", Peer.tempid(":db.part/user"), 414 | ":db/ident", ":graph.element.type/edge")); 415 | 416 | // The incoming vertex of an edge attribute 417 | tx.get().add(Util.map(":db/id", Peer.tempid(":db.part/db"), 418 | ":db/ident", ":graph.edge/inVertex", 419 | ":db/valueType", ":db.type/ref", 420 | ":db/cardinality", ":db.cardinality/one", 421 | ":db/doc", "The incoming vertex of an edge", 422 | ":db/index", true, 423 | ":db.install/_attribute", ":db.part/db")); 424 | 425 | // The outgoing vertex of an edge attribute 426 | tx.get().add(Util.map(":db/id", Peer.tempid(":db.part/db"), 427 | ":db/ident", ":graph.edge/outVertex", 428 | ":db/valueType", ":db.type/ref", 429 | ":db/cardinality", ":db.cardinality/one", 430 | ":db/doc", "The outgoing vertex of an edge", 431 | ":db/index", true, 432 | ":db.install/_attribute", ":db.part/db")); 433 | 434 | // The outgoing vertex of an edge attribute 435 | tx.get().add(Util.map(":db/id", Peer.tempid(":db.part/db"), 436 | ":db/ident", ":graph.edge/label", 437 | ":db/valueType", ":db.type/string", 438 | ":db/cardinality", ":db.cardinality/one", 439 | ":db/doc", "The label of a vertex", 440 | ":db/index", true, 441 | ":db.install/_attribute", ":db.part/db")); 442 | 443 | // The previous transaction through which the entity (vertex or edge) was changed 444 | tx.get().add(Util.map(":db/id", Peer.tempid(":db.part/db"), 445 | ":db/ident", ":graph.element/previousTransaction", 446 | ":db/valueType", ":db.type/ref", 447 | ":db/cardinality", ":db.cardinality/many", 448 | ":db/doc", "The previous transactions of the elements that wer changed", 449 | ":db/index", true, 450 | ":db.install/_attribute", ":db.part/db")); 451 | 452 | tx.get().add(Util.map(":db/id", Peer.tempid(":db.part/db"), 453 | ":db/ident", ":graph.element/previousTransaction/elementId", 454 | ":db/valueType", ":db.type/ref", 455 | ":db/cardinality", ":db.cardinality/one", 456 | ":db/doc", "The element id of the entity that was part of the previous transaction", 457 | ":db/index", true, 458 | ":db.install/_attribute", ":db.part/db")); 459 | 460 | tx.get().add(Util.map(":db/id", Peer.tempid(":db.part/db"), 461 | ":db/ident", ":graph.element/previousTransaction/transactionId", 462 | ":db/valueType", ":db.type/ref", 463 | ":db/cardinality", ":db.cardinality/one", 464 | ":db/doc", "The transaction id for the entity that was part of the previous transaction", 465 | ":db/index", true, 466 | ":db.install/_attribute", ":db.part/db")); 467 | 468 | String addTransactionInfoCode = "Object transactInfoId = tempid(\":db.part/user\");\n" + 469 | "return list(list(\":db/add\", transactInfoId, \":graph.element/previousTransaction/transactionId\", lastTransaction), list(\":db/add\", transactInfoId, \":graph.element/previousTransaction/elementId\", id), list(\":db/add\", tempid(\":db.part/tx\"), \":graph.element/previousTransaction\", transactInfoId));\n"; 470 | 471 | // Database function that retrieves the previous transaction and sets the new one 472 | tx.get().add(Util.map(":db/id", Peer.tempid(":db.part/user"), 473 | ":db/ident", ":add-transaction-info", 474 | ":db/fn", Peer.function(Util.map("lang", "java", 475 | "params", Util.list("db", "id", "lastTransaction"), 476 | "code", addTransactionInfoCode)))); 477 | 478 | // Add new graph partition 479 | tx.get().add(Util.map(":db/id", Peer.tempid(":db.part/db"), 480 | ":db/ident", ":graph", 481 | ":db.install/_partition", ":db.part/db")); 482 | 483 | tx.get().add(datomic.Util.map(":db/id", datomic.Peer.tempid(":db.part/tx"), ":db/txInstant", new Date(0))); 484 | connection.transact(tx.get()).get(); 485 | tx.get().clear(); 486 | } 487 | 488 | } --------------------------------------------------------------------------------