
org.neo4j.driver.internal.async.NetworkSession Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-java-driver Show documentation
Show all versions of neo4j-java-driver Show documentation
Access to the Neo4j graph database through Java
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* 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.
*/
package org.neo4j.driver.internal.async;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicBoolean;
import org.neo4j.driver.AccessMode;
import org.neo4j.driver.Bookmark;
import org.neo4j.driver.Logger;
import org.neo4j.driver.Logging;
import org.neo4j.driver.Query;
import org.neo4j.driver.TransactionConfig;
import org.neo4j.driver.async.ResultCursor;
import org.neo4j.driver.exceptions.ClientException;
import org.neo4j.driver.exceptions.TransactionNestingException;
import org.neo4j.driver.internal.BookmarkHolder;
import org.neo4j.driver.internal.DatabaseName;
import org.neo4j.driver.internal.FailableCursor;
import org.neo4j.driver.internal.ImpersonationUtil;
import org.neo4j.driver.internal.cursor.AsyncResultCursor;
import org.neo4j.driver.internal.cursor.ResultCursorFactory;
import org.neo4j.driver.internal.cursor.RxResultCursor;
import org.neo4j.driver.internal.logging.PrefixedLogger;
import org.neo4j.driver.internal.retry.RetryLogic;
import org.neo4j.driver.internal.spi.Connection;
import org.neo4j.driver.internal.spi.ConnectionProvider;
import org.neo4j.driver.internal.util.Futures;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static org.neo4j.driver.internal.util.Futures.completedWithNull;
public class NetworkSession
{
private final ConnectionProvider connectionProvider;
private final NetworkSessionConnectionContext connectionContext;
private final AccessMode mode;
private final RetryLogic retryLogic;
protected final Logger log;
private final BookmarkHolder bookmarkHolder;
private final long fetchSize;
private volatile CompletionStage transactionStage = completedWithNull();
private volatile CompletionStage connectionStage = completedWithNull();
private volatile CompletionStage extends FailableCursor> resultCursorStage = completedWithNull();
private final AtomicBoolean open = new AtomicBoolean( true );
public NetworkSession( ConnectionProvider connectionProvider, RetryLogic retryLogic, DatabaseName databaseName, AccessMode mode,
BookmarkHolder bookmarkHolder, String impersonatedUser, long fetchSize, Logging logging )
{
this.connectionProvider = connectionProvider;
this.mode = mode;
this.retryLogic = retryLogic;
this.log = new PrefixedLogger( "[" + hashCode() + "]", logging.getLog( getClass() ) );
this.bookmarkHolder = bookmarkHolder;
CompletableFuture databaseNameFuture = databaseName.databaseName()
.map( ignored -> CompletableFuture.completedFuture( databaseName ) )
.orElse( new CompletableFuture<>() );
this.connectionContext = new NetworkSessionConnectionContext( databaseNameFuture, bookmarkHolder.getBookmark(), impersonatedUser );
this.fetchSize = fetchSize;
}
public CompletionStage runAsync( Query query, TransactionConfig config )
{
CompletionStage newResultCursorStage =
buildResultCursorFactory( query, config ).thenCompose( ResultCursorFactory::asyncResult );
resultCursorStage = newResultCursorStage.exceptionally( error -> null );
return newResultCursorStage.thenCompose( AsyncResultCursor::mapSuccessfulRunCompletionAsync ).thenApply( cursor -> cursor ); // convert the return type
}
public CompletionStage runRx(Query query, TransactionConfig config )
{
CompletionStage newResultCursorStage =
buildResultCursorFactory( query, config ).thenCompose( ResultCursorFactory::rxResult );
resultCursorStage = newResultCursorStage.exceptionally( error -> null );
return newResultCursorStage;
}
public CompletionStage beginTransactionAsync( TransactionConfig config )
{
return this.beginTransactionAsync( mode, config );
}
public CompletionStage beginTransactionAsync( AccessMode mode, TransactionConfig config )
{
ensureSessionIsOpen();
// create a chain that acquires connection and starts a transaction
CompletionStage newTransactionStage = ensureNoOpenTxBeforeStartingTx()
.thenCompose( ignore -> acquireConnection( mode ) )
.thenApply( connection -> ImpersonationUtil.ensureImpersonationSupport( connection, connection.impersonatedUser() ) )
.thenCompose( connection ->
{
UnmanagedTransaction tx = new UnmanagedTransaction( connection, bookmarkHolder, fetchSize );
return tx.beginAsync( bookmarkHolder.getBookmark(), config );
} );
// update the reference to the only known transaction
CompletionStage currentTransactionStage = transactionStage;
transactionStage = newTransactionStage
.exceptionally( error -> null ) // ignore errors from starting new transaction
.thenCompose( tx ->
{
if ( tx == null )
{
// failed to begin new transaction, keep reference to the existing one
return currentTransactionStage;
}
// new transaction started, keep reference to it
return completedFuture( tx );
} );
return newTransactionStage;
}
public CompletionStage resetAsync()
{
return existingTransactionOrNull()
.thenAccept( tx ->
{
if ( tx != null )
{
tx.markTerminated( null );
}
} )
.thenCompose( ignore -> connectionStage )
.thenCompose( connection ->
{
if ( connection != null )
{
// there exists an active connection, send a RESET message over it
return connection.reset();
}
return completedWithNull();
} );
}
public RetryLogic retryLogic()
{
return retryLogic;
}
public Bookmark lastBookmark()
{
return bookmarkHolder.getBookmark();
}
public CompletionStage releaseConnectionAsync()
{
return connectionStage.thenCompose( connection ->
{
if ( connection != null )
{
// there exists connection, try to release it back to the pool
return connection.release();
}
// no connection so return null
return completedWithNull();
} );
}
public CompletionStage connectionAsync()
{
return connectionStage;
}
public boolean isOpen()
{
return open.get();
}
public CompletionStage closeAsync()
{
if ( open.compareAndSet( true, false ) )
{
return resultCursorStage.thenCompose( cursor ->
{
if ( cursor != null )
{
// there exists a cursor with potentially unconsumed error, try to extract and propagate it
return cursor.discardAllFailureAsync();
}
// no result cursor exists so no error exists
return completedWithNull();
} ).thenCompose( cursorError -> closeTransactionAndReleaseConnection().thenApply( txCloseError ->
{
// now we have cursor error, active transaction has been closed and connection has been released
// back to the pool; try to propagate cursor and transaction close errors, if any
CompletionException combinedError = Futures.combineErrors( cursorError, txCloseError );
if ( combinedError != null )
{
throw combinedError;
}
return null;
} ) );
}
return completedWithNull();
}
protected CompletionStage currentConnectionIsOpen()
{
return connectionStage.handle( ( connection, error ) ->
error == null && // no acquisition error
connection != null && // some connection has actually been acquired
connection.isOpen() ); // and it's still open
}
private CompletionStage buildResultCursorFactory( Query query, TransactionConfig config )
{
ensureSessionIsOpen();
return ensureNoOpenTxBeforeRunningQuery()
.thenCompose( ignore -> acquireConnection( mode ) )
.thenApply( connection -> ImpersonationUtil.ensureImpersonationSupport( connection, connection.impersonatedUser() ) )
.thenCompose(
connection ->
{
try
{
ResultCursorFactory factory = connection
.protocol()
.runInAutoCommitTransaction( connection, query, bookmarkHolder, config, fetchSize );
return completedFuture( factory );
}
catch ( Throwable e )
{
return Futures.failedFuture( e );
}
} );
}
private CompletionStage acquireConnection( AccessMode mode )
{
CompletionStage currentConnectionStage = connectionStage;
CompletionStage newConnectionStage = resultCursorStage.thenCompose( cursor ->
{
if ( cursor == null )
{
return completedWithNull();
}
// make sure previous result is fully consumed and connection is released back to the pool
return cursor.pullAllFailureAsync();
} ).thenCompose( error ->
{
if ( error == null )
{
// there is no unconsumed error, so one of the following is true:
// 1) this is first time connection is acquired in this session
// 2) previous result has been successful and is fully consumed
// 3) previous result failed and error has been consumed
// return existing connection, which should've been released back to the pool by now
return currentConnectionStage.exceptionally( ignore -> null );
}
else
{
// there exists unconsumed error, re-throw it
throw new CompletionException( error );
}
} ).thenCompose( existingConnection ->
{
if ( existingConnection != null && existingConnection.isOpen() )
{
// there somehow is an existing open connection, this should not happen, just a precondition
throw new IllegalStateException( "Existing open connection detected" );
}
return connectionProvider.acquireConnection( connectionContext.contextWithMode( mode ) );
} );
connectionStage = newConnectionStage.exceptionally( error -> null );
return newConnectionStage;
}
private CompletionStage closeTransactionAndReleaseConnection()
{
return existingTransactionOrNull().thenCompose( tx ->
{
if ( tx != null )
{
// there exists an open transaction, let's close it and propagate the error, if any
return tx.closeAsync()
.thenApply( ignore -> (Throwable) null )
.exceptionally( error -> error );
}
// no open transaction so nothing to close
return completedWithNull();
} ).thenCompose( txCloseError ->
// then release the connection and propagate transaction close error, if any
releaseConnectionAsync().thenApply( ignore -> txCloseError ) );
}
private CompletionStage ensureNoOpenTxBeforeRunningQuery()
{
return ensureNoOpenTx( "Queries cannot be run directly on a session with an open transaction; " +
"either run from within the transaction or use a different session." );
}
private CompletionStage ensureNoOpenTxBeforeStartingTx()
{
return ensureNoOpenTx( "You cannot begin a transaction on a session with an open transaction; " +
"either run from within the transaction or use a different session." );
}
private CompletionStage ensureNoOpenTx( String errorMessage )
{
return existingTransactionOrNull().thenAccept( tx ->
{
if ( tx != null )
{
throw new TransactionNestingException( errorMessage );
}
} );
}
private CompletionStage existingTransactionOrNull()
{
return transactionStage
.exceptionally( error -> null ) // handle previous connection acquisition and tx begin failures
.thenApply( tx -> tx != null && tx.isOpen() ? tx : null );
}
private void ensureSessionIsOpen()
{
if ( !open.get() )
{
throw new ClientException(
"No more interaction with this session are allowed as the current session is already closed. " );
}
}
/**
* The {@link NetworkSessionConnectionContext#mode} can be mutable for a session connection context
*/
private static class NetworkSessionConnectionContext implements ConnectionContext
{
private final CompletableFuture databaseNameFuture;
private AccessMode mode;
// This bookmark is only used for rediscovery.
// It has to be the initial bookmark given at the creation of the session.
// As only that bookmark could carry extra system bookmarks
private final Bookmark rediscoveryBookmark;
private final String impersonatedUser;
private NetworkSessionConnectionContext( CompletableFuture databaseNameFuture, Bookmark bookmark, String impersonatedUser )
{
this.databaseNameFuture = databaseNameFuture;
this.rediscoveryBookmark = bookmark;
this.impersonatedUser = impersonatedUser;
}
private ConnectionContext contextWithMode( AccessMode mode )
{
this.mode = mode;
return this;
}
@Override
public CompletableFuture databaseNameFuture()
{
return databaseNameFuture;
}
@Override
public AccessMode mode()
{
return mode;
}
@Override
public Bookmark rediscoveryBookmark()
{
return rediscoveryBookmark;
}
@Override
public String impersonatedUser()
{
return impersonatedUser;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy