org.neo4j.driver.internal.DriverFactory Maven / Gradle / Ivy
/*
* 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;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.net.URI;
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Config;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Logger;
import org.neo4j.driver.Logging;
import org.neo4j.driver.MetricsAdapter;
import org.neo4j.driver.internal.async.connection.BootstrapFactory;
import org.neo4j.driver.internal.async.connection.ChannelConnector;
import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl;
import org.neo4j.driver.internal.async.pool.ConnectionPoolImpl;
import org.neo4j.driver.internal.async.pool.PoolSettings;
import org.neo4j.driver.internal.cluster.RoutingContext;
import org.neo4j.driver.internal.cluster.RoutingSettings;
import org.neo4j.driver.internal.cluster.loadbalancing.LeastConnectedLoadBalancingStrategy;
import org.neo4j.driver.internal.cluster.loadbalancing.LoadBalancer;
import org.neo4j.driver.internal.cluster.loadbalancing.LoadBalancingStrategy;
import org.neo4j.driver.internal.logging.NettyLogging;
import org.neo4j.driver.internal.metrics.DevNullMetricsProvider;
import org.neo4j.driver.internal.metrics.InternalMetricsProvider;
import org.neo4j.driver.internal.metrics.MetricsProvider;
import org.neo4j.driver.internal.metrics.MicrometerMetricsProvider;
import org.neo4j.driver.internal.retry.ExponentialBackoffRetryLogic;
import org.neo4j.driver.internal.retry.RetryLogic;
import org.neo4j.driver.internal.retry.RetrySettings;
import org.neo4j.driver.internal.security.SecurityPlan;
import org.neo4j.driver.internal.spi.ConnectionPool;
import org.neo4j.driver.internal.spi.ConnectionProvider;
import org.neo4j.driver.internal.util.Clock;
import org.neo4j.driver.internal.util.Futures;
import org.neo4j.driver.net.ServerAddressResolver;
import static org.neo4j.driver.internal.Scheme.isRoutingScheme;
import static org.neo4j.driver.internal.cluster.IdentityResolver.IDENTITY_RESOLVER;
import static org.neo4j.driver.internal.util.ErrorUtil.addSuppressed;
public class DriverFactory
{
public static final String NO_ROUTING_CONTEXT_ERROR_MESSAGE = "Routing parameters are not supported with scheme 'bolt'. Given URI: ";
public final Driver newInstance( URI uri, AuthToken authToken, RoutingSettings routingSettings,
RetrySettings retrySettings, Config config, SecurityPlan securityPlan )
{
return newInstance( uri, authToken, routingSettings, retrySettings, config, null, securityPlan );
}
public final Driver newInstance( URI uri, AuthToken authToken, RoutingSettings routingSettings,
RetrySettings retrySettings, Config config, EventLoopGroup eventLoopGroup, SecurityPlan securityPlan )
{
Bootstrap bootstrap;
boolean ownsEventLoopGroup;
if ( eventLoopGroup == null )
{
bootstrap = createBootstrap( config.eventLoopThreads() );
ownsEventLoopGroup = true;
}
else
{
bootstrap = createBootstrap( eventLoopGroup );
ownsEventLoopGroup = false;
}
authToken = authToken == null ? AuthTokens.none() : authToken;
BoltServerAddress address = new BoltServerAddress( uri );
RoutingSettings newRoutingSettings = routingSettings.withRoutingContext( new RoutingContext( uri ) );
InternalLoggerFactory.setDefaultFactory( new NettyLogging( config.logging() ) );
EventExecutorGroup eventExecutorGroup = bootstrap.config().group();
RetryLogic retryLogic = createRetryLogic( retrySettings, eventExecutorGroup, config.logging() );
MetricsProvider metricsProvider = getOrCreateMetricsProvider( config, createClock() );
ConnectionPool connectionPool = createConnectionPool( authToken, securityPlan, bootstrap, metricsProvider, config,
ownsEventLoopGroup, newRoutingSettings.routingContext() );
return createDriver( uri, securityPlan, address, connectionPool, eventExecutorGroup, newRoutingSettings, retryLogic, metricsProvider, config );
}
protected ConnectionPool createConnectionPool( AuthToken authToken, SecurityPlan securityPlan, Bootstrap bootstrap,
MetricsProvider metricsProvider, Config config, boolean ownsEventLoopGroup, RoutingContext routingContext )
{
Clock clock = createClock();
ConnectionSettings settings = new ConnectionSettings( authToken, config.userAgent(), config.connectionTimeoutMillis() );
ChannelConnector connector = createConnector( settings, securityPlan, config, clock, routingContext );
PoolSettings poolSettings = new PoolSettings( config.maxConnectionPoolSize(),
config.connectionAcquisitionTimeoutMillis(), config.maxConnectionLifetimeMillis(),
config.idleTimeBeforeConnectionTest()
);
return new ConnectionPoolImpl( connector, bootstrap, poolSettings, metricsProvider.metricsListener(), config.logging(), clock, ownsEventLoopGroup );
}
protected static MetricsProvider getOrCreateMetricsProvider( Config config, Clock clock )
{
MetricsAdapter metricsAdapter = config.metricsAdapter();
// This can actually only happen when someone mocks the config
if ( metricsAdapter == null )
{
metricsAdapter = config.isMetricsEnabled() ? MetricsAdapter.DEFAULT : MetricsAdapter.DEV_NULL;
}
switch ( metricsAdapter )
{
case DEV_NULL:
return DevNullMetricsProvider.INSTANCE;
case DEFAULT:
return new InternalMetricsProvider( clock, config.logging() );
case MICROMETER:
return MicrometerMetricsProvider.forGlobalRegistry();
}
throw new IllegalStateException( "Unknown or unsupported MetricsAdapter: " + metricsAdapter );
}
protected ChannelConnector createConnector( ConnectionSettings settings, SecurityPlan securityPlan,
Config config, Clock clock, RoutingContext routingContext )
{
return new ChannelConnectorImpl( settings, securityPlan, config.logging(), clock, routingContext, getDomainNameResolver() );
}
private InternalDriver createDriver( URI uri, SecurityPlan securityPlan, BoltServerAddress address, ConnectionPool connectionPool,
EventExecutorGroup eventExecutorGroup, RoutingSettings routingSettings, RetryLogic retryLogic,
MetricsProvider metricsProvider, Config config )
{
try
{
String scheme = uri.getScheme().toLowerCase();
if ( isRoutingScheme( scheme ) )
{
return createRoutingDriver( securityPlan, address, connectionPool, eventExecutorGroup, routingSettings, retryLogic, metricsProvider, config );
}
else
{
assertNoRoutingContext( uri, routingSettings );
return createDirectDriver( securityPlan, address, connectionPool, retryLogic, metricsProvider, config );
}
}
catch ( Throwable driverError )
{
// we need to close the connection pool if driver creation threw exception
closeConnectionPoolAndSuppressError( connectionPool, driverError );
throw driverError;
}
}
/**
* Creates a new driver for "bolt" scheme.
*
* This method is protected only for testing
*/
protected InternalDriver createDirectDriver( SecurityPlan securityPlan, BoltServerAddress address, ConnectionPool connectionPool, RetryLogic retryLogic,
MetricsProvider metricsProvider, Config config )
{
ConnectionProvider connectionProvider = new DirectConnectionProvider( address, connectionPool );
SessionFactory sessionFactory = createSessionFactory( connectionProvider, retryLogic, config );
InternalDriver driver = createDriver( securityPlan, sessionFactory, metricsProvider, config );
Logger log = config.logging().getLog( getClass() );
log.info( "Direct driver instance %s created for server address %s", driver.hashCode(), address );
return driver;
}
/**
* Creates new a new driver for "neo4j" scheme.
*
* This method is protected only for testing
*/
protected InternalDriver createRoutingDriver( SecurityPlan securityPlan, BoltServerAddress address, ConnectionPool connectionPool,
EventExecutorGroup eventExecutorGroup, RoutingSettings routingSettings, RetryLogic retryLogic, MetricsProvider metricsProvider, Config config )
{
ConnectionProvider connectionProvider = createLoadBalancer( address, connectionPool, eventExecutorGroup,
config, routingSettings );
SessionFactory sessionFactory = createSessionFactory( connectionProvider, retryLogic, config );
InternalDriver driver = createDriver( securityPlan, sessionFactory, metricsProvider, config );
Logger log = config.logging().getLog( getClass() );
log.info( "Routing driver instance %s created for server address %s", driver.hashCode(), address );
return driver;
}
/**
* Creates new {@link Driver}.
*
* This method is protected only for testing
*/
protected InternalDriver createDriver( SecurityPlan securityPlan, SessionFactory sessionFactory, MetricsProvider metricsProvider, Config config )
{
return new InternalDriver( securityPlan, sessionFactory, metricsProvider, config.logging() );
}
/**
* Creates new {@link LoadBalancer} for the routing driver.
*
* This method is protected only for testing
*/
protected LoadBalancer createLoadBalancer( BoltServerAddress address, ConnectionPool connectionPool,
EventExecutorGroup eventExecutorGroup, Config config, RoutingSettings routingSettings )
{
LoadBalancingStrategy loadBalancingStrategy = new LeastConnectedLoadBalancingStrategy( connectionPool, config.logging() );
ServerAddressResolver resolver = createResolver( config );
LoadBalancer loadBalancer = new LoadBalancer( address, routingSettings, connectionPool, eventExecutorGroup, createClock(),
config.logging(), loadBalancingStrategy, resolver, getDomainNameResolver() );
handleNewLoadBalancer( loadBalancer );
return loadBalancer;
}
/**
* Handles new {@link LoadBalancer} instance.
*
* This method is protected for Testkit backend usage only.
*
* @param loadBalancer the new load balancer instance.
*/
protected void handleNewLoadBalancer( LoadBalancer loadBalancer )
{
}
private static ServerAddressResolver createResolver( Config config )
{
ServerAddressResolver configuredResolver = config.resolver();
return configuredResolver != null ? configuredResolver : IDENTITY_RESOLVER;
}
/**
* Creates new {@link Clock}.
*
* This method is protected only for testing
*/
protected Clock createClock()
{
return Clock.SYSTEM;
}
/**
* Creates new {@link SessionFactory}.
*
* This method is protected only for testing
*/
protected SessionFactory createSessionFactory( ConnectionProvider connectionProvider, RetryLogic retryLogic,
Config config )
{
return new SessionFactoryImpl( connectionProvider, retryLogic, config );
}
/**
* Creates new {@link RetryLogic}.
*
* This method is protected only for testing
*/
protected RetryLogic createRetryLogic( RetrySettings settings, EventExecutorGroup eventExecutorGroup,
Logging logging )
{
return new ExponentialBackoffRetryLogic( settings, eventExecutorGroup, createClock(), logging );
}
/**
* Creates new {@link Bootstrap}.
*
* This method is protected only for testing
*/
protected Bootstrap createBootstrap( int size )
{
return BootstrapFactory.newBootstrap( size );
}
/**
* Creates new {@link Bootstrap}.
*
* This method is protected only for testing
*/
protected Bootstrap createBootstrap( EventLoopGroup eventLoopGroup )
{
return BootstrapFactory.newBootstrap( eventLoopGroup );
}
/**
* Provides an instance of {@link DomainNameResolver} that is used for domain name resolution.
*
* This method is protected only for testing
*
* @return the instance of {@link DomainNameResolver}.
*/
protected DomainNameResolver getDomainNameResolver()
{
return DefaultDomainNameResolver.getInstance();
}
private static void assertNoRoutingContext( URI uri, RoutingSettings routingSettings )
{
RoutingContext routingContext = routingSettings.routingContext();
if ( routingContext.isDefined() )
{
throw new IllegalArgumentException( NO_ROUTING_CONTEXT_ERROR_MESSAGE + "'" + uri + "'" );
}
}
private static void closeConnectionPoolAndSuppressError( ConnectionPool connectionPool, Throwable mainError )
{
try
{
Futures.blockingGet( connectionPool.close() );
}
catch ( Throwable closeError )
{
addSuppressed( mainError, closeError );
}
}
}