/* * 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.http.dialect.impl; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.hibernate.boot.model.relational.Sequence; import org.hibernate.ogm.datastore.neo4j.dialect.impl.NodeLabel; import org.hibernate.ogm.datastore.neo4j.dialect.impl.RemoteNeo4jSequenceGenerator; import org.hibernate.ogm.datastore.neo4j.logging.impl.Log; import org.hibernate.ogm.datastore.neo4j.logging.impl.LoggerFactory; import org.hibernate.ogm.datastore.neo4j.remote.common.request.impl.RemoteStatement; import org.hibernate.ogm.datastore.neo4j.remote.common.request.impl.RemoteStatements; import org.hibernate.ogm.datastore.neo4j.remote.http.impl.HttpNeo4jClient; import org.hibernate.ogm.datastore.neo4j.remote.http.json.impl.ErrorResponse; import org.hibernate.ogm.datastore.neo4j.remote.http.json.impl.Statement; import org.hibernate.ogm.datastore.neo4j.remote.http.json.impl.StatementResult; import org.hibernate.ogm.datastore.neo4j.remote.http.json.impl.Statements; import org.hibernate.ogm.datastore.neo4j.remote.http.json.impl.StatementsResponse; import org.hibernate.ogm.id.impl.OgmSequenceGenerator; import org.hibernate.ogm.id.impl.OgmTableGenerator; import org.hibernate.ogm.model.key.spi.IdSourceKey; import org.hibernate.ogm.model.key.spi.IdSourceKeyMetadata; import org.hibernate.ogm.model.key.spi.IdSourceKeyMetadata.IdSourceType; /** * Generates the next value of an id sequence as represented by {@link IdSourceKey}. * <p> * Both, {@link IdSourceType#TABLE} and {@link IdSourceType#SEQUENCE} are supported. For the table strategy, nodes in * the following form are used (the exact property names and the label value can be configured using the options exposed * by {@link OgmTableGenerator}): * * <pre> * (:hibernate_sequences:TABLE_BASED_SEQUENCE { sequence_name = 'ExampleSequence', current_value : 3 }) * </pre> * * For the sequence strategy, nodes in the following form are used (the sequence name can be configured using the option * exposed by {@link OgmSequenceGenerator}): * * <pre> * (:SEQUENCE { sequence_name = 'ExampleSequence', next_val : 3 }) * </pre> * * Sequences are created at startup. * <p> * A write lock is acquired on the node every time the sequence needs to be updated. * * @author Davide D'Alto <davide@hibernate.org> * @author Gunnar Morling */ public class HttpNeo4jSequenceGenerator extends RemoteNeo4jSequenceGenerator { private static final Log log = LoggerFactory.getLogger(); private final HttpNeo4jClient client; public HttpNeo4jSequenceGenerator(HttpNeo4jClient neo4jDb, int sequenceCacheMaxSize) { super( sequenceCacheMaxSize ); this.client = neo4jDb; } /** * Create the sequence nodes setting the initial value if the node does not exist already. * <p> * All nodes are created inside the same transaction. */ private void createSequencesConstraintsStatements(Statements statements, Iterable<Sequence> sequences) { Statement statement = createUniqueConstraintStatement( SEQUENCE_NAME_PROPERTY, NodeLabel.SEQUENCE.name() ); statements.addStatement( statement ); } /** * Adds a node for each generator of type {@link IdSourceType#SEQUENCE}. Table-based generators are created lazily * at runtime. * * @param sequences the generators to process */ private Statements createSequencesStatements(Iterable<Sequence> sequences) { Statements statements = new Statements(); for ( Sequence sequence : sequences ) { addSequence( statements, sequence ); } return statements; } private void addSequence(Statements statements, Sequence sequence) { Statement statement = new Statement( SEQUENCE_CREATION_QUERY, Collections.<String, Object>singletonMap( SEQUENCE_NAME_QUERY_PARAM, sequence.getName().render() ) ); statements.addStatement( statement ); } /** * Adds a unique constraint to make sure that each node of the same "sequence table" is unique. */ private void addUniqueConstraintForTableBasedSequence(Statements statements, IdSourceKeyMetadata generatorKeyMetadata) { Statement statement = createUniqueConstraintStatement( generatorKeyMetadata.getKeyColumnName(), generatorKeyMetadata.getName() ); statements.addStatement( statement ); } private Statement createUniqueConstraintStatement(String propertyName, String label) { String queryString = createUniqueConstraintQuery( propertyName, label ); Statement statement = new Statement( queryString ); return statement; } public void createUniqueConstraintsForTableSequences(Statements statements, Iterable<IdSourceKeyMetadata> tableIdGenerators) { for ( IdSourceKeyMetadata idSourceKeyMetadata : tableIdGenerators ) { if ( idSourceKeyMetadata.getType() == IdSourceType.TABLE ) { addUniqueConstraintForTableBasedSequence( statements, idSourceKeyMetadata ); } } } protected Number nextValue(RemoteStatements remoteStatements) { Statements statements = convert( remoteStatements ); List<StatementResult> results = client.executeQueriesInNewTransaction( statements ).getResults(); // We use 1, because we are interested to the result of the second statement (the one that updates the node and returns the value) Number nextValue = (Number) results.get( 1 ).getData().get( 0 ).getRow().get( 0 ); return nextValue; } private Statements convert(RemoteStatements remoteStatements) { Statements statements = new Statements(); for ( RemoteStatement remoteStatement : remoteStatements ) { Statement statement = new Statement(); statement.setStatement( remoteStatement.getQuery() ); statement.setParameters( remoteStatement.getParams() ); if ( remoteStatement.isAsRow() ) { statement.setResultDataContents( Arrays.asList( Statement.AS_ROW ) ); } statements.addStatement( statement ); } return statements; } @Override public void createSequences(List<Sequence> sequences, Iterable<IdSourceKeyMetadata> idSourceKeyMetadata) { createUniqueConstraints( sequences, idSourceKeyMetadata ); Statements statements = createSequencesStatements( sequences ); StatementsResponse response = client.executeQueriesInNewTransaction( statements ); validateSequencesCreation( response ); } private void createUniqueConstraints(List<Sequence> sequences, Iterable<IdSourceKeyMetadata> idSourceKeyMetadata) { Statements statements = new Statements(); createSequencesConstraintsStatements( statements, sequences ); createUniqueConstraintsForTableSequences( statements, idSourceKeyMetadata ); StatementsResponse response = client.executeQueriesInNewTransaction( statements ); validateConstraintsCreation( response ); } private void validateSequencesCreation(StatementsResponse response) { if ( !response.getErrors().isEmpty() ) { ErrorResponse errorResponse = response.getErrors().get( 0 ); throw log.sequencesCreationException( errorResponse.getCode(), errorResponse.getMessage() ); } } private void validateConstraintsCreation(StatementsResponse response) { if ( !response.getErrors().isEmpty() ) { ErrorResponse errorResponse = response.getErrors().get( 0 ); throw log.constraintsCreationException( errorResponse.getCode(), errorResponse.getMessage() ); } } }