com.couchbase.client.java.Cluster Maven / Gradle / Ivy
Show all versions of java-client Show documentation
/*
* Copyright (c) 2018 Couchbase, Inc.
*
* 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 com.couchbase.client.java;
import com.couchbase.client.core.Core;
import com.couchbase.client.core.CoreLimiter;
import com.couchbase.client.core.annotation.Stability;
import com.couchbase.client.core.diagnostics.ClusterState;
import com.couchbase.client.core.diagnostics.DiagnosticsResult;
import com.couchbase.client.core.diagnostics.PingResult;
import com.couchbase.client.core.env.Authenticator;
import com.couchbase.client.core.env.OwnedOrExternal;
import com.couchbase.client.core.env.PasswordAuthenticator;
import com.couchbase.client.core.env.SeedNode;
import com.couchbase.client.core.error.CouchbaseException;
import com.couchbase.client.core.error.TimeoutException;
import com.couchbase.client.core.error.context.ReducedQueryErrorContext;
import com.couchbase.client.core.util.ConnectionString;
import com.couchbase.client.java.analytics.AnalyticsOptions;
import com.couchbase.client.java.analytics.AnalyticsResult;
import com.couchbase.client.java.codec.JsonSerializer;
import com.couchbase.client.java.diagnostics.DiagnosticsOptions;
import com.couchbase.client.java.diagnostics.PingOptions;
import com.couchbase.client.java.diagnostics.WaitUntilReadyOptions;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.http.CouchbaseHttpClient;
import com.couchbase.client.java.manager.analytics.AnalyticsIndexManager;
import com.couchbase.client.java.manager.bucket.BucketManager;
import com.couchbase.client.java.manager.eventing.EventingFunctionManager;
import com.couchbase.client.java.manager.query.QueryIndexManager;
import com.couchbase.client.java.manager.search.SearchIndexManager;
import com.couchbase.client.java.manager.user.UserManager;
import com.couchbase.client.java.query.QueryAccessor;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryResult;
import com.couchbase.client.java.search.SearchOptions;
import com.couchbase.client.java.search.SearchQuery;
import com.couchbase.client.java.search.SearchRequest;
import com.couchbase.client.java.search.result.SearchResult;
import com.couchbase.client.java.search.vector.VectorSearch;
import com.couchbase.client.java.transactions.Transactions;
import java.io.Closeable;
import java.time.Duration;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static com.couchbase.client.core.util.ConnectionStringUtil.asConnectionString;
import static com.couchbase.client.core.util.Validators.notNull;
import static com.couchbase.client.core.util.Validators.notNullOrEmpty;
import static com.couchbase.client.java.AsyncCluster.extractClusterEnvironment;
import static com.couchbase.client.java.AsyncUtils.block;
import static com.couchbase.client.java.ClusterOptions.clusterOptions;
import static com.couchbase.client.java.ReactiveCluster.DEFAULT_ANALYTICS_OPTIONS;
import static com.couchbase.client.java.ReactiveCluster.DEFAULT_DIAGNOSTICS_OPTIONS;
import static com.couchbase.client.java.ReactiveCluster.DEFAULT_QUERY_OPTIONS;
import static com.couchbase.client.java.ReactiveCluster.DEFAULT_SEARCH_OPTIONS;
/**
* The {@link Cluster} is the main entry point when connecting to a Couchbase cluster.
*
* Most likely you want to start out by using the {@link #connect(String, String, String)} entry point. For more
* advanced options you want to use the {@link #connect(String, ClusterOptions)} method. The entry point that allows
* overriding the seed nodes ({@link #connect(Set, ClusterOptions)} is only needed if you run a couchbase cluster
* at non-standard ports.
*
* See the individual connect methods for more information, but here is a snippet to get you off the ground quickly. It
* assumes you have Couchbase running locally and the "travel-sample" sample bucket loaded:
*
* //Connect and open a bucket
* Cluster cluster = Cluster.connect("127.0.0.1","Administrator","password");
* Bucket bucket = cluster.bucket("travel-sample");
* Collection collection = bucket.defaultCollection();
*
* // Perform a N1QL query
* QueryResult queryResult = cluster.query("select * from `travel-sample` limit 5");
* System.out.println(queryResult.rowsAsObject());
*
* // Perform a KV request and load a document
* GetResult getResult = collection.get("airline_10");
* System.out.println(getResult);
*
*
* When the application shuts down (or the SDK is not needed anymore), you are required to call {@link #disconnect()}.
* If you omit this step, the application will terminate (all spawned threads are daemon threads) but any operations
* or work in-flight will not be able to complete and lead to undesired side-effects. Note that disconnect will also
* shutdown all associated {@link Bucket buckets}.
*
* Cluster-level operations like {@link #query(String)} will not work unless at leas one bucket is opened against a
* pre 6.5 cluster. If you are using 6.5 or later, you can run cluster-level queries without opening a bucket. All
* of these operations are lazy, so the SDK will bootstrap in the background and service queries as quickly as possible.
* This also means that the first operations might be a bit slower until all sockets are opened in the background and
* the configuration is loaded. If you want to wait explicitly, you can utilize the {@link #waitUntilReady(Duration)}
* method before performing your first query.
*
* The SDK will only work against Couchbase Server 5.0 and later, because RBAC (role-based access control) is a first
* class concept since 3.0 and therefore required.
*/
public class Cluster implements Closeable {
/**
* Holds the underlying async cluster reference.
*/
private final AsyncCluster asyncCluster;
/**
* Holds the adjacent reactive cluster reference.
*/
private final ReactiveCluster reactiveCluster;
/**
* Stores already opened buckets for reuse.
*/
private final Map bucketCache = new ConcurrentHashMap<>();
/**
* Connect to a Couchbase cluster with a username and a password as credentials.
*
* This is the simplest (and recommended) method to connect to the cluster if you do not need to provide any
* custom options.
*
* The first argument (the connection string in its simplest form) is used to supply the hostnames of the cluster. In
* development it is OK to only pass in one hostname (or IP address), but in production we recommend passing in at
* least 3 nodes of the cluster (comma separated). The reason is that if one or more of the nodes are not reachable
* the client will still be able to bootstrap (and your application will become more resilient as a result).
*
* Here is how you specify one node to use for bootstrapping:
*
* Cluster cluster = Cluster.connect("127.0.0.1", "user", "password"); // ok during development
*
* This is what we recommend in production:
*
* Cluster cluster = Cluster.connect("host1,host2,host3", "user", "password"); // recommended in production
*
* It is important to understand that the SDK will only use the bootstrap ("seed nodes") host list to establish an
* initial contact with the cluster. Once the configuration is loaded this list is discarded and the client will
* connect to all nodes based on this configuration.
*
* This method will return immediately and the SDK will try to establish all the necessary resources and connections
* in the background. This means that depending on how fast it can be bootstrapped, the first couple cluster-level
* operations like {@link #query(String)} will take a bit longer. If you want to wait explicitly until those resources
* are available, you can use the {@link #waitUntilReady(Duration)} method before running any of them:
*
* Cluster cluster = Cluster.connect("host1,host2,host3", "user", "password");
* cluster.waitUntilReady(Duration.ofSeconds(5));
* QueryResult result = cluster.query("select * from bucket limit 1");
*
*
* @param connectionString connection string used to locate the Couchbase cluster.
* @param username the name of the user with appropriate permissions on the cluster.
* @param password the password of the user with appropriate permissions on the cluster.
* @return the instantiated {@link Cluster}.
*/
public static Cluster connect(final String connectionString, final String username, final String password) {
return connect(connectionString, clusterOptions(PasswordAuthenticator.create(username, password)));
}
/**
* Connect to a Couchbase cluster with custom options.
*
* You likely want to use this over the simpler {@link #connect(String, String, String)} if:
*
* - A custom {@link ClusterEnvironment}
* - Or a custom {@link Authenticator}
*
* needs to be provided.
*
* A custom environment can be passed in like this:
*
* // on bootstrap:
* ClusterEnvironment environment = ClusterEnvironment.builder().build();
* Cluster cluster = Cluster.connect(
* "127.0.0.1",
* clusterOptions("user", "password").environment(environment)
* );
*
* // on shutdown:
* cluster.disconnect();
* environment.shutdown();
*
* It is VERY important to shut down the environment when being passed in separately (as shown in
* the code sample above) and AFTER the cluster is disconnected. This will ensure an orderly shutdown
* and makes sure that no resources are left lingering.
*
* If you want to pass in a custom {@link Authenticator}, it is likely because you are setting up certificate-based
* authentication instead of using a username and a password directly. Remember to also enable TLS.
*
* ClusterEnvironment environment = ClusterEnvironment
* .builder()
* .securityConfig(SecurityConfig.enableTls(true))
* .build();
*
* Authenticator authenticator = CertificateAuthenticator.fromKey(...);
*
* Cluster cluster = Cluster.connect(
* "127.0.0.1",
* clusterOptions(authenticator).environment(environment)
* );
*
* This method will return immediately and the SDK will try to establish all the necessary resources and connections
* in the background. This means that depending on how fast it can be bootstrapped, the first couple cluster-level
* operations like {@link #query(String)} will take a bit longer. If you want to wait explicitly until those resources
* are available, you can use the {@link #waitUntilReady(Duration)} method before running any of them:
*
* Cluster cluster = Cluster.connect("host1,host2,host3", "user", "password");
* cluster.waitUntilReady(Duration.ofSeconds(5));
* QueryResult result = cluster.query("select * from bucket limit 1");
*
*
* @param connectionString connection string used to locate the Couchbase cluster.
* @param options custom options when creating the cluster.
* @return the instantiated {@link Cluster}.
*/
public static Cluster connect(final String connectionString, final ClusterOptions options) {
notNullOrEmpty(connectionString, "ConnectionString");
notNull(options, "ClusterOptions");
final ConnectionString connStr = ConnectionString.create(connectionString);
final ClusterOptions.Built opts = options.build();
final OwnedOrExternal environmentSupplier = extractClusterEnvironment(connStr, opts);
return new Cluster(
environmentSupplier,
opts.authenticator(),
connStr
);
}
/**
* Connect to a Couchbase cluster with a list of seed nodes and custom options.
*
* Note that you likely only want to use this method if you need to pass in custom ports for specific seed nodes
* during bootstrap. Otherwise we recommend relying on the simpler {@link #connect(String, String, String)} method
* instead.
*
* The following example shows how to bootstrap against a node with custom KV and management ports:
*
* var seedNodes = Set.of(
* SeedNode.create("127.0.0.1")
* .withKvPort(12000)
* .withManagerPort(9000)
* );
* Cluster cluster Cluster.connect(seedNodes, clusterOptions("user", "password"));
*
*
* @param seedNodes the seed nodes used to connect to the cluster.
* @param options custom options when creating the cluster.
* @return the instantiated {@link Cluster}.
*/
public static Cluster connect(final Set seedNodes, final ClusterOptions options) {
return connect(asConnectionString(seedNodes).original(), options);
}
/**
* Configures the maximum allowed core instances before warning/failing.
*
* @param maxAllowedInstances the number of max allowed core instances.
*/
@Stability.Uncommitted
public static void maxAllowedInstances(final int maxAllowedInstances) {
CoreLimiter.setMaxAllowedInstances(maxAllowedInstances);
}
/**
* Configures if the SDK should fail to create instead of warn if the instance limit is reached.
*
* @param failIfInstanceLimitReached true if it should throw an exception instead of warn.
*/
@Stability.Uncommitted
public static void failIfInstanceLimitReached(final boolean failIfInstanceLimitReached) {
CoreLimiter.setFailIfInstanceLimitReached(failIfInstanceLimitReached);
}
private Cluster(
final OwnedOrExternal environment,
final Authenticator authenticator,
final ConnectionString connectionString
) {
this.asyncCluster = new AsyncCluster(environment, authenticator, connectionString);
this.reactiveCluster = new ReactiveCluster(asyncCluster);
}
/**
* Provides access to the related {@link AsyncCluster}.
*
* Note that the {@link AsyncCluster} is considered advanced API and should only be used to get the last drop
* of performance or if you are building higher-level abstractions on top. If in doubt, we recommend using the
* {@link #reactive()} API instead.
*/
public AsyncCluster async() {
return asyncCluster;
}
/**
* Provides access to the related {@link ReactiveCluster}.
*/
public ReactiveCluster reactive() {
return reactiveCluster;
}
/**
* Provides access to the underlying {@link Core}.
*
*
This is advanced and volatile API - it might change any time without notice. Use with care!
*/
@Stability.Volatile
public Core core() {
return asyncCluster.core();
}
/**
* Returns a specialized HTTP client for making requests to the Couchbase Server REST API.
*/
@Stability.Volatile
public CouchbaseHttpClient httpClient() {
return new CouchbaseHttpClient(asyncCluster.httpClient());
}
/**
* The user manager allows to manage users and groups.
*/
public UserManager users() {
return new UserManager(asyncCluster.users());
}
/**
* The bucket manager allows to perform administrative tasks on buckets and their resources.
*/
public BucketManager buckets() {
return new BucketManager(asyncCluster.buckets());
}
/**
* The analytics index manager allows to modify and create indexes for the analytics service.
*/
public AnalyticsIndexManager analyticsIndexes() {
return new AnalyticsIndexManager(this);
}
/**
* The query index manager allows to modify and create indexes for the query service.
*/
public QueryIndexManager queryIndexes() {
return new QueryIndexManager(asyncCluster.queryIndexes());
}
/**
* The search index manager allows to modify and create indexes for the search service.
*/
public SearchIndexManager searchIndexes() {
return new SearchIndexManager(asyncCluster.searchIndexes());
}
/**
* Provides access to the eventing function management services for functions in the admin scope.
*/
@Stability.Uncommitted
public EventingFunctionManager eventingFunctions() {
return new EventingFunctionManager(asyncCluster.eventingFunctions());
}
/**
* Provides access to the used {@link ClusterEnvironment}.
*/
public ClusterEnvironment environment() {
return asyncCluster.environment();
}
/**
* Performs a query against the query (N1QL) services.
*
* @param statement the N1QL query statement.
* @return the {@link QueryResult} once the response arrives successfully.
* @throws TimeoutException if the operation times out before getting a result.
* @throws CouchbaseException for all other error reasons (acts as a base type and catch-all).
*/
public QueryResult query(final String statement) {
return query(statement, DEFAULT_QUERY_OPTIONS);
}
/**
* Performs a query against the query (N1QL) services with custom options.
*
* @param statement the N1QL query statement as a raw string.
* @param options the custom options for this query.
* @return the {@link QueryResult} once the response arrives successfully.
* @throws TimeoutException if the operation times out before getting a result.
* @throws CouchbaseException for all other error reasons (acts as a base type and catch-all).
*/
public QueryResult query(final String statement, final QueryOptions options) {
notNull(options, "QueryOptions", () -> new ReducedQueryErrorContext(statement));
final QueryOptions.Built opts = options.build();
JsonSerializer serializer = opts.serializer() == null ? environment().jsonSerializer() : opts.serializer();
return new QueryResult(async().queryOps.queryBlocking(statement, opts, null, null, QueryAccessor::convertCoreQueryError), serializer);
}
/**
* Performs an analytics query with default {@link AnalyticsOptions}.
*
* @param statement the query statement as a raw string.
* @return the {@link AnalyticsResult} once the response arrives successfully.
* @throws TimeoutException if the operation times out before getting a result.
* @throws CouchbaseException for all other error reasons (acts as a base type and catch-all).
*/
public AnalyticsResult analyticsQuery(final String statement) {
return analyticsQuery(statement, DEFAULT_ANALYTICS_OPTIONS);
}
/**
* Performs an analytics query with custom {@link AnalyticsOptions}.
*
* @param statement the query statement as a raw string.
* @param options the custom options for this query.
* @return the {@link AnalyticsResult} once the response arrives successfully.
* @throws TimeoutException if the operation times out before getting a result.
* @throws CouchbaseException for all other error reasons (acts as a base type and catch-all).
*/
public AnalyticsResult analyticsQuery(final String statement, final AnalyticsOptions options) {
return block(async().analyticsQuery(statement, options));
}
/**
* Performs a request against the Full Text Search (FTS) service, with default {@link SearchOptions}.
*
* This can be used to perform a traditional FTS query, and/or a vector search.
*
* This method is for global FTS indexes. For scoped indexes, use {@link Scope} instead.
*
* @param searchRequest the request, in the form of a {@link SearchRequest}
* @return the {@link SearchResult} once the response arrives successfully.
* @throws TimeoutException if the operation times out before getting a result.
* @throws CouchbaseException for all other error reasons (acts as a base type and catch-all).
*/
public SearchResult search(final String indexName, final SearchRequest searchRequest) {
return search(indexName, searchRequest, DEFAULT_SEARCH_OPTIONS);
}
/**
* Performs a request against the Full Text Search (FTS) service, with custom {@link SearchOptions}.
*
* This can be used to perform a traditional FTS query, and/or a vector search.
*
* This method is for global FTS indexes. For scoped indexes, use {@link Scope} instead.
*
* @param searchRequest the request, in the form of a {@link SearchRequest}
* @return the {@link SearchResult} once the response arrives successfully.
* @throws TimeoutException if the operation times out before getting a result.
* @throws CouchbaseException for all other error reasons (acts as a base type and catch-all).
*/
public SearchResult search(final String indexName, final SearchRequest searchRequest, final SearchOptions options) {
return block(asyncCluster.search(indexName, searchRequest, options));
}
/**
* Performs a Full Text Search (FTS) query with default {@link SearchOptions}.
*
* This method is for global FTS indexes. For scoped indexes, use {@link Scope} instead.
*
* New users should consider the newer {@link #search(String, SearchRequest)} interface instead, which can do both the traditional FTS {@link SearchQuery} that this method performs,
* and/or can also be used to perform a {@link VectorSearch}.
*
* @param query the query, in the form of a {@link SearchQuery}
* @return the {@link SearchResult} once the response arrives successfully.
* @throws TimeoutException if the operation times out before getting a result.
* @throws CouchbaseException for all other error reasons (acts as a base type and catch-all).
*/
public SearchResult searchQuery(final String indexName, final SearchQuery query) {
return searchQuery(indexName, query, DEFAULT_SEARCH_OPTIONS);
}
/**
* Performs a Full Text Search (FTS) query with custom {@link SearchOptions}.
*
* This method is for global FTS indexes. For scoped indexes, use {@link Scope} instead.
*
* New users should consider the newer {@link #search(String, SearchRequest)} interface instead, which can do both the traditional FTS {@link SearchQuery} that this method performs,
* and/or can also be used to perform a {@link VectorSearch}.
*
* @param query the query, in the form of a {@link SearchQuery}
* @param options the custom options for this query.
* @return the {@link SearchResult} once the response arrives successfully.
* @throws TimeoutException if the operation times out before getting a result.
* @throws CouchbaseException for all other error reasons (acts as a base type and catch-all).
*/
public SearchResult searchQuery(final String indexName, final SearchQuery query, final SearchOptions options) {
return block(asyncCluster.searchQuery(indexName, query, options));
}
/**
* Opens a {@link Bucket} with the given name.
*
* @param bucketName the name of the bucket to open.
* @return a {@link Bucket} once opened.
*/
public Bucket bucket(final String bucketName) {
return bucketCache.computeIfAbsent(bucketName, n -> new Bucket(asyncCluster.bucket(n)));
}
/**
* Performs a non-reversible disconnect of this {@link Cluster}.
*
* If this method is used, the default disconnect timeout on the environment is used. Please use the companion
* overload ({@link #disconnect(Duration)} if you want to provide a custom duration.
*
* If a custom {@link ClusterEnvironment} has been passed in during connect, it is VERY important to
* shut it down after calling this method. This will prevent any in-flight tasks to be stopped prematurely.
*/
public void disconnect() {
block(asyncCluster.disconnect());
}
/**
* Performs a non-reversible disconnect of this {@link Cluster}.
*
* If a custom {@link ClusterEnvironment} has been passed in during connect, it is VERY important to
* shut it down after calling this method. This will prevent any in-flight tasks to be stopped prematurely.
*
* @param timeout allows to override the default disconnect duration.
*/
public void disconnect(final Duration timeout) {
block(asyncCluster.disconnect(timeout));
}
/**
* Runs a diagnostic report on the current state of the cluster from the SDKs point of view.
*
* Please note that it does not perform any I/O to do this, it will only use the current known state of the cluster
* to assemble the report (so, if for example no N1QL query has been run the socket pool might be empty and as
* result not show up in the report).
*
* @return the {@link DiagnosticsResult} once complete.
*/
public DiagnosticsResult diagnostics() {
return block(asyncCluster.diagnostics(DEFAULT_DIAGNOSTICS_OPTIONS));
}
/**
* Runs a diagnostic report with custom options on the current state of the cluster from the SDKs point of view.
*
* Please note that it does not perform any I/O to do this, it will only use the current known state of the cluster
* to assemble the report (so, if for example no N1QL query has been run the socket pool might be empty and as
* result not show up in the report).
*
* @param options options that allow to customize the report.
* @return the {@link DiagnosticsResult} once complete.
*/
public DiagnosticsResult diagnostics(final DiagnosticsOptions options) {
return block(asyncCluster.diagnostics(options));
}
/**
* Performs application-level ping requests against services in the couchbase cluster.
*
* Note that this operation performs active I/O against services and endpoints to assess their health. If you do
* not wish to perform I/O, consider using the {@link #diagnostics()} instead. You can also combine the functionality
* of both APIs as needed, which is {@link #waitUntilReady(Duration)} is doing in its implementation as well.
*
* @return the {@link PingResult} once complete.
*/
public PingResult ping() {
return block(asyncCluster.ping());
}
/**
* Performs application-level ping requests with custom options against services in the couchbase cluster.
*
* Note that this operation performs active I/O against services and endpoints to assess their health. If you do
* not wish to perform I/O, consider using the {@link #diagnostics(DiagnosticsOptions)} instead. You can also combine
* the functionality of both APIs as needed, which is {@link #waitUntilReady(Duration)} is doing in its
* implementation as well.
*
* @return the {@link PingResult} once complete.
*/
public PingResult ping(final PingOptions options) {
return block(asyncCluster.ping(options));
}
/**
* Waits until the desired {@link ClusterState} is reached.
*
* This method will wait until either the cluster state is "online", or the timeout is reached. Since the SDK is
* bootstrapping lazily, this method allows to eagerly check during bootstrap if all of the services are online
* and usable before moving on.
*
* @param timeout the maximum time to wait until readiness.
*/
public void waitUntilReady(final Duration timeout) {
block(asyncCluster.waitUntilReady(timeout));
}
/**
* Waits until the desired {@link ClusterState} is reached.
*
* This method will wait until either the cluster state is "online" by default, or the timeout is reached. Since the
* SDK is bootstrapping lazily, this method allows to eagerly check during bootstrap if all of the services are online
* and usable before moving on. You can tune the properties through {@link WaitUntilReadyOptions}.
*
* @param timeout the maximum time to wait until readiness.
* @param options the options to customize the readiness waiting.
*/
public void waitUntilReady(final Duration timeout, final WaitUntilReadyOptions options) {
block(asyncCluster.waitUntilReady(timeout, options));
}
/**
* Allows access to transactions.
*
* @return the {@link Transactions} interface.
*/
@Stability.Uncommitted
public Transactions transactions() {
return new Transactions(core(), environment().jsonSerializer());
}
/**
* Implementing the {@link Closeable} interface for try-with-resources support.
*
* Calls {@link #disconnect()} and as such behaves with similar semantics.
*/
@Override
public void close() {
disconnect();
}
}