/*
* Hibernate OGM, Domain model persistence for NoSQL datastores
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.ogm.datastore.neo4j.remote.bolt.dialect.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.hibernate.ogm.datastore.neo4j.dialect.impl.BaseNeo4jEntityQueries;
import org.hibernate.ogm.datastore.neo4j.remote.common.dialect.impl.RemoteNeo4jAssociationPropertiesRow;
import org.hibernate.ogm.datastore.neo4j.remote.common.util.impl.RemoteNeo4jHelper;
import org.hibernate.ogm.dialect.query.spi.ClosableIterator;
import org.hibernate.ogm.dialect.spi.TupleTypeContext;
import org.hibernate.ogm.model.key.spi.EntityKey;
import org.hibernate.ogm.model.key.spi.EntityKeyMetadata;
import org.hibernate.ogm.util.impl.ArrayHelper;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.Record;
import org.neo4j.driver.v1.Statement;
import org.neo4j.driver.v1.StatementResult;
import org.neo4j.driver.v1.Transaction;
import org.neo4j.driver.v1.Value;
import org.neo4j.driver.v1.types.Node;
import org.neo4j.driver.v1.types.Relationship;
/**
* @author Davide D'Alto
*/
public class BoltNeo4jEntityQueries extends BaseNeo4jEntityQueries {
private static final Iterator<NodeWithEmbeddedNodes> EMPTY_NODES_ITERATOR = ( Collections.<NodeWithEmbeddedNodes>emptyList() ).iterator();
private static final Iterator<RemoteNeo4jAssociationPropertiesRow> EMPTY_RELATIONSHIPS_ITERATOR = ( Collections
.<RemoteNeo4jAssociationPropertiesRow>emptyList() ).iterator();
private static final ClosableIteratorAdapter<NodeWithEmbeddedNodes> EMPTY_NODES = new ClosableIteratorAdapter<>( EMPTY_NODES_ITERATOR );
private static final ClosableIteratorAdapter<RemoteNeo4jAssociationPropertiesRow> EMPTY_RELATIONSHIPS = new ClosableIteratorAdapter<>(
EMPTY_RELATIONSHIPS_ITERATOR );
public BoltNeo4jEntityQueries(EntityKeyMetadata entityKeyMetadata, TupleTypeContext tupleTypeContext) {
super( entityKeyMetadata, tupleTypeContext, true );
}
public NodeWithEmbeddedNodes findEntity(Transaction tx, Object[] columnValues) {
String findQuery = getFindEntityWithEmbeddedEndNodeQuery();
Map<String, Object> params = params( columnValues );
Statement statement = new Statement( findQuery, params );
StatementResult queryResult = tx.run( statement );
ClosableIterator<NodeWithEmbeddedNodes> closableIterator = null;
try {
closableIterator = closableIterator( queryResult );
if ( closableIterator.hasNext() ) {
return closableIterator.next();
}
return null;
}
finally {
close( closableIterator );
}
}
private void close(ClosableIterator<?> closableIterator) {
if ( closableIterator != null ) {
closableIterator.close();
}
}
public ClosableIterator<NodeWithEmbeddedNodes> findEntitiesWithEmbedded(Transaction tx) {
StatementResult results = tx.run( getFindEntitiesQuery() );
return closableIterator( results );
}
public ClosableIterator<NodeWithEmbeddedNodes> findEntities(EntityKey[] keys, Transaction tx) {
if ( singlePropertyKey ) {
return singlePropertyIdFindEntities( keys, tx );
}
else {
return multiPropertiesIdFindEntities( keys, tx );
}
}
/*
* When the id is mapped on several properties
*/
private ClosableIterator<NodeWithEmbeddedNodes> multiPropertiesIdFindEntities(EntityKey[] keys, Transaction tx) {
String query = getMultiGetQueryCacheQuery( keys );
Map<String, Object> params = multiGetParams( keys );
StatementResult results = tx.run( query, params );
return closableIterator( results );
}
/*
* When the id is mapped with a single property
*/
private ClosableIterator<NodeWithEmbeddedNodes> singlePropertyIdFindEntities(EntityKey[] keys, Transaction tx) {
Object[] paramsValues = new Object[keys.length];
for ( int i = 0; i < keys.length; i++ ) {
paramsValues[i] = keys[i].getColumnValues()[0];
}
Map<String, Object> params = Collections.singletonMap( "0", (Object) paramsValues );
Statement statement = new Statement( multiGetQuery, params );
StatementResult statementResult = tx.run( statement );
return closableIterator( statementResult, keys );
}
private ClosableIterator<NodeWithEmbeddedNodes> closableIterator(StatementResult results) {
return closableIterator( results, null );
}
private ClosableIterator<NodeWithEmbeddedNodes> closableIterator(StatementResult results, EntityKey[] keys) {
if ( results != null && results.hasNext() ) {
List<Node> owners = new ArrayList<>();
Map<Long, Map<String, Collection<Node>>> nodes = new HashMap<>();
while ( results.hasNext() ) {
Record record = results.next();
Node owner = record.get( BoltNeo4jEntityQueries.ENTITY_ALIAS ).asNode();
Value value = record.get( BoltNeo4jEntityQueries.EMBEDDED_ALIAS );
Node embeddedNode = value.isNull() ? null : value.asNode();
Map<String, Collection<Node>> embeddedNodesMap = nodes.get( owner.id() );
if ( embeddedNodesMap == null ) {
embeddedNodesMap = new HashMap<>();
nodes.put( owner.id(), embeddedNodesMap );
owners.add( owner );
}
if ( embeddedNode != null ) {
StringBuilder builder = new StringBuilder();
List<Object> embeddedObjects = asList( record );
for ( Object object : embeddedObjects ) {
builder.append( "." );
builder.append( ( (Relationship) object ).type() );
}
String path = builder.substring( 1 );
if ( !embeddedNodesMap.containsKey( path ) ) {
Collection<Node> embeddedNodes = new ArrayList<>();
embeddedNodesMap.put( path, embeddedNodes );
}
embeddedNodesMap.get( path ).add( embeddedNode );
}
}
if ( keys == null ) {
List<NodeWithEmbeddedNodes> nodeWithEmbeddeds = new ArrayList<>();
for ( Node owner : owners ) {
nodeWithEmbeddeds.add( new NodeWithEmbeddedNodes( owner, nodes.get( owner.id() ) ) );
}
return new ClosableIteratorAdapter<>( nodeWithEmbeddeds.iterator() );
}
else {
NodeWithEmbeddedNodes[] array = new NodeWithEmbeddedNodes[keys.length];
for ( Node owner : owners ) {
int index = findKeyIndex( keys, owner );
if ( index > -1 ) {
array[index] = new NodeWithEmbeddedNodes( owner, nodes.get( owner.id() ) );
}
}
List<NodeWithEmbeddedNodes> nullRemoved = new ArrayList<>();
for ( NodeWithEmbeddedNodes node : array ) {
if ( node != null ) {
nullRemoved.add( node );
}
}
return new ClosableIteratorAdapter<>( nullRemoved.iterator() );
}
}
return EMPTY_NODES;
}
private int findKeyIndex(EntityKey[] keys, Node owner) {
for ( int i = 0; i < keys.length; i++ ) {
if ( RemoteNeo4jHelper.matches( owner.asMap(), keys[i].getColumnNames(), keys[i].getColumnValues() ) ) {
return i;
}
}
return -1;
}
public Node findAssociatedEntity(Transaction tx, Object[] keyValues, String associationrole) {
Map<String, Object> params = params( keyValues );
String query = getFindAssociatedEntityQuery( associationrole );
if ( query != null ) {
StatementResult statementResult = tx.run( query, params );
if ( statementResult.hasNext() ) {
return statementResult.single().get( 0 ).asNode();
}
}
return null;
}
public Statement getCreateEntityWithPropertiesQueryStatement(Object[] columnValues, Map<String, Object> properties) {
String query = getCreateEntityWithPropertiesQuery();
Map<String, Object> params = Collections.singletonMap( "props", (Object) properties );
return new Statement( query, params );
}
public Statement removeEmbeddedColumnStatement(Object[] keyValues, String embeddedColumn, Transaction transaction) {
String query = getRemoveEmbeddedPropertyQuery().get( embeddedColumn );
Map<String, Object> params = params( keyValues );
return new Statement( query, params );
}
public Statement removeColumnStatement(Object[] columnValues, String column, Transaction transaction) {
String query = getRemoveColumnQuery( column );
Map<String, Object> params = params( columnValues );
return new Statement( query, params );
}
public void removeToOneAssociation(Transaction tx, Object[] columnValues, String associationRole) {
Map<String, Object> params = params( ArrayHelper.concat( columnValues, associationRole ) );
tx.run( getRemoveToOneAssociation(), params );
}
public Statement getUpdateEntityPropertiesStatement(Object[] columnValues, Map<String, Object> properties) {
String query = getUpdateEntityPropertiesQuery( properties );
Object[] paramsValues = ArrayHelper.concat( Arrays.asList( columnValues, new Object[properties.size()] ) );
int index = columnValues.length;
for ( Map.Entry<String, Object> entry : properties.entrySet() ) {
paramsValues[index++] = entry.getValue();
}
return new Statement( query, params( paramsValues ) );
}
public Statement updateEmbeddedColumnStatement(Object[] keyValues, String column, Object value) {
String query = getUpdateEmbeddedColumnQuery( keyValues, column );
Map<String, Object> params = params( ArrayHelper.concat( keyValues, value, value ) );
return new Statement( query, params );
}
public Node findAssociatedEntity(Driver driver, Object[] keyValues, String associationrole) {
return null;
}
public Statement getUpdateOneToOneAssociationStatement(String associationRole, Object[] ownerKeyValues, Object[] targetKeyValues) {
String query = getUpdateToOneQuery( associationRole );
Map<String, Object> params = params( ownerKeyValues );
params.putAll( params( targetKeyValues, ownerKeyValues.length ) );
return new Statement( query, params );
}
public void removeEntity(Transaction transaction, Object[] columnValues) {
transaction.run( getRemoveEntityQuery(), params( columnValues ) );
}
public ClosableIterator<RemoteNeo4jAssociationPropertiesRow> findAssociation(Transaction tx, Object[] columnValues, String role) {
// Find the target node
String queryForAssociation = getFindAssociationQuery( role );
// Find the embedded properties of the target node
String queryForEmbedded = getFindAssociationTargetEmbeddedValues( role );
// Execute the queries
Map<String, Object> params = params( columnValues );
Statement associationStatement = new Statement( queryForAssociation, params );
StatementResult associationResult = tx.run( associationStatement );
Statement embeddedStatement = new Statement( queryForEmbedded, params );
StatementResult embeddedResult = tx.run( embeddedStatement );
List<RemoteNeo4jAssociationPropertiesRow> responseRows = new ArrayList<>();
Record embeddedRecord = null;
while ( associationResult.hasNext() ) {
Record record = associationResult.next();
Object idTarget = record.get( 0 );
Map<String, Object> ownerNode = record.get( ENTITY_ALIAS ).asMap();
Map<String, Object> targetNode = new HashMap<>( record.get( "target" ).asMap() );
Map<String, Object> rel = record.get( "r" ).asMap();
while ( embeddedResult.hasNext() || embeddedRecord != null ) {
if ( embeddedRecord == null ) {
embeddedRecord = embeddedResult.next();
}
Object embeddedOwnerId = embeddedRecord.get( 0 );
if ( embeddedOwnerId.equals( idTarget ) ) {
addTargetEmbeddedProperties( targetNode, embeddedRecord );
if ( embeddedResult.hasNext() ) {
embeddedRecord = embeddedResult.next();
}
else {
embeddedRecord = null;
}
}
else {
break;
}
}
RemoteNeo4jAssociationPropertiesRow associationPropertiesRow = new RemoteNeo4jAssociationPropertiesRow( rel, ownerNode, targetNode );
responseRows.add( associationPropertiesRow );
}
if ( responseRows.isEmpty() ) {
return EMPTY_RELATIONSHIPS;
}
return new ClosableIteratorAdapter<>( responseRows.iterator() );
}
private List<Object> asList(Record embeddeds) {
if ( embeddeds.get( 1 ).isNull() ) {
return Collections.emptyList();
}
return embeddeds.get( 1 ).asList();
}
private void addTargetEmbeddedProperties(Map<String, Object> targetNode, Record row) {
if ( !row.get( 1 ).isNull() ) {
List<Object> pathToNode = row.get( 1 ).asList();
Map<String, Object> embeddedNodeProperties = (Map<String, Object>) row.get( 3 ).asMap();
String path = concat( pathToNode );
for ( Map.Entry<String, Object> entry : embeddedNodeProperties.entrySet() ) {
targetNode.put( path + "." + entry.getKey(), entry.getValue() );
}
}
}
private String concat(List<?> pathToNode) {
StringBuilder path = new StringBuilder();
for ( Object entry : pathToNode ) {
path.append( "." );
path.append( entry );
}
return path.substring( 1 );
}
private static class ClosableIteratorAdapter<T> implements ClosableIterator<T> {
private final Iterator<T> iterator;
public ClosableIteratorAdapter(Iterator<T> iterator) {
this.iterator = iterator;
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public T next() {
return iterator.next();
}
@Override
public void close() {
}
@Override
public void remove() {
}
}
}