Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.steelbridgelabs.oss.neo4j.structure.Neo4JSession Maven / Gradle / Ivy
/*
* 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 vertices = new HashMap<>();
private final Map edges = new HashMap<>();
private final Set deletedVertices = new HashSet<>();
private final Set deletedEdges = new HashSet<>();
private final Set transientVertices = new HashSet<>();
private final Set transientEdges = new HashSet<>();
private final Map transientVertexIndex = new HashMap<>();
private final Map transientEdgeIndex = new HashMap<>();
private final Set vertexUpdateQueue = new HashSet<>();
private final Set edgeUpdateQueue = new HashSet<>();
private final Set vertexDeleteQueue = new HashSet<>();
private final Set 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 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 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 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 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 query = vertices(result);
// combine stream from memory and query result
Iterator 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 query = vertices(result);
// combine stream from memory and query result
Iterator 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 query = vertices(result);
// combine stream from memory (transient) and query result
Iterator 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 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 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 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 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 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 query = edges(result);
// combine stream from memory and query result
Iterator 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 query = edges(result);
// combine stream from memory and query result
Iterator 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 query = edges(result);
// combine stream from memory (transient) and query result
Iterator 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 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 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 Iterator combine(Stream collection, Stream query) {
// create a copy of first stream (state can be modified in the middle of the iteration)
List 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 void verifyIdentifiers(Class 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 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();
}
}