com.datastax.oss.driver.api.core.session.SessionBuilder Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 com.datastax.oss.driver.api.core.session;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.auth.AuthProvider;
import com.datastax.oss.driver.api.core.auth.PlainTextAuthProviderBase;
import com.datastax.oss.driver.api.core.auth.ProgrammaticPlainTextAuthProvider;
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
import com.datastax.oss.driver.api.core.config.DriverConfig;
import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
import com.datastax.oss.driver.api.core.context.DriverContext;
import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy;
import com.datastax.oss.driver.api.core.loadbalancing.NodeDistanceEvaluator;
import com.datastax.oss.driver.api.core.metadata.EndPoint;
import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.api.core.metadata.NodeStateListener;
import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener;
import com.datastax.oss.driver.api.core.ssl.ProgrammaticSslEngineFactory;
import com.datastax.oss.driver.api.core.ssl.SslEngineFactory;
import com.datastax.oss.driver.api.core.tracker.RequestTracker;
import com.datastax.oss.driver.api.core.type.codec.TypeCodec;
import com.datastax.oss.driver.api.core.type.codec.registry.MutableCodecRegistry;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.datastax.oss.driver.internal.core.ContactPoints;
import com.datastax.oss.driver.internal.core.config.cloud.CloudConfig;
import com.datastax.oss.driver.internal.core.config.cloud.CloudConfigFactory;
import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader;
import com.datastax.oss.driver.internal.core.context.DefaultDriverContext;
import com.datastax.oss.driver.internal.core.context.InternalDriverContext;
import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint;
import com.datastax.oss.driver.internal.core.session.DefaultSession;
import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation;
import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionStage;
import java.util.function.Predicate;
import javax.net.ssl.SSLContext;
import net.jcip.annotations.NotThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base implementation to build session instances.
*
* You only need to deal with this directly if you use custom driver extensions. For the default
* session implementation, see {@link CqlSession#builder()}.
*
*
This class is mutable and not thread-safe.
*/
@NotThreadSafe
public abstract class SessionBuilder {
private static final Logger LOG = LoggerFactory.getLogger(SessionBuilder.class);
@SuppressWarnings("unchecked")
protected final SelfT self = (SelfT) this;
protected DriverConfigLoader configLoader;
protected Set programmaticContactPoints = new HashSet<>();
protected CqlIdentifier keyspace;
protected Callable cloudConfigInputStream;
protected ProgrammaticArguments.Builder programmaticArgumentsBuilder =
ProgrammaticArguments.builder();
private boolean programmaticSslFactory = false;
private boolean programmaticLocalDatacenter = false;
/**
* Sets the configuration loader to use.
*
* If you don't call this method, the builder will use the default implementation, based on the
* Typesafe config library. More precisely, configuration properties are loaded and merged from
* the following (first-listed are higher priority):
*
*
* - system properties
*
- {@code application.conf} (all resources on classpath with this name)
*
- {@code application.json} (all resources on classpath with this name)
*
- {@code application.properties} (all resources on classpath with this name)
*
- {@code reference.conf} (all resources on classpath with this name). In particular, this
* will load the {@code reference.conf} included in the core driver JAR, that defines
* default options for all mandatory options.
*
*
* The resulting configuration is expected to contain a {@code datastax-java-driver} section.
*
* This default loader will honor the reload interval defined by the option {@code
* basic.config-reload-interval}.
*
* @see Typesafe config's
* standard loading behavior
*/
@NonNull
public SelfT withConfigLoader(@Nullable DriverConfigLoader configLoader) {
this.configLoader = configLoader;
return self;
}
@NonNull
@Deprecated
protected DriverConfigLoader defaultConfigLoader() {
return new DefaultDriverConfigLoader();
}
@NonNull
protected DriverConfigLoader defaultConfigLoader(@Nullable ClassLoader classLoader) {
if (classLoader == null) {
return new DefaultDriverConfigLoader();
} else {
return new DefaultDriverConfigLoader(classLoader);
}
}
/**
* Adds contact points to use for the initial connection to the cluster.
*
*
These are addresses of Cassandra nodes that the driver uses to discover the cluster
* topology. Only one contact point is required (the driver will retrieve the address of the other
* nodes automatically), but it is usually a good idea to provide more than one contact point,
* because if that single contact point is unavailable, the driver cannot initialize itself
* correctly.
*
*
Contact points can also be provided statically in the configuration. If both are specified,
* they will be merged. If both are absent, the driver will default to 127.0.0.1:9042.
*
*
Contrary to the configuration, DNS names with multiple A-records will not be handled here.
* If you need that, extract them manually with {@link java.net.InetAddress#getAllByName(String)}
* before calling this method. Similarly, if you need connect addresses to stay unresolved, make
* sure you pass unresolved instances here (see {@code advanced.resolve-contact-points} in the
* configuration for more explanations).
*/
@NonNull
public SelfT addContactPoints(@NonNull Collection contactPoints) {
for (InetSocketAddress contactPoint : contactPoints) {
addContactPoint(contactPoint);
}
return self;
}
/**
* Adds a contact point to use for the initial connection to the cluster.
*
* @see #addContactPoints(Collection)
*/
@NonNull
public SelfT addContactPoint(@NonNull InetSocketAddress contactPoint) {
this.programmaticContactPoints.add(new DefaultEndPoint(contactPoint));
return self;
}
/**
* Adds contact points to use for the initial connection to the cluster.
*
* You only need this method if you use a custom {@link EndPoint} implementation. Otherwise,
* use {@link #addContactPoints(Collection)}.
*/
@NonNull
public SelfT addContactEndPoints(@NonNull Collection contactPoints) {
for (EndPoint contactPoint : contactPoints) {
addContactEndPoint(contactPoint);
}
return self;
}
/**
* Adds a contact point to use for the initial connection to the cluster.
*
* You only need this method if you use a custom {@link EndPoint} implementation. Otherwise,
* use {@link #addContactPoint(InetSocketAddress)}.
*/
@NonNull
public SelfT addContactEndPoint(@NonNull EndPoint contactPoint) {
this.programmaticContactPoints.add(contactPoint);
return self;
}
/**
* Registers additional codecs for custom type mappings.
*
* @param typeCodecs neither the individual codecs, nor the vararg array itself, can be {@code
* null}.
*/
@NonNull
public SelfT addTypeCodecs(@NonNull TypeCodec... typeCodecs) {
this.programmaticArgumentsBuilder.addTypeCodecs(typeCodecs);
return self;
}
/**
* Registers a node state listener to use with the session.
*
*
Listeners can be registered in two ways: either programmatically with this method, or via
* the configuration using the {@code advanced.metadata.node-state-listener.classes} option.
*
*
This method unregisters any previously-registered listener. If you intend to register more
* than one listener, use {@link #addNodeStateListener(NodeStateListener)} instead.
*/
@NonNull
public SelfT withNodeStateListener(@Nullable NodeStateListener nodeStateListener) {
this.programmaticArgumentsBuilder.withNodeStateListener(nodeStateListener);
return self;
}
/**
* Registers a node state listener to use with the session, without removing previously-registered
* listeners.
*
*
Listeners can be registered in two ways: either programmatically with this method, or via
* the configuration using the {@code advanced.metadata.node-state-listener.classes} option.
*
*
Unlike {@link #withNodeStateListener(NodeStateListener)}, this method adds the new listener
* to the list of already-registered listeners, thus allowing applications to register multiple
* listeners. When multiple listeners are registered, they are notified in sequence every time a
* new listener event is triggered.
*/
@NonNull
public SelfT addNodeStateListener(@NonNull NodeStateListener nodeStateListener) {
programmaticArgumentsBuilder.addNodeStateListener(nodeStateListener);
return self;
}
/**
* Registers a schema change listener to use with the session.
*
*
Listeners can be registered in two ways: either programmatically with this method, or via
* the configuration using the {@code advanced.metadata.schema-change-listener.classes} option.
*
*
This method unregisters any previously-registered listener. If you intend to register more
* than one listener, use {@link #addSchemaChangeListener(SchemaChangeListener)} instead.
*/
@NonNull
public SelfT withSchemaChangeListener(@Nullable SchemaChangeListener schemaChangeListener) {
this.programmaticArgumentsBuilder.withSchemaChangeListener(schemaChangeListener);
return self;
}
/**
* Registers a schema change listener to use with the session, without removing
* previously-registered listeners.
*
*
Listeners can be registered in two ways: either programmatically with this method, or via
* the configuration using the {@code advanced.metadata.schema-change-listener.classes} option.
*
*
Unlike {@link #withSchemaChangeListener(SchemaChangeListener)}, this method adds the new
* listener to the list of already-registered listeners, thus allowing applications to register
* multiple listeners. When multiple listeners are registered, they are notified in sequence every
* time a new listener event is triggered.
*/
@NonNull
public SelfT addSchemaChangeListener(@NonNull SchemaChangeListener schemaChangeListener) {
programmaticArgumentsBuilder.addSchemaChangeListener(schemaChangeListener);
return self;
}
/**
* Registers a request tracker to use with the session.
*
*
Trackers can be registered in two ways: either programmatically with this method, or via the
* configuration using the {@code advanced.request-tracker.classes} option.
*
*
This method unregisters any previously-registered tracker. If you intend to register more
* than one tracker, use {@link #addRequestTracker(RequestTracker)} instead.
*/
@NonNull
public SelfT withRequestTracker(@Nullable RequestTracker requestTracker) {
this.programmaticArgumentsBuilder.withRequestTracker(requestTracker);
return self;
}
/**
* Registers a request tracker to use with the session, without removing previously-registered
* trackers.
*
*
Trackers can be registered in two ways: either programmatically with this method, or via the
* configuration using the {@code advanced.request-tracker.classes} option.
*
*
Unlike {@link #withRequestTracker(RequestTracker)}, this method adds the new tracker to the
* list of already-registered trackers, thus allowing applications to register multiple trackers.
* When multiple trackers are registered, they are notified in sequence every time a new tracker
* event is triggered.
*/
@NonNull
public SelfT addRequestTracker(@NonNull RequestTracker requestTracker) {
programmaticArgumentsBuilder.addRequestTracker(requestTracker);
return self;
}
/**
* Registers an authentication provider to use with the session.
*
*
If the provider is specified programmatically with this method, it overrides the
* configuration (that is, the {@code advanced.auth-provider.class} option will be ignored).
*/
@NonNull
public SelfT withAuthProvider(@Nullable AuthProvider authProvider) {
this.programmaticArgumentsBuilder.withAuthProvider(authProvider);
return self;
}
/**
* Configures the session to use plaintext authentication with the given username and password.
*
*
This methods calls {@link #withAuthProvider(AuthProvider)} to register a special provider
* implementation. Therefore calling it overrides the configuration (that is, the {@code
* advanced.auth-provider.class} option will be ignored).
*
*
Note that this approach holds the credentials in clear text in memory, which makes them
* vulnerable to an attacker who is able to perform memory dumps. If this is not acceptable for
* you, consider writing your own {@link AuthProvider} implementation ({@link
* PlainTextAuthProviderBase} is a good starting point), and providing it either with {@link
* #withAuthProvider(AuthProvider)} or via the configuration ({@code
* advanced.auth-provider.class}).
*/
@NonNull
public SelfT withAuthCredentials(@NonNull String username, @NonNull String password) {
return withAuthProvider(new ProgrammaticPlainTextAuthProvider(username, password));
}
/**
* Configures the session to use DSE plaintext authentication with the given username and
* password, and perform proxy authentication with the given authorization id.
*
*
This feature is only available in DataStax Enterprise. If connecting to Apache Cassandra,
* the authorization id will be ignored; it is recommended to use {@link
* #withAuthCredentials(String, String)} instead.
*
*
This methods calls {@link #withAuthProvider(AuthProvider)} to register a special provider
* implementation. Therefore calling it overrides the configuration (that is, the {@code
* advanced.auth-provider.class} option will be ignored).
*
*
Note that this approach holds the credentials in clear text in memory, which makes them
* vulnerable to an attacker who is able to perform memory dumps. If this is not acceptable for
* you, consider writing your own {@link AuthProvider} implementation (the internal class {@code
* PlainTextAuthProviderBase} is a good starting point), and providing it either with {@link
* #withAuthProvider(AuthProvider)} or via the configuration ({@code
* advanced.auth-provider.class}).
*/
@NonNull
public SelfT withAuthCredentials(
@NonNull String username, @NonNull String password, @NonNull String authorizationId) {
return withAuthProvider(
new ProgrammaticPlainTextAuthProvider(username, password, authorizationId));
}
/**
* @deprecated this method only exists to ease the transition from driver 3, it is an alias for
* {@link #withAuthCredentials(String, String)}.
*/
@Deprecated
@NonNull
public SelfT withCredentials(@NonNull String username, @NonNull String password) {
return withAuthCredentials(username, password);
}
/**
* @deprecated this method only exists to ease the transition from driver 3, it is an alias for
* {@link #withAuthCredentials(String, String,String)}.
*/
@Deprecated
@NonNull
public SelfT withCredentials(
@NonNull String username, @NonNull String password, @NonNull String authorizationId) {
return withAuthCredentials(username, password, authorizationId);
}
/**
* Registers an SSL engine factory for the session.
*
*
If the factory is provided programmatically with this method, it overrides the configuration
* (that is, the {@code advanced.ssl-engine-factory} option will be ignored).
*
* @see ProgrammaticSslEngineFactory
*/
@NonNull
public SelfT withSslEngineFactory(@Nullable SslEngineFactory sslEngineFactory) {
this.programmaticSslFactory = true;
this.programmaticArgumentsBuilder.withSslEngineFactory(sslEngineFactory);
return self;
}
/**
* Configures the session to use SSL with the given context.
*
*
This is a convenience method for clients that already have an {@link SSLContext} instance.
* It wraps its argument into a {@link ProgrammaticSslEngineFactory}, and passes it to {@link
* #withSslEngineFactory(SslEngineFactory)}.
*
*
If you use this method, there is no way to customize cipher suites, or turn on host name
* validation. If you need finer control, use {@link #withSslEngineFactory(SslEngineFactory)}
* directly and pass either your own implementation of {@link SslEngineFactory}, or a {@link
* ProgrammaticSslEngineFactory} created with custom cipher suites and/or host name validation.
*
*
Also, note that SSL engines will be created with advisory peer information ({@link
* SSLContext#createSSLEngine(String, int)}) whenever possible.
*/
@NonNull
public SelfT withSslContext(@Nullable SSLContext sslContext) {
return withSslEngineFactory(
sslContext == null ? null : new ProgrammaticSslEngineFactory(sslContext));
}
/**
* Specifies the datacenter that is considered "local" by the load balancing policy.
*
*
This is a programmatic alternative to the configuration option {@code
* basic.load-balancing-policy.local-datacenter}. If this method is used, it takes precedence and
* overrides the configuration.
*
*
Note that this setting may or may not be relevant depending on the load balancing policy
* implementation in use. The driver's built-in {@code DefaultLoadBalancingPolicy} relies on it;
* if you use a third-party implementation, refer to their documentation.
*/
public SelfT withLocalDatacenter(@NonNull String profileName, @NonNull String localDatacenter) {
this.programmaticLocalDatacenter = true;
this.programmaticArgumentsBuilder.withLocalDatacenter(profileName, localDatacenter);
return self;
}
/** Alias to {@link #withLocalDatacenter(String, String)} for the default profile. */
public SelfT withLocalDatacenter(@NonNull String localDatacenter) {
return withLocalDatacenter(DriverExecutionProfile.DEFAULT_NAME, localDatacenter);
}
/**
* Adds a custom {@link NodeDistanceEvaluator} for a particular execution profile. This assumes
* that you're also using a dedicated load balancing policy for that profile.
*
*
Node distance evaluators are honored by all the driver built-in load balancing policies. If
* you use a custom policy implementation however, you'll need to explicitly invoke the evaluator
* whenever appropriate.
*
*
If an evaluator is specified programmatically with this method, it overrides the
* configuration (that is, the {@code load-balancing-policy.evaluator.class} option will be
* ignored).
*
* @see #withNodeDistanceEvaluator(NodeDistanceEvaluator)
*/
@NonNull
public SelfT withNodeDistanceEvaluator(
@NonNull String profileName, @NonNull NodeDistanceEvaluator nodeDistanceEvaluator) {
this.programmaticArgumentsBuilder.withNodeDistanceEvaluator(profileName, nodeDistanceEvaluator);
return self;
}
/**
* Alias to {@link #withNodeDistanceEvaluator(String, NodeDistanceEvaluator)} for the default
* profile.
*/
@NonNull
public SelfT withNodeDistanceEvaluator(@NonNull NodeDistanceEvaluator nodeDistanceEvaluator) {
return withNodeDistanceEvaluator(DriverExecutionProfile.DEFAULT_NAME, nodeDistanceEvaluator);
}
/**
* Adds a custom filter to include/exclude nodes for a particular execution profile. This assumes
* that you're also using a dedicated load balancing policy for that profile.
*
*
The predicate's {@link Predicate#test(Object) test()} method will be invoked each time the
* {@link LoadBalancingPolicy} processes a topology or state change: if it returns false, the
* policy will suggest distance IGNORED (meaning the driver won't ever connect to it if all
* policies agree), and never included in any query plan.
*
*
Note that this behavior is implemented in the driver built-in load balancing policies. If
* you use a custom policy implementation, you'll need to explicitly invoke the filter.
*
*
If the filter is specified programmatically with this method, it overrides the configuration
* (that is, the {@code load-balancing-policy.filter.class} option will be ignored).
*
*
This method has been deprecated in favor of {@link
* #withNodeDistanceEvaluator(String, NodeDistanceEvaluator)}. If you were using node
* filters, you can easily replace your filters with the following implementation of {@link
* NodeDistanceEvaluator}:
*
*
{@code
* public class NodeFilterToDistanceEvaluatorAdapter implements NodeDistanceEvaluator {
*
* private final Predicate nodeFilter;
*
* public NodeFilterToDistanceEvaluatorAdapter(Predicate nodeFilter) {
* this.nodeFilter = nodeFilter;
* }
*
* public NodeDistance evaluateDistance(Node node, String localDc) {
* return nodeFilter.test(node) ? null : NodeDistance.IGNORED;
* }
* }
* }
*
* The same can be achieved using a lambda + closure:
*
* {@code
* Predicate nodeFilter = ...
* NodeDistanceEvaluator evaluator =
* (node, localDc) -> nodeFilter.test(node) ? null : NodeDistance.IGNORED;
* }
*
* @see #withNodeFilter(Predicate)
* @deprecated Use {@link #withNodeDistanceEvaluator(String, NodeDistanceEvaluator)} instead.
*/
@Deprecated
@NonNull
public SelfT withNodeFilter(@NonNull String profileName, @NonNull Predicate nodeFilter) {
this.programmaticArgumentsBuilder.withNodeFilter(profileName, nodeFilter);
return self;
}
/**
* Alias to {@link #withNodeFilter(String, Predicate)} for the default profile.
*
* This method has been deprecated in favor of {@link
* #withNodeDistanceEvaluator(NodeDistanceEvaluator)}. See the javadocs of {@link
* #withNodeFilter(String, Predicate)} to understand how to migrate your legacy node filters.
*
* @deprecated Use {@link #withNodeDistanceEvaluator(NodeDistanceEvaluator)} instead.
*/
@Deprecated
@NonNull
public SelfT withNodeFilter(@NonNull Predicate nodeFilter) {
return withNodeFilter(DriverExecutionProfile.DEFAULT_NAME, nodeFilter);
}
/**
* Sets the keyspace to connect the session to.
*
* Note that this can also be provided by the configuration; if both are defined, this method
* takes precedence.
*/
@NonNull
public SelfT withKeyspace(@Nullable CqlIdentifier keyspace) {
this.keyspace = keyspace;
return self;
}
/**
* Shortcut for {@link #withKeyspace(CqlIdentifier)
* setKeyspace(CqlIdentifier.fromCql(keyspaceName))}
*/
@NonNull
public SelfT withKeyspace(@Nullable String keyspaceName) {
return withKeyspace(keyspaceName == null ? null : CqlIdentifier.fromCql(keyspaceName));
}
/**
* The {@link ClassLoader} to use to reflectively load class names defined in configuration.
*
*
Unless you define a custom {@link #configLoader}, this class loader will also be used to
* locate application-specific configuration resources.
*
*
If you do not provide any custom class loader, the driver will attempt to use the following
* ones:
*
*
* - When reflectively loading class names defined in configuration: same class loader that
* loaded the core driver classes.
*
- When locating application-specific configuration resources: the current thread's
* {@linkplain Thread#getContextClassLoader() context class loader}.
*
*
* This is generally the right thing to do.
*
* Defining a different class loader is typically only needed in web or OSGi environments where
* there are complex class loading requirements.
*
*
For example, if the driver jar is loaded by the web server's system class loader (that is,
* the driver jar was placed in the "/lib" folder of the web server), but the application tries to
* load a custom load balancing policy declared in the web app's "WEB-INF/lib" folder, the system
* class loader will not be able to load such class. Instead, you must use the web app's class
* loader, that you can obtain by calling {@link Thread#getContextClassLoader()}:
*
*
{@code
* CqlSession.builder()
* .addContactEndPoint(...)
* .withClassLoader(Thread.currentThread().getContextClassLoader())
* .build();
* }
*
* Indeed, in most web environments, {@code Thread.currentThread().getContextClassLoader()} will
* return the web app's class loader, which is a child of the web server's system class loader.
* This class loader is thus capable of loading both the implemented interface and the
* implementing class, in spite of them being declared in different places.
*
* For OSGi deployments, it is usually not necessary to use this method. Even if the
* implemented interface and the implementing class are located in different bundles, the right
* class loader to use should be the default one (the driver bundle's class loader). In
* particular, it is not advised to rely on {@code Thread.currentThread().getContextClassLoader()}
* in OSGi environments, so you should never pass that class loader to this method. See Using
* a custom ClassLoader in our OSGi online docs for more information.
*/
@NonNull
public SelfT withClassLoader(@Nullable ClassLoader classLoader) {
this.programmaticArgumentsBuilder.withClassLoader(classLoader);
return self;
}
/**
* Configures this SessionBuilder for Cloud deployments by retrieving connection information from
* the provided {@link Path}.
*
*
To connect to a Cloud database, you must first download the secure database bundle from the
* DataStax Astra console that contains the connection information, then instruct the driver to
* read its contents using either this method or one if its variants.
*
*
For more information, please refer to the DataStax Astra documentation.
*
* @param cloudConfigPath Path to the secure connect bundle zip file.
* @see #withCloudSecureConnectBundle(URL)
* @see #withCloudSecureConnectBundle(InputStream)
*/
@NonNull
public SelfT withCloudSecureConnectBundle(@NonNull Path cloudConfigPath) {
try {
URL cloudConfigUrl = cloudConfigPath.toAbsolutePath().normalize().toUri().toURL();
this.cloudConfigInputStream = cloudConfigUrl::openStream;
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Incorrect format of cloudConfigPath", e);
}
return self;
}
/**
* Registers a CodecRegistry to use for the session.
*
*
When both this and {@link #addTypeCodecs(TypeCodec[])} are called, the added type codecs
* will be registered on the provided CodecRegistry.
*/
@NonNull
public SelfT withCodecRegistry(@Nullable MutableCodecRegistry codecRegistry) {
this.programmaticArgumentsBuilder.withCodecRegistry(codecRegistry);
return self;
}
/**
* Configures this SessionBuilder for Cloud deployments by retrieving connection information from
* the provided {@link URL}.
*
*
To connect to a Cloud database, you must first download the secure database bundle from the
* DataStax Astra console that contains the connection information, then instruct the driver to
* read its contents using either this method or one if its variants.
*
*
For more information, please refer to the DataStax Astra documentation.
*
* @param cloudConfigUrl URL to the secure connect bundle zip file.
* @see #withCloudSecureConnectBundle(Path)
* @see #withCloudSecureConnectBundle(InputStream)
*/
@NonNull
public SelfT withCloudSecureConnectBundle(@NonNull URL cloudConfigUrl) {
this.cloudConfigInputStream = cloudConfigUrl::openStream;
return self;
}
/**
* Configures this SessionBuilder for Cloud deployments by retrieving connection information from
* the provided {@link InputStream}.
*
*
To connect to a Cloud database, you must first download the secure database bundle from the
* DataStax Astra console that contains the connection information, then instruct the driver to
* read its contents using either this method or one if its variants.
*
*
For more information, please refer to the DataStax Astra documentation.
*
*
Note that the provided stream will be consumed and closed when either {@link
* #build()} or {@link #buildAsync()} are called; attempting to reuse it afterwards will result in
* an error being thrown.
*
* @param cloudConfigInputStream A stream containing the secure connect bundle zip file.
* @see #withCloudSecureConnectBundle(Path)
* @see #withCloudSecureConnectBundle(URL)
*/
@NonNull
public SelfT withCloudSecureConnectBundle(@NonNull InputStream cloudConfigInputStream) {
this.cloudConfigInputStream = () -> cloudConfigInputStream;
return self;
}
/**
* Configures this SessionBuilder to use the provided Cloud proxy endpoint.
*
*
Normally, this method should not be called directly; the normal and easiest way to configure
* the driver for Cloud deployments is through a {@linkplain #withCloudSecureConnectBundle(URL)
* secure connect bundle}.
*
*
Setting this option to any non-null address will make the driver use a special topology
* monitor tailored for Cloud deployments. This topology monitor assumes that the target cluster
* should be contacted through the proxy specified here, using SNI routing.
*
*
For more information, please refer to the DataStax Astra documentation.
*
* @param cloudProxyAddress The address of the Cloud proxy to use.
* @see Server Name Indication
*/
@NonNull
public SelfT withCloudProxyAddress(@Nullable InetSocketAddress cloudProxyAddress) {
this.programmaticArgumentsBuilder.withCloudProxyAddress(cloudProxyAddress);
return self;
}
/**
* A unique identifier for the created session.
*
*
It will be sent in the {@code STARTUP} protocol message, under the key {@code CLIENT_ID},
* for each new connection established by the driver. Currently, this information is used by
* Insights monitoring (if the target cluster does not support Insights, the entry will be ignored
* by the server).
*
*
If you don't call this method, the driver will generate an identifier with {@link
* Uuids#random()}.
*/
@NonNull
public SelfT withClientId(@Nullable UUID clientId) {
this.programmaticArgumentsBuilder.withStartupClientId(clientId);
return self;
}
/**
* The name of the application using the created session.
*
*
It will be sent in the {@code STARTUP} protocol message, under the key {@code
* APPLICATION_NAME}, for each new connection established by the driver. Currently, this
* information is used by Insights monitoring (if the target cluster does not support Insights,
* the entry will be ignored by the server).
*
*
This can also be defined in the driver configuration with the option {@code
* basic.application.name}; if you specify both, this method takes precedence and the
* configuration option will be ignored. If neither is specified, the entry is not included in the
* message.
*/
@NonNull
public SelfT withApplicationName(@Nullable String applicationName) {
this.programmaticArgumentsBuilder.withStartupApplicationName(applicationName);
return self;
}
/**
* The version of the application using the created session.
*
*
It will be sent in the {@code STARTUP} protocol message, under the key {@code
* APPLICATION_VERSION}, for each new connection established by the driver. Currently, this
* information is used by Insights monitoring (if the target cluster does not support Insights,
* the entry will be ignored by the server).
*
*
This can also be defined in the driver configuration with the option {@code
* basic.application.version}; if you specify both, this method takes precedence and the
* configuration option will be ignored. If neither is specified, the entry is not included in the
* message.
*/
@NonNull
public SelfT withApplicationVersion(@Nullable String applicationVersion) {
this.programmaticArgumentsBuilder.withStartupApplicationVersion(applicationVersion);
return self;
}
/**
* The metric registry object for storing driver metrics.
*
*
The argument should be an instance of the base registry type for the metrics framework you
* are using (see {@code advanced.metrics.factory.class} in the configuration):
*
*
* - Dropwizard (the default): {@code com.codahale.metrics.MetricRegistry}
*
- Micrometer: {@code io.micrometer.core.instrument.MeterRegistry}
*
- MicroProfile: {@code org.eclipse.microprofile.metrics.MetricRegistry}
*
*
* Only MicroProfile requires an external instance of its registry to be provided. For
* Micrometer, if no Registry object is provided, Micrometer's {@code globalRegistry} will be
* used. For Dropwizard, if no Registry object is provided, an instance of {@code MetricRegistry}
* will be created and used.
*/
@NonNull
public SelfT withMetricRegistry(@Nullable Object metricRegistry) {
this.programmaticArgumentsBuilder.withMetricRegistry(metricRegistry);
return self;
}
/**
* Creates the session with the options set by this builder.
*
* The session initialization will happen asynchronously in a driver internal thread pool.
*
* @return a completion stage that completes with the session when it is fully initialized.
*/
@NonNull
public CompletionStage buildAsync() {
CompletionStage buildStage = buildDefaultSessionAsync();
CompletionStage wrapStage = buildStage.thenApply(this::wrap);
// thenApply does not propagate cancellation (!)
CompletableFutures.propagateCancellation(wrapStage, buildStage);
return wrapStage;
}
/**
* Convenience method to call {@link #buildAsync()} and block on the result.
*
* Usage in non-blocking applications: beware that session initialization is a costly
* operation. It should only be triggered from a thread that is allowed to block. If that is not
* the case, consider using {@link #buildAsync()} instead.
*
*
This must not be called on a driver thread.
*/
@NonNull
public SessionT build() {
BlockingOperation.checkNotDriverThread();
return CompletableFutures.getUninterruptibly(buildAsync());
}
protected abstract SessionT wrap(@NonNull CqlSession defaultSession);
@NonNull
protected final CompletionStage buildDefaultSessionAsync() {
try {
ProgrammaticArguments programmaticArguments = programmaticArgumentsBuilder.build();
DriverConfigLoader configLoader =
this.configLoader != null
? this.configLoader
: defaultConfigLoader(programmaticArguments.getClassLoader());
DriverExecutionProfile defaultConfig = configLoader.getInitialConfig().getDefaultProfile();
if (cloudConfigInputStream == null) {
String configUrlString =
defaultConfig.getString(DefaultDriverOption.CLOUD_SECURE_CONNECT_BUNDLE, null);
if (configUrlString != null) {
cloudConfigInputStream = () -> getURL(configUrlString).openStream();
}
}
List configContactPoints =
defaultConfig.getStringList(DefaultDriverOption.CONTACT_POINTS, Collections.emptyList());
if (cloudConfigInputStream != null) {
if (!programmaticContactPoints.isEmpty() || !configContactPoints.isEmpty()) {
LOG.info(
"Both a secure connect bundle and contact points were provided. These are mutually exclusive. The contact points from the secure bundle will have priority.");
// clear the contact points provided in the setting file and via addContactPoints
configContactPoints = Collections.emptyList();
programmaticContactPoints = new HashSet<>();
}
if (programmaticSslFactory
|| defaultConfig.isDefined(DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS)) {
LOG.info(
"Both a secure connect bundle and SSL options were provided. They are mutually exclusive. The SSL options from the secure bundle will have priority.");
}
CloudConfig cloudConfig =
new CloudConfigFactory().createCloudConfig(cloudConfigInputStream.call());
addContactEndPoints(cloudConfig.getEndPoints());
boolean localDataCenterDefined =
anyProfileHasDatacenterDefined(configLoader.getInitialConfig());
if (programmaticLocalDatacenter || localDataCenterDefined) {
LOG.info(
"Both a secure connect bundle and a local datacenter were provided. They are mutually exclusive. The local datacenter from the secure bundle will have priority.");
programmaticArgumentsBuilder.clearDatacenters();
}
withLocalDatacenter(cloudConfig.getLocalDatacenter());
withSslEngineFactory(cloudConfig.getSslEngineFactory());
withCloudProxyAddress(cloudConfig.getProxyAddress());
programmaticArguments = programmaticArgumentsBuilder.build();
}
boolean resolveAddresses =
defaultConfig.getBoolean(DefaultDriverOption.RESOLVE_CONTACT_POINTS, true);
Set contactPoints =
ContactPoints.merge(programmaticContactPoints, configContactPoints, resolveAddresses);
if (keyspace == null && defaultConfig.isDefined(DefaultDriverOption.SESSION_KEYSPACE)) {
keyspace =
CqlIdentifier.fromCql(defaultConfig.getString(DefaultDriverOption.SESSION_KEYSPACE));
}
return DefaultSession.init(
(InternalDriverContext) buildContext(configLoader, programmaticArguments),
contactPoints,
keyspace);
} catch (Throwable t) {
// We construct the session synchronously (until the init() call), but async clients expect a
// failed future if anything goes wrong. So wrap any error from that synchronous part.
return CompletableFutures.failedFuture(t);
}
}
private boolean anyProfileHasDatacenterDefined(DriverConfig driverConfig) {
for (DriverExecutionProfile driverExecutionProfile : driverConfig.getProfiles().values()) {
if (driverExecutionProfile.isDefined(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER)) {
return true;
}
}
return false;
}
/**
* Returns URL based on the configUrl setting. If the configUrl has no protocol provided, the
* method will fallback to file:// protocol and return URL that has file protocol specified.
*
* @param configUrl url to config secure bundle
* @return URL with file protocol if there was not explicit protocol provided in the configUrl
* setting
*/
private URL getURL(String configUrl) throws MalformedURLException {
try {
return new URL(configUrl);
} catch (MalformedURLException e1) {
try {
return Paths.get(configUrl).toAbsolutePath().normalize().toUri().toURL();
} catch (MalformedURLException e2) {
e2.addSuppressed(e1);
throw e2;
}
}
}
/**
* This must return an instance of {@code InternalDriverContext} (it's not expressed
* directly in the signature to avoid leaking that type through the protected API).
*/
protected DriverContext buildContext(
DriverConfigLoader configLoader, ProgrammaticArguments programmaticArguments) {
// Preserve backward compatibility with the deprecated method:
@SuppressWarnings("deprecation")
DriverContext legacyApiContext =
buildContext(
configLoader,
programmaticArguments.getTypeCodecs(),
programmaticArguments.getNodeStateListener(),
programmaticArguments.getSchemaChangeListener(),
programmaticArguments.getRequestTracker(),
programmaticArguments.getLocalDatacenters(),
programmaticArguments.getNodeFilters(),
programmaticArguments.getClassLoader());
if (legacyApiContext != null) {
return legacyApiContext;
}
return new DefaultDriverContext(configLoader, programmaticArguments);
}
/**
* @deprecated this method only exists for backward compatibility (if a subclass written for
* driver 4.1.0 returns a non-null result, that value will be used). Please override {@link
* #buildContext(DriverConfigLoader, ProgrammaticArguments)} instead.
*/
@Deprecated
@SuppressWarnings("DeprecatedIsStillUsed")
protected DriverContext buildContext(
DriverConfigLoader configLoader,
List> typeCodecs,
NodeStateListener nodeStateListener,
SchemaChangeListener schemaChangeListener,
RequestTracker requestTracker,
Map localDatacenters,
Map> nodeFilters,
ClassLoader classLoader) {
return null;
}
}