
org.neo4j.server.rest.transactional.TransactionHandle Maven / Gradle / Ivy
/*
* Copyright (c) 2002-2016 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.server.rest.transactional;
import java.io.IOException;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.neo4j.cypher.CypherException;
import org.neo4j.cypher.InvalidSemanticsException;
import org.neo4j.graphdb.Result;
import org.neo4j.graphdb.security.AuthorizationViolationException;
import org.neo4j.kernel.DeadlockDetectedException;
import org.neo4j.kernel.api.KernelTransaction.Type;
import org.neo4j.kernel.api.exceptions.KernelException;
import org.neo4j.kernel.api.exceptions.Status;
import org.neo4j.kernel.api.exceptions.TransactionFailureException;
import org.neo4j.kernel.api.security.AccessMode;
import org.neo4j.kernel.impl.query.QueryExecutionEngine;
import org.neo4j.kernel.impl.query.QueryExecutionKernelException;
import org.neo4j.kernel.impl.query.QuerySession;
import org.neo4j.logging.Log;
import org.neo4j.logging.LogProvider;
import org.neo4j.server.rest.transactional.error.InternalBeginTransactionError;
import org.neo4j.server.rest.transactional.error.Neo4jError;
import org.neo4j.server.rest.web.TransactionUriScheme;
import static org.neo4j.helpers.collection.Iterators.addToCollection;
/**
* Encapsulates executing statements in a transaction, committing the transaction, or rolling it back.
*
* Constructing a {@link TransactionHandle} does not immediately ask the kernel to create a
* {@link org.neo4j.kernel.api.KernelTransaction}; instead a {@link org.neo4j.kernel.api.KernelTransaction} is
* only created when the first statements need to be executed.
*
* At the end of each statement-executing method, the {@link org.neo4j.kernel.api.KernelTransaction} is either
* suspended (ready to be resumed by a later operation), or committed, or rolled back.
*
* If you acquire instances of this class from {@link TransactionHandleRegistry}, it will prevent concurrent access to
* the same instance. Therefore the implementation assumes that a single instance will only be accessed from
* a single thread.
*
* All of the public methods on this class are "single-shot"; once you have called one method, the handle returns
* itself
* to the registry. If you want to use it again, you'll need to acquire it back from the registry to ensure exclusive
* use.
*/
public class TransactionHandle implements TransactionTerminationHandle
{
private final TransitionalPeriodTransactionMessContainer txManagerFacade;
private final QueryExecutionEngine engine;
private final TransactionRegistry registry;
private final TransactionUriScheme uriScheme;
private final Type type;
private final AccessMode mode;
private final Log log;
private final long id;
private TransitionalTxManagementKernelTransaction context;
TransactionHandle( TransitionalPeriodTransactionMessContainer txManagerFacade, QueryExecutionEngine engine,
TransactionRegistry registry, TransactionUriScheme uriScheme, boolean implicitTransaction, AccessMode mode,
LogProvider logProvider )
{
this.txManagerFacade = txManagerFacade;
this.engine = engine;
this.registry = registry;
this.uriScheme = uriScheme;
this.type = implicitTransaction ? Type.implicit : Type.explicit;
this.mode = mode;
this.log = logProvider.getLog( getClass() );
this.id = registry.begin( this );
}
public URI uri()
{
return uriScheme.txUri( id );
}
public boolean isImplicit()
{
return type == Type.implicit;
}
public void execute( StatementDeserializer statements, ExecutionResultSerializer output,
HttpServletRequest request )
{
List errors = new LinkedList<>();
try
{
output.transactionCommitUri( uriScheme.txCommitUri( id ) );
ensureActiveTransaction();
execute( statements, output, errors, request );
}
catch ( InternalBeginTransactionError e )
{
errors.add( e.toNeo4jError() );
}
finally
{
output.errors( errors );
output.finish();
}
}
@Override
public boolean terminate()
{
if ( context != null )
{
context.terminate();
}
return true;
}
public void commit( StatementDeserializer statements, ExecutionResultSerializer output, HttpServletRequest request )
{
List errors = new LinkedList<>();
try
{
try
{
Statement peek = statements.peek();
if ( isImplicit() && peek == null ) /* JSON parse error */
{
addToCollection( statements.errors(), errors );
}
else
{
ensureActiveTransaction();
executeStatements( statements, output, errors, request );
closeContextAndCollectErrors( errors );
}
}
finally
{
registry.forget( id );
}
}
catch ( InternalBeginTransactionError e )
{
errors.add( e.toNeo4jError() );
}
catch ( CypherException e )
{
errors.add( new Neo4jError( e.status(), e ) );
throw e;
}
finally
{
output.errors( errors );
output.finish();
}
}
public void rollback( ExecutionResultSerializer output )
{
List errors = new LinkedList<>();
try
{
ensureActiveTransaction();
rollback( errors );
}
catch ( InternalBeginTransactionError e )
{
errors.add( e.toNeo4jError() );
}
finally
{
output.errors( errors );
output.finish();
}
}
void forceRollback() throws TransactionFailureException
{
context.resumeSinceTransactionsAreStillThreadBound();
context.rollback();
}
private void ensureActiveTransaction() throws InternalBeginTransactionError
{
if ( context == null )
{
try
{
context = txManagerFacade.newTransaction( type, mode );
}
catch ( RuntimeException e )
{
log.error( "Failed to start transaction.", e );
throw new InternalBeginTransactionError( e );
}
}
else
{
context.resumeSinceTransactionsAreStillThreadBound();
}
}
private void execute( StatementDeserializer statements, ExecutionResultSerializer output,
List errors, HttpServletRequest request )
{
executeStatements( statements, output, errors, request );
if ( Neo4jError.shouldRollBackOn( errors ) )
{
rollback( errors );
}
else
{
context.suspendSinceTransactionsAreStillThreadBound();
long lastActiveTimestamp = registry.release( id, this );
output.transactionStatus( lastActiveTimestamp );
}
}
private void closeContextAndCollectErrors( List errors )
{
if ( errors.isEmpty() )
{
try
{
context.commit();
}
catch ( Exception e )
{
if ( e.getCause() instanceof Status.HasStatus )
{
errors.add( new Neo4jError( ((Status.HasStatus) e.getCause()).status(), e ) );
}
else
{
log.error( "Failed to commit transaction.", e );
errors.add( new Neo4jError( Status.Transaction.TransactionCommitFailed, e ) );
}
}
}
else
{
try
{
context.rollback();
}
catch ( Exception e )
{
log.error( "Failed to rollback transaction.", e );
errors.add( new Neo4jError( Status.Transaction.TransactionRollbackFailed, e ) );
}
}
}
private void rollback( List errors )
{
try
{
context.rollback();
}
catch ( Exception e )
{
log.error( "Failed to rollback transaction.", e );
errors.add( new Neo4jError( Status.Transaction.TransactionRollbackFailed, e ) );
}
finally
{
registry.forget( id );
}
}
private void executeStatements( StatementDeserializer statements, ExecutionResultSerializer output,
List errors, HttpServletRequest request )
{
try
{
boolean hasPrevious = false;
while ( statements.hasNext() )
{
Statement statement = statements.next();
try
{
boolean hasPeriodicCommit = engine.isPeriodicCommit( statement.statement() );
if ( (statements.hasNext() || hasPrevious) && hasPeriodicCommit )
{
throw new QueryExecutionKernelException(
new InvalidSemanticsException( "Cannot execute another statement after executing " +
"PERIODIC COMMIT statement in the same transaction" ) );
}
if ( !hasPrevious && hasPeriodicCommit )
{
context.closeTransactionForPeriodicCommit();
}
hasPrevious = true;
QuerySession querySession = txManagerFacade.create( engine.queryService(), type, mode, request );
Result result = safelyExecute( statement, hasPeriodicCommit, querySession );
output.statementResult( result, statement.includeStats(), statement.resultDataContents() );
output.notifications( result.getNotifications() );
}
catch ( KernelException | CypherException | AuthorizationViolationException e )
{
errors.add( new Neo4jError( e.status(), e ) );
break;
}
catch ( DeadlockDetectedException e )
{
errors.add( new Neo4jError( Status.Transaction.DeadlockDetected, e ) );
}
catch ( IOException e )
{
errors.add( new Neo4jError( Status.Network.CommunicationError, e ) );
break;
}
catch ( Exception e )
{
Throwable cause = e.getCause();
if ( cause instanceof Status.HasStatus )
{
errors.add( new Neo4jError( ((Status.HasStatus) cause).status(), cause ) );
}
else
{
errors.add( new Neo4jError( Status.Statement.ExecutionFailed, e ) );
}
break;
}
}
addToCollection( statements.errors(), errors );
}
catch ( Throwable e )
{
errors.add( new Neo4jError( Status.General.UnknownError, e ) );
}
}
private Result safelyExecute( Statement statement, boolean hasPeriodicCommit, QuerySession querySession )
throws QueryExecutionKernelException
{
try
{
return engine.executeQuery( statement.statement(), statement.parameters(), querySession );
}
finally
{
if ( hasPeriodicCommit )
{
context.reopenAfterPeriodicCommit();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy