/* * Copyright 2016 SteelBridge Laboratories, LLC. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * For more information: http://steelbridgelabs.com */ package com.steelbridgelabs.oss.neo4j.structure; import com.steelbridgelabs.oss.neo4j.structure.summary.ResultSummaryLogger; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.tinkerpop.gremlin.structure.Transaction; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.util.ElementHelper; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Record; import org.neo4j.driver.Result; import org.neo4j.driver.Session; import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.types.Node; import org.neo4j.driver.types.Relationship; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; /** * @author Rogelio J. Baucells */ class Neo4JSession implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(Neo4JSession.class); private final Neo4JGraph graph; private final Neo4JReadPartition partition; private final Session session; private final Neo4JElementIdProvider<?> vertexIdProvider; private final Neo4JElementIdProvider<?> edgeIdProvider; private final Map<Object, Neo4JVertex> vertices = new HashMap<>(); private final Map<Object, Neo4JEdge> edges = new HashMap<>(); private final Set<Object> deletedVertices = new HashSet<>(); private final Set<Object> deletedEdges = new HashSet<>(); private final Set<Neo4JVertex> transientVertices = new HashSet<>(); private final Set<Neo4JEdge> transientEdges = new HashSet<>(); private final Map<Object, Neo4JVertex> transientVertexIndex = new HashMap<>(); private final Map<Object, Neo4JEdge> transientEdgeIndex = new HashMap<>(); private final Set<Neo4JVertex> vertexUpdateQueue = new HashSet<>(); private final Set<Neo4JEdge> edgeUpdateQueue = new HashSet<>(); private final Set<Neo4JVertex> vertexDeleteQueue = new HashSet<>(); private final Set<Neo4JEdge> edgeDeleteQueue = new HashSet<>(); private final boolean readonly; private org.neo4j.driver.Transaction transaction; private boolean verticesLoaded = false; private boolean edgesLoaded = false; private boolean profilerEnabled = false; Neo4JSession(Neo4JGraph graph, Session session, Neo4JElementIdProvider<?> vertexIdProvider, Neo4JElementIdProvider<?> edgeIdProvider, boolean readonly) { Objects.requireNonNull(graph, "graph cannot be null"); Objects.requireNonNull(session, "session cannot be null"); Objects.requireNonNull(vertexIdProvider, "vertexIdProvider cannot be null"); Objects.requireNonNull(edgeIdProvider, "edgeIdProvider cannot be null"); // log information if (logger.isDebugEnabled()) logger.debug("Creating session [{}]", session.hashCode()); // store fields this.graph = graph; this.partition = graph.getPartition(); this.session = session; this.vertexIdProvider = vertexIdProvider; this.edgeIdProvider = edgeIdProvider; this.readonly = readonly; } public org.neo4j.driver.Transaction beginTransaction() { // check we have a transaction already in progress if (transaction != null && transaction.isOpen()) throw Transaction.Exceptions.transactionAlreadyOpen(); // begin transaction transaction = session.beginTransaction(); // log information if (logger.isDebugEnabled()) logger.debug("Beginning transaction on session [{}]-[{}]", session.hashCode(), transaction.hashCode()); // return transaction instance return transaction; } boolean isTransactionOpen() { return transaction != null && transaction.isOpen(); } void commit() { // check we have an open transaction if (transaction != null) { // log information if (logger.isDebugEnabled()) logger.debug("Committing transaction [{}]", transaction.hashCode()); // flush session flush(); // indicate success (this is the moment that data is committed to the server) transaction.commit(); // close neo4j transaction transaction.close(); // commit transient vertices transientVertices.forEach(Neo4JVertex::commit); // commit transient edges transientEdges.forEach(Neo4JEdge::commit); // commit dirty vertices vertexUpdateQueue.forEach(Neo4JVertex::commit); // commit dirty edges edgeUpdateQueue.forEach(Neo4JEdge::commit); // move transient vertices to vertices transientVertices.forEach(vertex -> vertices.put(vertex.id(), vertex)); // move transient edges to edges transientEdges.forEach(edge -> edges.put(edge.id(), edge)); // clean internal structures deletedEdges.clear(); edgeDeleteQueue.clear(); deletedVertices.clear(); vertexDeleteQueue.clear(); transientEdges.clear(); transientVertices.clear(); transientVertexIndex.clear(); transientEdgeIndex.clear(); vertexUpdateQueue.clear(); edgeUpdateQueue.clear(); // log information if (logger.isDebugEnabled()) logger.debug("Successfully committed transaction [{}]", transaction.hashCode()); // remove instance transaction = null; } } void rollback() { // check we have an open transaction if (transaction != null) { // log information if (logger.isDebugEnabled()) logger.debug("Rolling back transaction [{}]", transaction.hashCode()); // indicate failure transaction.rollback(); // close neo4j transaction (this is the moment that data is rolled-back from the server) transaction.close(); // reset vertices loaded flag if needed if (!vertexUpdateQueue.isEmpty() || !deletedVertices.isEmpty()) verticesLoaded = false; // reset edges loaded flag if needed if (!edgeUpdateQueue.isEmpty() || !deletedEdges.isEmpty()) edgesLoaded = false; // rollback dirty vertices vertexUpdateQueue.forEach(Neo4JVertex::rollback); // rollback dirty edges edgeUpdateQueue.forEach(Neo4JEdge::rollback); // restore deleted vertices vertexDeleteQueue.forEach(vertex -> { // restore in map vertices.put(vertex.id(), vertex); // rollback vertex vertex.rollback(); }); // restore deleted edges edgeDeleteQueue.forEach(edge -> { // restore in map edges.put(edge.id(), edge); // rollback edge edge.rollback(); }); // clean internal structures deletedEdges.clear(); edgeDeleteQueue.clear(); deletedVertices.clear(); vertexDeleteQueue.clear(); transientEdges.clear(); transientVertices.clear(); transientVertexIndex.clear(); transientEdgeIndex.clear(); vertexUpdateQueue.clear(); edgeUpdateQueue.clear(); // log information if (logger.isDebugEnabled()) logger.debug("Successfully rolled-back transaction [{}]", transaction.hashCode()); // remove instance transaction = null; } } void closeTransaction() { // check we have an open transaction if (transaction != null) { // log information if (logger.isDebugEnabled()) logger.debug("Closing transaction [{}]", transaction.hashCode()); // close transaction transaction.close(); // remove instance transaction = null; } } Bookmark lastBookmark() { // last bookmark in session return session.lastBookmark(); } Neo4JVertex addVertex(Object... keyValues) { Objects.requireNonNull(keyValues, "keyValues cannot be null"); // verify parameters are key/value pairs ElementHelper.legalPropertyKeyValueArray(keyValues); // id cannot be present if (ElementHelper.getIdValue(keyValues).isPresent()) throw Vertex.Exceptions.userSuppliedIdsNotSupported(); // create vertex Neo4JVertex vertex = new Neo4JVertex(graph, this, vertexIdProvider, edgeIdProvider, Arrays.asList(ElementHelper.getLabelValue(keyValues).orElse(Vertex.DEFAULT_LABEL).split(Neo4JVertex.LabelDelimiter))); // add vertex to transient set (before processing properties to avoid having a transient vertex in update queue) transientVertices.add(vertex); // attach properties ElementHelper.attachProperties(vertex, keyValues); // check vertex has id Object id = vertex.id(); if (id != null) transientVertexIndex.put(id, vertex); // return vertex return vertex; } Neo4JEdge addEdge(String label, Neo4JVertex out, Neo4JVertex in, Object... keyValues) { Objects.requireNonNull(label, "label cannot be null"); Objects.requireNonNull(out, "out cannot be null"); Objects.requireNonNull(in, "in cannot be null"); Objects.requireNonNull(keyValues, "keyValues cannot be null"); // validate label ElementHelper.validateLabel(label); // verify parameters are key/value pairs ElementHelper.legalPropertyKeyValueArray(keyValues); // id cannot be present if (ElementHelper.getIdValue(keyValues).isPresent()) throw Vertex.Exceptions.userSuppliedIdsNotSupported(); // create edge Neo4JEdge edge = new Neo4JEdge(graph, this, edgeIdProvider, label, out, in); // register transient edge (before processing properties to avoid having a transient edge in update queue) transientEdges.add(edge); // attach properties ElementHelper.attachProperties(edge, keyValues); // register transient edge with adjacent vertices out.addOutEdge(edge); in.addInEdge(edge); // check edge has id Object id = edge.id(); if (id != null) transientEdgeIndex.put(id, edge); // return edge return edge; } private String generateVertexMatchPattern(String alias) { // get labels from read partition to be applied in vertex patterns Set<String> labels = partition.vertexMatchPatternLabels(); if (!labels.isEmpty()) { // vertex match within partition return "(" + alias + labels.stream().map(label -> ":`" + label + "`").collect(Collectors.joining("")) + ")"; } // vertex match return "(" + alias + ")"; } boolean isProfilerEnabled() { return profilerEnabled; } void setProfilerEnabled(boolean profilerEnabled) { this.profilerEnabled = profilerEnabled; } public Iterator<Vertex> vertices(Object[] ids) { Objects.requireNonNull(ids, "ids cannot be null"); // verify identifiers verifyIdentifiers(Vertex.class, ids); // check we have all vertices already loaded if (!verticesLoaded) { // check ids if (ids.length > 0) { // parameters as a stream Set<Object> identifiers = Arrays.stream(ids).map(id -> processIdentifier(vertexIdProvider, id)).collect(Collectors.toSet()); // filter ids, remove ids already in memory (only ids that might exist on server) List<Object> filter = identifiers.stream().filter(id -> !vertices.containsKey(id) && !transientVertexIndex.containsKey(id)).collect(Collectors.toList()); // check we need to execute statement in server if (!filter.isEmpty()) { // vertex match predicate String predicate = partition.vertexMatchPredicate("n"); // change operator on single id filtering (performance optimization) if (filter.size() == 1) { // execute statement Result result = executeStatement("MATCH " + generateVertexMatchPattern("n") + " WHERE " + vertexIdProvider.matchPredicateOperand("n") + " = $id" + (predicate != null ? " AND " + predicate : "") + " RETURN n", Collections.singletonMap("id", filter.get(0))); // create stream from query Stream<Vertex> query = vertices(result); // combine stream from memory and query result Iterator<Vertex> iterator = combine(Stream.concat(identifiers.stream().filter(vertices::containsKey).map(id -> (Vertex)vertices.get(id)), identifiers.stream().filter(transientVertexIndex::containsKey).map(id -> (Vertex)transientVertexIndex.get(id))), query); // process summary (query has been already consumed by combine) ResultSummaryLogger.log(result.consume()); // return iterator return iterator; } // execute statement Result result = executeStatement("MATCH " + generateVertexMatchPattern("n") + " WHERE " + vertexIdProvider.matchPredicateOperand("n") + " IN $ids" + (predicate != null ? " AND " + predicate : "") + " RETURN n", Collections.singletonMap("ids", filter)); // create stream from query Stream<Vertex> query = vertices(result); // combine stream from memory and query result Iterator<Vertex> iterator = combine(Stream.concat(identifiers.stream().filter(vertices::containsKey).map(id -> (Vertex)vertices.get(id)), identifiers.stream().filter(transientVertexIndex::containsKey).map(id -> (Vertex)transientVertexIndex.get(id))), query); // process summary (query has been already consumed by combine) ResultSummaryLogger.log(result.consume()); // return iterator return iterator; } // no need to execute query, only items in memory return combine(identifiers.stream().filter(vertices::containsKey).map(id -> (Vertex)vertices.get(id)), identifiers.stream().filter(transientVertexIndex::containsKey).map(id -> (Vertex)transientVertexIndex.get(id))); } // vertex match predicate String predicate = partition.vertexMatchPredicate("n"); // execute statement Result result = executeStatement("MATCH " + generateVertexMatchPattern("n") + (predicate != null ? " WHERE " + predicate : "") + " RETURN n", Collections.emptyMap()); // create stream from query Stream<Vertex> query = vertices(result); // combine stream from memory (transient) and query result Iterator<Vertex> iterator = combine(transientVertices.stream().map(vertex -> (Vertex)vertex), query); // process summary (query has been already consumed by combine) ResultSummaryLogger.log(result.consume()); // it is safe to update loaded flag at this time verticesLoaded = true; // return iterator return iterator; } // check ids if (ids.length > 0) { // parameters as a stream (set to remove duplicated ids) Set<Object> identifiers = Arrays.stream(ids).map(id -> processIdentifier(vertexIdProvider, id)).collect(Collectors.toSet()); // no need to execute query, only items in memory return combine(identifiers.stream().filter(vertices::containsKey).map(id -> (Vertex)vertices.get(id)), identifiers.stream().filter(transientVertexIndex::containsKey).map(id -> (Vertex)transientVertexIndex.get(id))); } // no need to execute query, all items in memory return combine(transientVertices.stream().map(vertex -> (Vertex)vertex), vertices.values().stream().map(vertex -> (Vertex)vertex)); } Stream<Vertex> vertices(Result result) { Objects.requireNonNull(result, "result cannot be null"); // create stream from result, skip deleted vertices return StreamSupport.stream(Spliterators.spliteratorUnknownSize(result, Spliterator.NONNULL | Spliterator.IMMUTABLE), false) .map(this::loadVertex) .filter(Objects::nonNull); } Iterator<Edge> edges(Object[] ids) { Objects.requireNonNull(ids, "ids cannot be null"); // verify identifiers verifyIdentifiers(Edge.class, ids); // check we have all edges already loaded if (!edgesLoaded) { // check ids if (ids.length > 0) { // parameters as a stream Set<Object> identifiers = Arrays.stream(ids).map(id -> processIdentifier(edgeIdProvider, id)).collect(Collectors.toSet()); // filter ids, remove ids already in memory (only ids that might exist on server) List<Object> filter = identifiers.stream().filter(id -> !edges.containsKey(id) && !transientEdgeIndex.containsKey(id)).collect(Collectors.toList()); // check we need to execute statement in server if (!filter.isEmpty()) { // change operator on single id filtering (performance optimization) if (filter.size() == 1) { // execute statement Result result = executeStatement("MATCH " + generateVertexMatchPattern("n") + "-[r]->" + generateVertexMatchPattern("m") + " WHERE " + edgeIdProvider.matchPredicateOperand("r") + " = $id" + (partition.usesMatchPredicate() ? " AND " + partition.vertexMatchPredicate("n") + " AND " + partition.vertexMatchPredicate("m") : "") + " RETURN n, r, m", Collections.singletonMap("id", filter.get(0))); // find edges Stream<Edge> query = edges(result); // combine stream from memory and query result Iterator<Edge> iterator = combine(Stream.concat(identifiers.stream().filter(edges::containsKey).map(id -> (Edge)edges.get(id)), identifiers.stream().filter(transientEdgeIndex::containsKey).map(id -> (Edge)transientEdgeIndex.get(id))), query); // process summary (query has been already consumed by combine) ResultSummaryLogger.log(result.consume()); // return iterator return iterator; } // execute statement Result result = executeStatement("MATCH " + generateVertexMatchPattern("n") + "-[r]->" + generateVertexMatchPattern("m") + " WHERE " + edgeIdProvider.matchPredicateOperand("r") + " in $ids" + (partition.usesMatchPredicate() ? " AND " + partition.vertexMatchPredicate("n") + " AND " + partition.vertexMatchPredicate("m") : "") + " RETURN n, r, m", Collections.singletonMap("ids", filter)); // find edges Stream<Edge> query = edges(result); // combine stream from memory and query result Iterator<Edge> iterator = combine(Stream.concat(identifiers.stream().filter(edges::containsKey).map(id -> (Edge)edges.get(id)), identifiers.stream().filter(transientEdgeIndex::containsKey).map(id -> (Edge)transientEdgeIndex.get(id))), query); // process summary (query has been already consumed by combine) ResultSummaryLogger.log(result.consume()); // return iterator return iterator; } // no need to execute query, only items in memory return combine(identifiers.stream().filter(edges::containsKey).map(id -> (Edge)edges.get(id)), identifiers.stream().filter(transientEdgeIndex::containsKey).map(id -> (Edge)transientEdgeIndex.get(id))); } // execute statement Result result = executeStatement("MATCH " + generateVertexMatchPattern("n") + "-[r]->" + generateVertexMatchPattern("m") + (partition.usesMatchPredicate() ? " WHERE " + partition.vertexMatchPredicate("n") + " AND " + partition.vertexMatchPredicate("m") : "") + " RETURN n, r, m", Collections.emptyMap()); // find edges Stream<Edge> query = edges(result); // combine stream from memory (transient) and query result Iterator<Edge> iterator = combine(transientEdges.stream().map(edge -> (Edge)edge), query); // process summary (query has been already consumed by combine) ResultSummaryLogger.log(result.consume()); // it is safe to update loaded flag at this time edgesLoaded = true; // return iterator return iterator; } // check ids if (ids.length > 0) { // parameters as a stream (set to remove duplicated ids) Set<Object> identifiers = Arrays.stream(ids).map(id -> processIdentifier(edgeIdProvider, id)).collect(Collectors.toSet()); // no need to execute query, only items in memory return combine(identifiers.stream().filter(edges::containsKey).map(id -> (Edge)edges.get(id)), identifiers.stream().filter(transientEdgeIndex::containsKey).map(id -> (Edge)transientEdgeIndex.get(id))); } // no need to execute query, all items in memory return combine(transientEdges.stream().map(edge -> (Edge)edge), edges.values().stream().map(edge -> (Edge)edge)); } Stream<Edge> edges(Result result) { Objects.requireNonNull(result, "result cannot be null"); // create stream from result, skip deleted edges return StreamSupport.stream(Spliterators.spliteratorUnknownSize(result, Spliterator.NONNULL | Spliterator.IMMUTABLE), false) .map(this::loadEdge) .filter(Objects::nonNull); } private static <T> Iterator<T> combine(Stream<T> collection, Stream<T> query) { // create a copy of first stream (state can be modified in the middle of the iteration) List<T> copy = collection.collect(Collectors.toCollection(LinkedList::new)); // iterate query and accumulate to list query.forEach(copy::add); // return iterator return copy.iterator(); } void removeEdge(Neo4JEdge edge, boolean explicit) { // edge id Object id = edge.id(); // check edge is transient if (transientEdges.contains(edge)) { // log information logger.debug("Deleting transient edge: {}", edge); // check explicit delete on edge if (explicit) { // remove references from adjacent vertices edge.vertices(Direction.BOTH).forEachRemaining(vertex -> { // remove from vertex ((Neo4JVertex)vertex).removeEdge(edge); }); } // remove it from transient set transientEdges.remove(edge); // remove it from index if (id != null) transientEdgeIndex.remove(id); } else { // log information logger.debug("Deleting edge: {}", edge); // mark edge as deleted (prevent returning edge in query results) deletedEdges.add(id); // check we need to execute delete statement on edge if (explicit) { // remove references from adjacent vertices edge.vertices(Direction.BOTH).forEachRemaining(vertex -> { // remove from vertex ((Neo4JVertex)vertex).removeEdge(edge); }); // add to delete queue edgeDeleteQueue.add(edge); } // remove it from update queue (avoid issuing MERGE command for an element that has been deleted) edgeUpdateQueue.remove(edge); } // remove edge from map edges.remove(id); } private static <T> void verifyIdentifiers(Class<T> elementClass, Object... ids) { // check length if (ids.length > 0) { // first element in array Object first = ids[0]; // first element class Class<?> firstClass = first.getClass(); // check it is an element if (elementClass.isAssignableFrom(firstClass)) { // all ids must be of the same class if (!Stream.of(ids).allMatch(id -> elementClass.isAssignableFrom(id.getClass()))) throw Graph.Exceptions.idArgsMustBeEitherIdOrElement(); } else if (!Stream.of(ids).map(Object::getClass).allMatch(firstClass::equals)) throw Graph.Exceptions.idArgsMustBeEitherIdOrElement(); } } private static Object processIdentifier(Neo4JElementIdProvider provider, Object id) { // vertex if (id instanceof Vertex) return ((Vertex)id).id(); // edge if (id instanceof Edge) return ((Edge)id).id(); // delegate processing to provider return provider.processIdentifier(id); } private Vertex loadVertex(Record record) { // node Node node = record.get(0).asNode(); // vertex id Object vertexId = vertexIdProvider.get(node); // check vertex has been deleted if (!deletedVertices.contains(vertexId)) { // check this vertex has been already loaded into this session Vertex vertex = vertices.get(vertexId); if (vertex == null) { // check node belongs to partition if (partition.containsVertex(StreamSupport.stream(node.labels().spliterator(), false).collect(Collectors.toSet()))) { // create and register vertex return registerVertex(new Neo4JVertex(graph, this, vertexIdProvider, edgeIdProvider, node)); } // skip vertex (not in partition) return null; } // return vertex return vertex; } // skip vertex (deleted) return null; } private Edge loadEdge(Record record) { // relationship Relationship relationship = record.get(1).asRelationship(); // edge id Object edgeId = edgeIdProvider.get(relationship); // check edge has been deleted if (!deletedEdges.contains(edgeId)) { // check we have record in memory Neo4JEdge edge = edges.get(edgeId); if (edge == null) { // nodes Node firstNode = record.get(0).asNode(); Node secondNode = record.get(2).asNode(); // node ids Object firstNodeId = vertexIdProvider.get(firstNode); Object secondNodeId = vertexIdProvider.get(secondNode); // check edge has been deleted (one of the vertices was deleted) or the vertices are not in the read partition if (deletedVertices.contains(firstNodeId) || deletedVertices.contains(secondNodeId) || !partition.containsVertex(StreamSupport.stream(firstNode.labels().spliterator(), false).collect(Collectors.toSet())) || !partition.containsVertex(StreamSupport.stream(secondNode.labels().spliterator(), false).collect(Collectors.toSet()))) return null; // check we have first vertex in memory Neo4JVertex firstVertex = vertices.get(firstNodeId); if (firstVertex == null) { // create vertex firstVertex = new Neo4JVertex(graph, this, vertexIdProvider, edgeIdProvider, firstNode); // register it registerVertex(firstVertex); } // check we have second vertex in memory Neo4JVertex secondVertex = vertices.get(secondNodeId); if (secondVertex == null) { // create vertex secondVertex = new Neo4JVertex(graph, this, vertexIdProvider, edgeIdProvider, secondNode); // register it registerVertex(secondVertex); } // find out start and end of the relationship (edge could come in either direction) Neo4JVertex out = relationship.startNodeId() == firstNode.id() ? firstVertex : secondVertex; Neo4JVertex in = relationship.endNodeId() == firstNode.id() ? firstVertex : secondVertex; // create edge edge = new Neo4JEdge(graph, this, edgeIdProvider, out, relationship, in); // register with adjacent vertices out.addOutEdge(edge); in.addInEdge(edge); // register edge return registerEdge(edge); } // return edge return edge; } // skip edge return null; } private Vertex registerVertex(Neo4JVertex vertex) { // map vertex vertices.put(vertex.id(), vertex); // return vertex return vertex; } private Edge registerEdge(Neo4JEdge edge) { // edge id Object id = edge.id(); // map edge edges.put(id, edge); // return vertex return edge; } void removeVertex(Neo4JVertex vertex) { // vertex id Object id = vertex.id(); // check vertex is transient if (transientVertices.contains(vertex)) { // log information logger.debug("Deleting transient vertex: {}", vertex); // remove it from transient set transientVertices.remove(vertex); // remove it from index if (id != null) transientVertexIndex.remove(id); } else { // log information logger.debug("Deleting vertex: {}", vertex); // mark vertex as deleted (prevent returning vertex in query results) deletedVertices.add(id); // add vertex to queue vertexDeleteQueue.add(vertex); // remove it from update queue (avoid issuing MERGE command for an element that has been deleted) vertexUpdateQueue.remove(vertex); // remove vertex from map vertices.remove(id); } } void dirtyVertex(Neo4JVertex vertex) { // check element is a transient one if (!transientVertices.contains(vertex)) { // add vertex to processing queue vertexUpdateQueue.add(vertex); } } void dirtyEdge(Neo4JEdge edge) { // check element is a transient one if (!transientEdges.contains(edge)) { // add edge to processing queue edgeUpdateQueue.add(edge); } } private void flush() { try { // delete edges deleteEdges(); // delete vertices deleteVertices(); // create vertices createVertices(); // create edges createEdges(); // update edges updateEdges(); // update vertices (after edges to be able to locate the vertex if referenced by an edge) updateVertices(); } catch (ClientException ex) { // log error if (logger.isErrorEnabled()) logger.error("Error committing transaction [{}]", transaction.hashCode(), ex); // throw original exception throw ex; } } private void createVertices() { // insert vertices for (Neo4JVertex vertex : transientVertices) { // create command Neo4JDatabaseCommand command = vertex.insertCommand(); // execute statement Result result = executeStatement(command.getStatement(), command.getParameters()); // process result command.getCallback().accept(result); // process summary ResultSummaryLogger.log(result.consume()); } } private void updateVertices() { // update vertices for (Neo4JVertex vertex : vertexUpdateQueue) { // create command Neo4JDatabaseCommand command = vertex.updateCommand(); if (command != null) { // execute statement Result result = executeStatement(command.getStatement(), command.getParameters()); // process result command.getCallback().accept(result); // process summary ResultSummaryLogger.log(result.consume()); } } } private void deleteVertices() { // delete vertices for (Neo4JVertex vertex : vertexDeleteQueue) { // create command Neo4JDatabaseCommand command = vertex.deleteCommand(); // execute statement Result result = executeStatement(command.getStatement(), command.getParameters()); // process result command.getCallback().accept(result); // process summary ResultSummaryLogger.log(result.consume()); } } private void createEdges() { // insert edges for (Neo4JEdge edge : transientEdges) { // create command Neo4JDatabaseCommand command = edge.insertCommand(); // execute statement Result result = executeStatement(command.getStatement(), command.getParameters()); // process result command.getCallback().accept(result); // process summary ResultSummaryLogger.log(result.consume()); } } private void updateEdges() { // update edges for (Neo4JEdge edge : edgeUpdateQueue) { // create command Neo4JDatabaseCommand command = edge.updateCommand(); if (command != null) { // execute statement Result result = executeStatement(command.getStatement(), command.getParameters()); // process result command.getCallback().accept(result); // process summary ResultSummaryLogger.log(result.consume()); } } } private void deleteEdges() { // delete edges for (Neo4JEdge edge : edgeDeleteQueue) { // create command Neo4JDatabaseCommand command = edge.deleteCommand(); // execute statement Result result = executeStatement(command.getStatement(), command.getParameters()); // process result command.getCallback().accept(result); // process summary ResultSummaryLogger.log(result.consume()); } } Result executeStatement(String statement, Map<String, Object> parameters) { try { // statement (we are modifying text) String cypherStatement = statement; // check we need to modify statement if (profilerEnabled) { // statement text String text = statement; if (text != null) { // use upper case text = text.toUpperCase(Locale.US); // check we can append PROFILE to current statement if (!text.startsWith("PROFILE") && !text.startsWith("EXPLAIN")) { // create new statement cypherStatement = "PROFILE " + statement; } } } // log information if (logger.isDebugEnabled()) logger.debug("Executing Cypher statement on transaction [{}]: {}", transaction.hashCode(), cypherStatement); // execute on transaction return transaction.run(cypherStatement, parameters); } catch (ClientException ex) { // log error if (logger.isErrorEnabled()) logger.error("Error executing Cypher statement on transaction [{}]", transaction.hashCode(), ex); // throw original exception throw ex; } } Result executeStatement(String statement, Value parameters) { try { // statement (we are modifying text) String cypherStatement = statement; // check we need to modify statement if (profilerEnabled) { // statement text String text = statement; if (text != null) { // use upper case text = text.toUpperCase(Locale.US); // check we can append PROFILE to current statement if (!text.startsWith("PROFILE") && !text.startsWith("EXPLAIN")) { // create new statement cypherStatement = "PROFILE " + statement; } } } // log information if (logger.isDebugEnabled()) logger.debug("Executing Cypher statement on transaction [{}]: {}", transaction.hashCode(), cypherStatement); // execute on transaction return transaction.run(cypherStatement, parameters); } catch (ClientException ex) { // log error if (logger.isErrorEnabled()) logger.error("Error executing Cypher statement on transaction [{}]", transaction.hashCode(), ex); // throw original exception throw ex; } } public void close() { // close transaction closeTransaction(); // log information if (logger.isDebugEnabled()) logger.debug("Closing neo4j session [{}]", session.hashCode()); // close session session.close(); } @Override @SuppressWarnings("checkstyle:NoFinalizer") protected void finalize() throws Throwable { // check session is open if (session.isOpen()) { // log information if (logger.isErrorEnabled()) logger.error("Finalizing Neo4JSession [{}] without explicit call to close(), the code is leaking sessions!", session.hashCode()); } // base implementation super.finalize(); } }