diff --git a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java
new file mode 100644
index 0000000000..022a456947
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2002-2016 "Neo Technology,"
+ * Network Engine for Objects in Lund AB [http://neotechnology.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;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.GeneralSecurityException;
+
+import org.neo4j.driver.internal.cluster.RoutingSettings;
+import org.neo4j.driver.internal.net.BoltServerAddress;
+import org.neo4j.driver.internal.net.SocketConnector;
+import org.neo4j.driver.internal.net.pooling.PoolSettings;
+import org.neo4j.driver.internal.net.pooling.SocketConnectionPool;
+import org.neo4j.driver.internal.security.SecurityPlan;
+import org.neo4j.driver.internal.spi.ConnectionPool;
+import org.neo4j.driver.internal.spi.Connector;
+import org.neo4j.driver.internal.util.Clock;
+import org.neo4j.driver.v1.AuthToken;
+import org.neo4j.driver.v1.AuthTokens;
+import org.neo4j.driver.v1.Config;
+import org.neo4j.driver.v1.Driver;
+import org.neo4j.driver.v1.Logger;
+import org.neo4j.driver.v1.exceptions.ClientException;
+
+import static java.lang.String.format;
+import static org.neo4j.driver.internal.security.SecurityPlan.insecure;
+import static org.neo4j.driver.v1.Config.EncryptionLevel.REQUIRED;
+
+public class DriverFactory
+{
+ public final Driver newInstance( URI uri, AuthToken authToken, RoutingSettings routingSettings, Config config )
+ {
+ BoltServerAddress address = BoltServerAddress.from( uri );
+ SecurityPlan securityPlan = createSecurityPlan( address, config );
+ ConnectionPool connectionPool = createConnectionPool( authToken, securityPlan, config );
+
+ try
+ {
+ return createDriver( address, uri.getScheme(), connectionPool, config, routingSettings, securityPlan );
+ }
+ catch ( Throwable driverError )
+ {
+ // we need to close the connection pool if driver creation threw exception
+ try
+ {
+ connectionPool.close();
+ }
+ catch ( Throwable closeError )
+ {
+ driverError.addSuppressed( closeError );
+ }
+ throw driverError;
+ }
+ }
+
+ private Driver createDriver( BoltServerAddress address, String scheme, ConnectionPool connectionPool,
+ Config config, RoutingSettings routingSettings, SecurityPlan securityPlan )
+ {
+ switch ( scheme.toLowerCase() )
+ {
+ case "bolt":
+ return createDirectDriver( address, connectionPool, config, securityPlan );
+ case "bolt+routing":
+ return createRoutingDriver( address, connectionPool, config, routingSettings, securityPlan );
+ default:
+ throw new ClientException( format( "Unsupported URI scheme: %s", scheme ) );
+ }
+ }
+
+ /**
+ * Creates new {@link DirectDriver}.
+ *
+ * This method is package-private only for testing
+ */
+ DirectDriver createDirectDriver( BoltServerAddress address, ConnectionPool connectionPool, Config config,
+ SecurityPlan securityPlan )
+ {
+ return new DirectDriver( address, connectionPool, securityPlan, config.logging() );
+ }
+
+ /**
+ * Creates new {@link RoutingDriver}.
+ *
+ * This method is package-private only for testing
+ */
+ RoutingDriver createRoutingDriver( BoltServerAddress address, ConnectionPool connectionPool,
+ Config config, RoutingSettings routingSettings, SecurityPlan securityPlan )
+ {
+ return new RoutingDriver( routingSettings, address, connectionPool, securityPlan, Clock.SYSTEM,
+ config.logging() );
+ }
+
+ /**
+ * Creates new {@link ConnectionPool}.
+ *
+ * This method is package-private only for testing
+ */
+ ConnectionPool createConnectionPool( AuthToken authToken, SecurityPlan securityPlan, Config config )
+ {
+ authToken = authToken == null ? AuthTokens.none() : authToken;
+
+ ConnectionSettings connectionSettings = new ConnectionSettings( authToken );
+ PoolSettings poolSettings = new PoolSettings( config.maxIdleConnectionPoolSize() );
+ Connector connector = new SocketConnector( connectionSettings, securityPlan, config.logging() );
+
+ return new SocketConnectionPool( poolSettings, connector, Clock.SYSTEM, config.logging() );
+ }
+
+ private static SecurityPlan createSecurityPlan( BoltServerAddress address, Config config )
+ {
+ try
+ {
+ return createSecurityPlanImpl( address, config );
+ }
+ catch ( GeneralSecurityException | IOException ex )
+ {
+ throw new ClientException( "Unable to establish SSL parameters", ex );
+ }
+ }
+
+ /*
+ * Establish a complete SecurityPlan based on the details provided for
+ * driver construction.
+ */
+ private static SecurityPlan createSecurityPlanImpl( BoltServerAddress address, Config config )
+ throws GeneralSecurityException, IOException
+ {
+ Config.EncryptionLevel encryptionLevel = config.encryptionLevel();
+ boolean requiresEncryption = encryptionLevel.equals( REQUIRED );
+
+ if ( requiresEncryption )
+ {
+ Logger logger = config.logging().getLog( "session" );
+ switch ( config.trustStrategy().strategy() )
+ {
+
+ // DEPRECATED CASES //
+ case TRUST_ON_FIRST_USE:
+ logger.warn(
+ "Option `TRUST_ON_FIRST_USE` has been deprecated and will be removed in a future " +
+ "version of the driver. Please switch to use `TRUST_ALL_CERTIFICATES` instead." );
+ return SecurityPlan.forTrustOnFirstUse( config.trustStrategy().certFile(), address, logger );
+ case TRUST_SIGNED_CERTIFICATES:
+ logger.warn(
+ "Option `TRUST_SIGNED_CERTIFICATE` has been deprecated and will be removed in a future " +
+ "version of the driver. Please switch to use `TRUST_CUSTOM_CA_SIGNED_CERTIFICATES` instead." );
+ // intentional fallthrough
+ // END OF DEPRECATED CASES //
+
+ case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES:
+ return SecurityPlan.forCustomCASignedCertificates( config.trustStrategy().certFile() );
+ case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES:
+ return SecurityPlan.forSystemCASignedCertificates();
+ case TRUST_ALL_CERTIFICATES:
+ return SecurityPlan.forAllCertificates();
+ default:
+ throw new ClientException(
+ "Unknown TLS authentication strategy: " + config.trustStrategy().strategy().name() );
+ }
+ }
+ else
+ {
+ return insecure();
+ }
+ }
+}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/connector/socket/SocketUtils.java b/driver/src/main/java/org/neo4j/driver/internal/connector/socket/SocketUtils.java
deleted file mode 100644
index 0f6c1e75fe..0000000000
--- a/driver/src/main/java/org/neo4j/driver/internal/connector/socket/SocketUtils.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (c) 2002-2016 "Neo Technology,"
- * Network Engine for Objects in Lund AB [http://neotechnology.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.connector.socket;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.ByteChannel;
-
-import org.neo4j.driver.internal.util.BytePrinter;
-import org.neo4j.driver.v1.exceptions.ClientException;
-
-/**
- * Utility class for common operations.
- */
-public final class SocketUtils
-{
- private SocketUtils()
- {
- throw new UnsupportedOperationException( "Do not instantiate" );
- }
-
- public static void blockingRead(ByteChannel channel, ByteBuffer buf) throws IOException
- {
- while(buf.hasRemaining())
- {
- if (channel.read( buf ) < 0)
- {
- try
- {
- channel.close();
- }
- catch ( IOException e )
- {
- // best effort
- }
- String bufStr = BytePrinter.hex( buf ).trim();
- throw new ClientException( String.format(
- "Connection terminated while receiving data. This can happen due to network " +
- "instabilities, or due to restarts of the database. Expected %s bytes, received %s.",
- buf.limit(), bufStr.isEmpty() ? "none" : bufStr ) );
- }
- }
- }
-
- public static void blockingWrite(ByteChannel channel, ByteBuffer buf) throws IOException
- {
- while(buf.hasRemaining())
- {
- if (channel.write( buf ) < 0)
- {
- try
- {
- channel.close();
- }
- catch ( IOException e )
- {
- // best effort
- }
- String bufStr = BytePrinter.hex( buf ).trim();
- throw new ClientException( String.format(
- "Connection terminated while sending data. This can happen due to network " +
- "instabilities, or due to restarts of the database. Expected %s bytes, wrote %s.",
- buf.limit(), bufStr.isEmpty() ? "none" :bufStr ) );
- }
- }
- }
-}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/net/ConcurrencyGuardingConnection.java b/driver/src/main/java/org/neo4j/driver/internal/net/ConcurrencyGuardingConnection.java
index fd5f8dab45..577360019b 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/net/ConcurrencyGuardingConnection.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/net/ConcurrencyGuardingConnection.java
@@ -174,15 +174,9 @@ public void receiveOne()
@Override
public void close()
{
- try
- {
- markAsInUse();
- delegate.close();
- }
- finally
- {
- markAsAvailable();
- }
+ // It is fine to call close concurrently with this connection being used somewhere else.
+ // This could happen when driver is closed while there still exist sessions that do some work.
+ delegate.close();
}
@Override
diff --git a/driver/src/main/java/org/neo4j/driver/internal/net/SocketConnector.java b/driver/src/main/java/org/neo4j/driver/internal/net/SocketConnector.java
new file mode 100644
index 0000000000..979b1f1018
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/internal/net/SocketConnector.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2002-2016 "Neo Technology,"
+ * Network Engine for Objects in Lund AB [http://neotechnology.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.net;
+
+import java.util.Map;
+
+import org.neo4j.driver.internal.ConnectionSettings;
+import org.neo4j.driver.internal.security.InternalAuthToken;
+import org.neo4j.driver.internal.security.SecurityPlan;
+import org.neo4j.driver.internal.spi.Connection;
+import org.neo4j.driver.internal.spi.Connector;
+import org.neo4j.driver.v1.AuthToken;
+import org.neo4j.driver.v1.AuthTokens;
+import org.neo4j.driver.v1.Logging;
+import org.neo4j.driver.v1.Value;
+import org.neo4j.driver.v1.exceptions.ClientException;
+
+public class SocketConnector implements Connector
+{
+ private final ConnectionSettings connectionSettings;
+ private final SecurityPlan securityPlan;
+ private final Logging logging;
+
+ public SocketConnector( ConnectionSettings connectionSettings, SecurityPlan securityPlan, Logging logging )
+ {
+ this.connectionSettings = connectionSettings;
+ this.securityPlan = securityPlan;
+ this.logging = logging;
+ }
+
+ @Override
+ public final Connection connect( BoltServerAddress address )
+ {
+ Connection connection = createConnection( address, securityPlan, logging );
+
+ // Because SocketConnection is not thread safe, wrap it in this guard
+ // to ensure concurrent access leads causes application errors
+ connection = new ConcurrencyGuardingConnection( connection );
+
+ try
+ {
+ connection.init( connectionSettings.userAgent(), tokenAsMap( connectionSettings.authToken() ) );
+ }
+ catch ( Throwable initError )
+ {
+ connection.close();
+ throw initError;
+ }
+
+ return connection;
+ }
+
+ /**
+ * Create new connection.
+ *
+ * This method is package-private only for testing
+ */
+ Connection createConnection( BoltServerAddress address, SecurityPlan securityPlan, Logging logging )
+ {
+ return new SocketConnection( address, securityPlan, logging );
+ }
+
+ private static Map tokenAsMap( AuthToken token )
+ {
+ if ( token instanceof InternalAuthToken )
+ {
+ return ((InternalAuthToken) token).toMap();
+ }
+ else
+ {
+ throw new ClientException(
+ "Unknown authentication token, `" + token + "`. Please use one of the supported " +
+ "tokens from `" + AuthTokens.class.getSimpleName() + "`." );
+ }
+ }
+}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/net/pooling/BlockingPooledConnectionQueue.java b/driver/src/main/java/org/neo4j/driver/internal/net/pooling/BlockingPooledConnectionQueue.java
index a23e0106e1..156e9c9b04 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/net/pooling/BlockingPooledConnectionQueue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/net/pooling/BlockingPooledConnectionQueue.java
@@ -27,7 +27,10 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
+import org.neo4j.driver.internal.net.BoltServerAddress;
import org.neo4j.driver.internal.util.Supplier;
+import org.neo4j.driver.v1.Logger;
+import org.neo4j.driver.v1.Logging;
/**
* A blocking queue that also keeps track of connections that are acquired in order
@@ -37,6 +40,7 @@ public class BlockingPooledConnectionQueue
{
/** The backing queue, keeps track of connections currently in queue */
private final BlockingQueue queue;
+ private final Logger logger;
private final AtomicBoolean isTerminating = new AtomicBoolean( false );
@@ -44,9 +48,10 @@ public class BlockingPooledConnectionQueue
private final Set acquiredConnections =
Collections.newSetFromMap(new ConcurrentHashMap());
- public BlockingPooledConnectionQueue( int capacity )
+ public BlockingPooledConnectionQueue( BoltServerAddress address, int capacity, Logging logging )
{
this.queue = new LinkedBlockingQueue<>( capacity );
+ this.logger = createLogger( address, logging );
}
/**
@@ -64,10 +69,10 @@ public boolean offer( PooledConnection pooledConnection )
pooledConnection.dispose();
}
if (isTerminating.get()) {
- PooledConnection poll = queue.poll();
- if (poll != null)
+ PooledConnection connection = queue.poll();
+ if (connection != null)
{
- poll.dispose();
+ connection.dispose();
}
}
return offer;
@@ -81,19 +86,19 @@ public boolean offer( PooledConnection pooledConnection )
public PooledConnection acquire( Supplier supplier )
{
- PooledConnection poll = queue.poll();
- if ( poll == null )
+ PooledConnection connection = queue.poll();
+ if ( connection == null )
{
- poll = supplier.get();
+ connection = supplier.get();
}
- acquiredConnections.add( poll );
+ acquiredConnections.add( connection );
if (isTerminating.get()) {
- acquiredConnections.remove( poll );
- poll.dispose();
+ acquiredConnections.remove( connection );
+ connection.dispose();
throw new IllegalStateException( "Pool has been closed, cannot acquire new values." );
}
- return poll;
+ return connection;
}
public List toList()
@@ -119,24 +124,43 @@ public boolean contains( PooledConnection pooledConnection )
/**
* Terminates all connections, both those that are currently in the queue as well
* as those that have been acquired.
+ *
+ * This method does not throw runtime exceptions. All connection close failures are only logged.
*/
public void terminate()
{
- if (isTerminating.compareAndSet( false, true ))
+ if ( isTerminating.compareAndSet( false, true ) )
{
while ( !queue.isEmpty() )
{
- PooledConnection conn = queue.poll();
- if ( conn != null )
- {
- //close the underlying connection without adding it back to the queue
- conn.dispose();
- }
+ PooledConnection idleConnection = queue.poll();
+ disposeSafely( idleConnection );
}
- for ( PooledConnection pooledConnection : acquiredConnections )
+ for ( PooledConnection acquiredConnection : acquiredConnections )
{
- pooledConnection.dispose();
+ disposeSafely( acquiredConnection );
}
}
}
+
+ private void disposeSafely( PooledConnection connection )
+ {
+ try
+ {
+ if ( connection != null )
+ {
+ // close the underlying connection without adding it back to the queue
+ connection.dispose();
+ }
+ }
+ catch ( Throwable disposeError )
+ {
+ logger.error( "Error disposing connection", disposeError );
+ }
+ }
+
+ private static Logger createLogger( BoltServerAddress address, Logging logging )
+ {
+ return logging.getLog( "connectionQueue[" + address + "]" );
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/net/pooling/SocketConnectionPool.java b/driver/src/main/java/org/neo4j/driver/internal/net/pooling/SocketConnectionPool.java
index f709e009b4..a30ef274d0 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/net/pooling/SocketConnectionPool.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/net/pooling/SocketConnectionPool.java
@@ -18,27 +18,16 @@
*/
package org.neo4j.driver.internal.net.pooling;
-import java.util.List;
-import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
-import org.neo4j.driver.internal.ConnectionSettings;
import org.neo4j.driver.internal.net.BoltServerAddress;
-import org.neo4j.driver.internal.net.ConcurrencyGuardingConnection;
-import org.neo4j.driver.internal.net.SocketConnection;
-import org.neo4j.driver.internal.security.InternalAuthToken;
-import org.neo4j.driver.internal.security.SecurityPlan;
import org.neo4j.driver.internal.spi.Connection;
import org.neo4j.driver.internal.spi.ConnectionPool;
+import org.neo4j.driver.internal.spi.Connector;
import org.neo4j.driver.internal.util.Clock;
import org.neo4j.driver.internal.util.Supplier;
-import org.neo4j.driver.v1.AuthToken;
-import org.neo4j.driver.v1.AuthTokens;
import org.neo4j.driver.v1.Logging;
-import org.neo4j.driver.v1.Value;
-import org.neo4j.driver.v1.exceptions.ClientException;
-
-import static java.util.Collections.emptyList;
/**
* The pool is designed to buffer certain amount of free sessions into session pool. When closing a session, we first
@@ -60,52 +49,26 @@ public class SocketConnectionPool implements ConnectionPool
private final ConcurrentHashMap pools =
new ConcurrentHashMap<>();
- private final Clock clock = Clock.SYSTEM;
+ private final AtomicBoolean closed = new AtomicBoolean();
- private final ConnectionSettings connectionSettings;
- private final SecurityPlan securityPlan;
private final PoolSettings poolSettings;
+ private final Connector connector;
+ private final Clock clock;
private final Logging logging;
- /** Shutdown flag */
-
- public SocketConnectionPool( ConnectionSettings connectionSettings, SecurityPlan securityPlan,
- PoolSettings poolSettings, Logging logging )
+ public SocketConnectionPool( PoolSettings poolSettings, Connector connector, Clock clock, Logging logging )
{
- this.connectionSettings = connectionSettings;
- this.securityPlan = securityPlan;
this.poolSettings = poolSettings;
+ this.connector = connector;
+ this.clock = clock;
this.logging = logging;
}
- private Connection connect( BoltServerAddress address ) throws ClientException
- {
- Connection conn = new SocketConnection( address, securityPlan, logging );
-
- // Because SocketConnection is not thread safe, wrap it in this guard
- // to ensure concurrent access leads causes application errors
- conn = new ConcurrencyGuardingConnection( conn );
- conn.init( connectionSettings.userAgent(), tokenAsMap( connectionSettings.authToken() ) );
- return conn;
- }
-
- private static Map tokenAsMap( AuthToken token )
- {
- if ( token instanceof InternalAuthToken )
- {
- return ((InternalAuthToken) token).toMap();
- }
- else
- {
- throw new ClientException(
- "Unknown authentication token, `" + token + "`. Please use one of the supported " +
- "tokens from `" + AuthTokens.class.getSimpleName() + "`." );
- }
- }
-
@Override
public Connection acquire( final BoltServerAddress address )
{
+ assertNotClosed();
+
final BlockingPooledConnectionQueue connections = pool( address );
Supplier supplier = new Supplier()
{
@@ -116,10 +79,17 @@ public PooledConnection get()
new PooledConnectionValidator( SocketConnectionPool.this );
PooledConnectionReleaseConsumer releaseConsumer =
new PooledConnectionReleaseConsumer( connections, connectionValidator );
- return new PooledConnection( connect( address ), releaseConsumer, clock );
+ return new PooledConnection( connector.connect( address ), releaseConsumer, clock );
}
};
PooledConnection conn = connections.acquire( supplier );
+
+ if ( closed.get() )
+ {
+ connections.terminate();
+ throw poolClosedException();
+ }
+
conn.updateTimestamp();
return conn;
}
@@ -129,7 +99,7 @@ private BlockingPooledConnectionQueue pool( BoltServerAddress address )
BlockingPooledConnectionQueue pool = pools.get( address );
if ( pool == null )
{
- pool = new BlockingPooledConnectionQueue( poolSettings.maxIdleConnectionPoolSize() );
+ pool = new BlockingPooledConnectionQueue( address, poolSettings.maxIdleConnectionPoolSize(), logging );
if ( pools.putIfAbsent( address, pool ) != null )
{
@@ -144,12 +114,10 @@ private BlockingPooledConnectionQueue pool( BoltServerAddress address )
public void purge( BoltServerAddress address )
{
BlockingPooledConnectionQueue connections = pools.remove( address );
- if ( connections == null )
+ if ( connections != null )
{
- return;
+ connections.terminate();
}
-
- connections.terminate();
}
@Override
@@ -161,28 +129,27 @@ public boolean hasAddress( BoltServerAddress address )
@Override
public void close()
{
- for ( BlockingPooledConnectionQueue pool : pools.values() )
+ if ( closed.compareAndSet( false, true ) )
{
- pool.terminate();
- }
+ for ( BlockingPooledConnectionQueue pool : pools.values() )
+ {
+ pool.terminate();
+ }
- pools.clear();
+ pools.clear();
+ }
}
-
- //for testing
- public List connectionsForAddress( BoltServerAddress address )
+ private void assertNotClosed()
{
- BlockingPooledConnectionQueue pooledConnections = pools.get( address );
- if ( pooledConnections == null )
- {
- return emptyList();
- }
- else
+ if ( closed.get() )
{
- return pooledConnections.toList();
+ throw poolClosedException();
}
}
-
+ private static RuntimeException poolClosedException()
+ {
+ return new IllegalStateException( "Pool closed" );
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/spi/Connector.java b/driver/src/main/java/org/neo4j/driver/internal/spi/Connector.java
new file mode 100644
index 0000000000..b512203c56
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/internal/spi/Connector.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2002-2016 "Neo Technology,"
+ * Network Engine for Objects in Lund AB [http://neotechnology.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.spi;
+
+import org.neo4j.driver.internal.net.BoltServerAddress;
+
+public interface Connector
+{
+ Connection connect( BoltServerAddress address );
+}
diff --git a/driver/src/main/java/org/neo4j/driver/v1/GraphDatabase.java b/driver/src/main/java/org/neo4j/driver/v1/GraphDatabase.java
index 4c61e9d05a..3ab9e17b8e 100644
--- a/driver/src/main/java/org/neo4j/driver/v1/GraphDatabase.java
+++ b/driver/src/main/java/org/neo4j/driver/v1/GraphDatabase.java
@@ -18,27 +18,9 @@
*/
package org.neo4j.driver.v1;
-import java.io.IOException;
import java.net.URI;
-import java.security.GeneralSecurityException;
-import org.neo4j.driver.internal.ConnectionSettings;
-import org.neo4j.driver.internal.DirectDriver;
-import org.neo4j.driver.internal.NetworkSession;
-import org.neo4j.driver.internal.RoutingDriver;
-import org.neo4j.driver.internal.net.BoltServerAddress;
-import org.neo4j.driver.internal.net.pooling.PoolSettings;
-import org.neo4j.driver.internal.net.pooling.SocketConnectionPool;
-import org.neo4j.driver.internal.security.SecurityPlan;
-import org.neo4j.driver.internal.spi.Connection;
-import org.neo4j.driver.internal.spi.ConnectionPool;
-import org.neo4j.driver.internal.util.Clock;
-import org.neo4j.driver.v1.exceptions.ClientException;
-import org.neo4j.driver.v1.util.Function;
-
-import static java.lang.String.format;
-import static org.neo4j.driver.internal.security.SecurityPlan.insecure;
-import static org.neo4j.driver.v1.Config.EncryptionLevel.REQUIRED;
+import org.neo4j.driver.internal.DriverFactory;
/**
* Creates {@link Driver drivers}, optionally letting you {@link #driver(URI, Config)} to configure them.
@@ -47,17 +29,6 @@
*/
public class GraphDatabase
{
-
- private static final Function
- SESSION_PROVIDER = new Function()
- {
- @Override
- public Session apply( Connection connection )
- {
- return new NetworkSession( connection );
- }
- };
-
/**
* Return a driver for a Neo4j instance with the default configuration settings
*
@@ -151,97 +122,9 @@ public static Driver driver( String uri, AuthToken authToken, Config config )
*/
public static Driver driver( URI uri, AuthToken authToken, Config config )
{
- // Break down the URI into its constituent parts
- String scheme = uri.getScheme();
- BoltServerAddress address = BoltServerAddress.from( uri );
-
- // Collate session parameters
- ConnectionSettings connectionSettings =
- new ConnectionSettings( authToken == null ? AuthTokens.none() : authToken );
-
// Make sure we have some configuration to play with
- if ( config == null )
- {
- config = Config.defaultConfig();
- }
-
- // Construct security plan
- SecurityPlan securityPlan;
- try
- {
- securityPlan = createSecurityPlan( address, config );
- }
- catch ( GeneralSecurityException | IOException ex )
- {
- throw new ClientException( "Unable to establish SSL parameters", ex );
- }
-
- // Establish pool settings
- PoolSettings poolSettings = new PoolSettings( config.maxIdleConnectionPoolSize() );
-
- // And finally, construct the driver proper
- ConnectionPool connectionPool =
- new SocketConnectionPool( connectionSettings, securityPlan, poolSettings, config.logging() );
- switch ( scheme.toLowerCase() )
- {
- case "bolt":
- return new DirectDriver( address, connectionPool, securityPlan, config.logging() );
- case "bolt+routing":
- return new RoutingDriver(
- config.routingSettings(),
- address,
- connectionPool,
- securityPlan,
- Clock.SYSTEM,
- config.logging() );
- default:
- throw new ClientException( format( "Unsupported URI scheme: %s", scheme ) );
- }
- }
-
- /*
- * Establish a complete SecurityPlan based on the details provided for
- * driver construction.
- */
- private static SecurityPlan createSecurityPlan( BoltServerAddress address, Config config )
- throws GeneralSecurityException, IOException
- {
- Config.EncryptionLevel encryptionLevel = config.encryptionLevel();
- boolean requiresEncryption = encryptionLevel.equals( REQUIRED );
-
- if ( requiresEncryption )
- {
- Logger logger = config.logging().getLog( "session" );
- switch ( config.trustStrategy().strategy() )
- {
-
- // DEPRECATED CASES //
- case TRUST_ON_FIRST_USE:
- logger.warn(
- "Option `TRUST_ON_FIRST_USE` has been deprecated and will be removed in a future " +
- "version of the driver. Please switch to use `TRUST_ALL_CERTIFICATES` instead." );
- return SecurityPlan.forTrustOnFirstUse( config.trustStrategy().certFile(), address, logger );
- case TRUST_SIGNED_CERTIFICATES:
- logger.warn(
- "Option `TRUST_SIGNED_CERTIFICATE` has been deprecated and will be removed in a future " +
- "version of the driver. Please switch to use `TRUST_CUSTOM_CA_SIGNED_CERTIFICATES` instead." );
- // intentional fallthrough
- // END OF DEPRECATED CASES //
+ config = config == null ? Config.defaultConfig() : config;
- case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES:
- return SecurityPlan.forCustomCASignedCertificates( config.trustStrategy().certFile() );
- case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES:
- return SecurityPlan.forSystemCASignedCertificates();
- case TRUST_ALL_CERTIFICATES:
- return SecurityPlan.forAllCertificates();
- default:
- throw new ClientException(
- "Unknown TLS authentication strategy: " + config.trustStrategy().strategy().name() );
- }
- }
- else
- {
- return insecure();
- }
+ return new DriverFactory().newInstance( uri, authToken, config.routingSettings(), config );
}
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java b/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java
new file mode 100644
index 0000000000..f96b9762f6
--- /dev/null
+++ b/driver/src/test/java/org/neo4j/driver/internal/DriverFactoryTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2002-2016 "Neo Technology,"
+ * Network Engine for Objects in Lund AB [http://neotechnology.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;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import org.neo4j.driver.internal.cluster.RoutingSettings;
+import org.neo4j.driver.internal.net.BoltServerAddress;
+import org.neo4j.driver.internal.security.SecurityPlan;
+import org.neo4j.driver.internal.spi.ConnectionPool;
+import org.neo4j.driver.v1.AuthToken;
+import org.neo4j.driver.v1.AuthTokens;
+import org.neo4j.driver.v1.Config;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.neo4j.driver.v1.Config.defaultConfig;
+
+@RunWith( Parameterized.class )
+public class DriverFactoryTest
+{
+ @Parameter
+ public URI uri;
+
+ @Parameters( name = "{0}" )
+ public static List uris()
+ {
+ return Arrays.asList(
+ URI.create( "bolt://localhost:7687" ),
+ URI.create( "bolt+routing://localhost:7687" )
+ );
+ }
+
+ @Test
+ public void connectionPoolClosedWhenDriverCreationFails() throws Exception
+ {
+ ConnectionPool connectionPool = mock( ConnectionPool.class );
+ DriverFactory factory = new ThrowingDriverFactory( connectionPool );
+
+ try
+ {
+ factory.newInstance( uri, dummyAuthToken(), dummyRoutingSettings(), defaultConfig() );
+ fail( "Exception expected" );
+ }
+ catch ( Exception e )
+ {
+ assertThat( e, instanceOf( UnsupportedOperationException.class ) );
+ }
+ verify( connectionPool ).close();
+ }
+
+ @Test
+ public void connectionPoolCloseExceptionIsSupressedWhenDriverCreationFails() throws Exception
+ {
+ ConnectionPool connectionPool = mock( ConnectionPool.class );
+ RuntimeException poolCloseError = new RuntimeException( "Pool close error" );
+ doThrow( poolCloseError ).when( connectionPool ).close();
+
+ DriverFactory factory = new ThrowingDriverFactory( connectionPool );
+
+ try
+ {
+ factory.newInstance( uri, dummyAuthToken(), dummyRoutingSettings(), defaultConfig() );
+ fail( "Exception expected" );
+ }
+ catch ( Exception e )
+ {
+ assertThat( e, instanceOf( UnsupportedOperationException.class ) );
+ assertArrayEquals( new Throwable[]{poolCloseError}, e.getSuppressed() );
+ }
+ verify( connectionPool ).close();
+ }
+
+ private static AuthToken dummyAuthToken()
+ {
+ return AuthTokens.basic( "neo4j", "neo4j" );
+ }
+
+ private static RoutingSettings dummyRoutingSettings()
+ {
+ return new RoutingSettings( 42, 42 );
+ }
+
+ private static class ThrowingDriverFactory extends DriverFactory
+ {
+ final ConnectionPool connectionPool;
+
+ ThrowingDriverFactory( ConnectionPool connectionPool )
+ {
+ this.connectionPool = connectionPool;
+ }
+
+ @Override
+ DirectDriver createDirectDriver( BoltServerAddress address, ConnectionPool connectionPool, Config config,
+ SecurityPlan securityPlan )
+ {
+ throw new UnsupportedOperationException( "Can't create direct driver" );
+ }
+
+ @Override
+ RoutingDriver createRoutingDriver( BoltServerAddress address, ConnectionPool connectionPool, Config config,
+ RoutingSettings routingSettings, SecurityPlan securityPlan )
+ {
+ throw new UnsupportedOperationException( "Can't create routing driver" );
+ }
+
+ @Override
+ ConnectionPool createConnectionPool( AuthToken authToken, SecurityPlan securityPlan, Config config )
+ {
+ return connectionPool;
+ }
+ }
+}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/net/ConcurrencyGuardingConnectionTest.java b/driver/src/test/java/org/neo4j/driver/internal/net/ConcurrencyGuardingConnectionTest.java
index 0e136fc98a..319f79de00 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/net/ConcurrencyGuardingConnectionTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/net/ConcurrencyGuardingConnectionTest.java
@@ -28,14 +28,16 @@
import java.util.concurrent.atomic.AtomicReference;
import org.neo4j.driver.internal.spi.Connection;
-import org.neo4j.driver.v1.util.Function;
import org.neo4j.driver.v1.exceptions.ClientException;
+import org.neo4j.driver.v1.util.Function;
import static java.util.Arrays.asList;
import static junit.framework.TestCase.fail;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
@RunWith( Parameterized.class )
public class ConcurrencyGuardingConnectionTest
@@ -44,17 +46,19 @@ public class ConcurrencyGuardingConnectionTest
public Function operation;
@Parameterized.Parameters
- public static List