1 package eu.ehri.project.core.impl.neo4j;
2
3 import com.google.common.base.Preconditions;
4 import com.tinkerpop.blueprints.CloseableIterable;
5 import com.tinkerpop.blueprints.Edge;
6 import com.tinkerpop.blueprints.Features;
7 import com.tinkerpop.blueprints.GraphQuery;
8 import com.tinkerpop.blueprints.MetaGraph;
9 import com.tinkerpop.blueprints.TransactionalGraph;
10 import com.tinkerpop.blueprints.Vertex;
11 import com.tinkerpop.blueprints.util.DefaultGraphQuery;
12 import com.tinkerpop.blueprints.util.ExceptionFactory;
13 import com.tinkerpop.blueprints.util.PropertyFilteredIterable;
14 import com.tinkerpop.blueprints.util.StringFactory;
15 import com.tinkerpop.blueprints.util.WrappingCloseableIterable;
16 import org.apache.commons.configuration.Configuration;
17 import org.apache.commons.configuration.ConfigurationConverter;
18 import org.neo4j.graphdb.GraphDatabaseService;
19 import org.neo4j.graphdb.Label;
20 import org.neo4j.graphdb.Node;
21 import org.neo4j.graphdb.NotFoundException;
22 import org.neo4j.graphdb.Relationship;
23 import org.neo4j.graphdb.RelationshipType;
24 import org.neo4j.graphdb.ResourceIterable;
25 import org.neo4j.graphdb.ResourceIterator;
26 import org.neo4j.graphdb.Transaction;
27 import org.neo4j.graphdb.TransactionFailureException;
28 import org.neo4j.graphdb.factory.GraphDatabaseBuilder;
29 import org.neo4j.graphdb.factory.GraphDatabaseFactory;
30
31 import java.io.File;
32 import java.util.Collections;
33 import java.util.Map;
34 import java.util.logging.Logger;
35
36
37
38
39 public class Neo4j2Graph implements TransactionalGraph, MetaGraph<GraphDatabaseService> {
40 private static final Logger logger = Logger.getLogger(Neo4j2Graph.class.getName());
41
42 private GraphDatabaseService rawGraph;
43
44 protected final ThreadLocal<Transaction> tx = new ThreadLocal<Transaction>() {
45 protected Transaction initialValue() {
46 return null;
47 }
48 };
49
50 protected final ThreadLocal<Boolean> checkElementsInTransaction = new ThreadLocal<Boolean>() {
51 protected Boolean initialValue() {
52 return false;
53 }
54 };
55
56 private static final Features FEATURES = new Features();
57
58 static {
59
60 FEATURES.supportsSerializableObjectProperty = false;
61 FEATURES.supportsBooleanProperty = true;
62 FEATURES.supportsDoubleProperty = true;
63 FEATURES.supportsFloatProperty = true;
64 FEATURES.supportsIntegerProperty = true;
65 FEATURES.supportsPrimitiveArrayProperty = true;
66 FEATURES.supportsUniformListProperty = true;
67 FEATURES.supportsMixedListProperty = false;
68 FEATURES.supportsLongProperty = true;
69 FEATURES.supportsMapProperty = false;
70 FEATURES.supportsStringProperty = true;
71
72 FEATURES.supportsDuplicateEdges = true;
73 FEATURES.supportsSelfLoops = true;
74 FEATURES.isPersistent = true;
75 FEATURES.isWrapper = false;
76 FEATURES.supportsVertexIteration = true;
77 FEATURES.supportsEdgeIteration = true;
78 FEATURES.supportsVertexIndex = false;
79 FEATURES.supportsEdgeIndex = true;
80 FEATURES.ignoresSuppliedIds = true;
81 FEATURES.supportsTransactions = true;
82 FEATURES.supportsIndices = true;
83 FEATURES.supportsKeyIndices = true;
84 FEATURES.supportsVertexKeyIndex = false;
85 FEATURES.supportsEdgeKeyIndex = false;
86 FEATURES.supportsEdgeRetrieval = true;
87 FEATURES.supportsVertexProperties = true;
88 FEATURES.supportsEdgeProperties = true;
89 FEATURES.supportsThreadedTransactions = false;
90 FEATURES.supportsThreadIsolatedTransactions = true;
91 }
92
93 protected boolean checkElementsInTransaction() {
94 if (this.tx.get() == null) {
95 return false;
96 } else {
97 return this.checkElementsInTransaction.get();
98 }
99 }
100
101 public Neo4j2Graph(String directory) {
102 this(directory, null);
103 }
104
105 public Neo4j2Graph(GraphDatabaseService rawGraph) {
106 this.rawGraph = rawGraph;
107
108 init();
109 }
110
111 public Neo4j2Graph(String directory, Map<String, String> configuration) {
112 try {
113 GraphDatabaseBuilder builder = new GraphDatabaseFactory().newEmbeddedDatabaseBuilder(new File(directory));
114 if (null != configuration)
115 this.rawGraph = builder.setConfig(configuration).newGraphDatabase();
116 else
117 this.rawGraph = builder.newGraphDatabase();
118
119 init();
120
121 } catch (Exception e) {
122 if (this.rawGraph != null)
123 this.rawGraph.shutdown();
124 throw new RuntimeException(e.getMessage(), e);
125 }
126 }
127
128 protected void init() {
129 }
130
131 public Neo4j2Graph(Configuration configuration) {
132 this(configuration.getString("blueprints.neo4j.directory", null),
133 ConfigurationConverter.getMap(configuration.subset("blueprints.neo4j.conf")));
134 }
135
136 @Override
137 public Neo4j2Vertex addVertex(Object id) {
138 this.autoStartTransaction(true);
139 return new Neo4j2Vertex(this.rawGraph.createNode(), this);
140 }
141
142 @Override
143 public Neo4j2Vertex getVertex(Object id) {
144 this.autoStartTransaction(false);
145
146 if (null == id)
147 throw ExceptionFactory.vertexIdCanNotBeNull();
148
149 try {
150 Long longId;
151 if (id instanceof Long)
152 longId = (Long) id;
153 else if (id instanceof Number)
154 longId = ((Number) id).longValue();
155 else
156 longId = Double.valueOf(id.toString()).longValue();
157 return new Neo4j2Vertex(this.rawGraph.getNodeById(longId), this);
158 } catch (NotFoundException e) {
159 return null;
160 } catch (NumberFormatException e) {
161 return null;
162 }
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176
177 @Override
178 public Iterable<Vertex> getVertices() {
179 this.autoStartTransaction(false);
180 return new Neo4j2VertexIterable(rawGraph.getAllNodes(), this);
181 }
182
183 public CloseableIterable<Vertex> getVerticesByLabel(final String label) {
184 this.autoStartTransaction(false);
185 ResourceIterable<Node> wrap = new ResourceIterable<Node>() {
186 @Override
187 public ResourceIterator<Node> iterator() {
188 return rawGraph.<Node>findNodes(Label.label(label));
189 }
190 };
191 return new Neo4j2VertexIterable(wrap, this);
192 }
193
194 public CloseableIterable<Vertex> getVerticesByLabelKeyValue(
195 final String label, final String key, final Object value) {
196 ResourceIterable<Node> wrap = new ResourceIterable<Node>() {
197 @Override
198 public ResourceIterator<Node> iterator() {
199 autoStartTransaction(false);
200 return rawGraph.<Node>findNodes(Label.label(label), key, value);
201 }
202 };
203 return new Neo4j2VertexIterable(wrap, this);
204 }
205
206
207
208
209
210
211
212
213
214
215
216 public CloseableIterable<Vertex> getVerticesByQuery(final String query, final Map<String, Object> params, String column) {
217 Preconditions.checkNotNull(query, "Query cannot be null");
218 Preconditions.checkNotNull(column, "Column cannot be null");
219 ResourceIterable<Node> wrap = () -> {
220 autoStartTransaction(false);
221 return rawGraph
222 .execute(query, params != null ? params : Collections.emptyMap())
223 .columnAs(column);
224 };
225 return new Neo4j2VertexIterable(wrap, this);
226 }
227
228 @Override
229 public Iterable<Vertex> getVertices(String key, Object value) {
230 this.autoStartTransaction(false);
231 return new PropertyFilteredIterable<>(key, value, this.getVertices());
232 }
233
234
235
236
237
238
239
240
241
242
243
244
245
246 @Override
247 public Iterable<Edge> getEdges() {
248 this.autoStartTransaction(false);
249 return new Neo4j2EdgeIterable(rawGraph.getAllRelationships(), this);
250 }
251
252 @Override
253 public Iterable<Edge> getEdges(String key, Object value) {
254 this.autoStartTransaction(false);
255 return new PropertyFilteredIterable<>(key, value, this.getEdges());
256 }
257
258 @Override
259 public void removeVertex(Vertex vertex) {
260 this.autoStartTransaction(true);
261
262 try {
263 Node node = ((Neo4j2Vertex) vertex).getRawVertex();
264 for (Relationship relationship : node.getRelationships(org.neo4j.graphdb.Direction.BOTH)) {
265 relationship.delete();
266 }
267 node.delete();
268 } catch (NotFoundException | IllegalStateException nfe) {
269 throw ExceptionFactory.vertexWithIdDoesNotExist(vertex.getId());
270 }
271 }
272
273 @Override
274 public Neo4j2Edge addEdge(Object id, Vertex outVertex, Vertex inVertex, String label) {
275 if (label == null)
276 throw ExceptionFactory.edgeLabelCanNotBeNull();
277
278 this.autoStartTransaction(true);
279 return new Neo4j2Edge(((Neo4j2Vertex) outVertex).getRawVertex().createRelationshipTo(((Neo4j2Vertex) inVertex).getRawVertex(),
280 RelationshipType.withName(label)), this);
281 }
282
283 @Override
284 public Neo4j2Edge getEdge(Object id) {
285 if (null == id)
286 throw ExceptionFactory.edgeIdCanNotBeNull();
287
288 this.autoStartTransaction(true);
289 try {
290 Long longId;
291 if (id instanceof Long)
292 longId = (Long) id;
293 else
294 longId = Double.valueOf(id.toString()).longValue();
295 return new Neo4j2Edge(this.rawGraph.getRelationshipById(longId), this);
296 } catch (NotFoundException e) {
297 return null;
298 } catch (NumberFormatException e) {
299 return null;
300 }
301 }
302
303 @Override
304 public void removeEdge(Edge edge) {
305 this.autoStartTransaction(true);
306 ((Relationship) ((Neo4j2Edge) edge).getRawElement()).delete();
307 }
308
309 @Override
310 public void stopTransaction(Conclusion conclusion) {
311 if (Conclusion.SUCCESS == conclusion)
312 commit();
313 else
314 rollback();
315 }
316
317 @Override
318 public void commit() {
319 if (null == tx.get()) {
320 return;
321 }
322
323 try {
324 tx.get().success();
325 } finally {
326 tx.get().close();
327 tx.remove();
328 }
329 }
330
331 @Override
332 public void rollback() {
333 if (null == tx.get()) {
334 return;
335 }
336
337 try {
338 tx.get().failure();
339 } finally {
340 tx.get().close();
341 tx.remove();
342 }
343 }
344
345 @Override
346 public void shutdown() {
347 try {
348 this.commit();
349 } catch (TransactionFailureException e) {
350 logger.warning("Failure on shutdown " + e.getMessage());
351
352 }
353 this.rawGraph.shutdown();
354 }
355
356
357
358
359
360
361
362
363 public void autoStartTransaction(boolean forWrite) {
364 if (tx.get() == null)
365 tx.set(this.rawGraph.beginTx());
366 }
367
368 public GraphDatabaseService getRawGraph() {
369 return this.rawGraph;
370 }
371
372 public Features getFeatures() {
373 return FEATURES;
374 }
375
376 public String toString() {
377 return StringFactory.graphString(this, this.rawGraph.toString());
378 }
379
380 public GraphQuery query() {
381 return new DefaultGraphQuery(this);
382 }
383
384 public CloseableIterable<Map<String, Object>> query(String query, Map<String, Object> params) {
385 ResourceIterable<Map<String, Object>> wrap = () -> {
386 autoStartTransaction(false);
387 return rawGraph.execute(query, params == null ? Collections.<String, Object>emptyMap() : params);
388 };
389 return new WrappingCloseableIterable<>(wrap);
390 }
391 }