package org.apereo.cas.couchbase.core;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseCluster;
import com.couchbase.client.java.view.DesignDocument;
import com.couchbase.client.java.view.View;
import com.google.common.base.Throwables;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* A factory class which produces a client for a particular Couchbase bucket.
* A design consideration was that we want the server to start even if Couchbase
* is unavailable, picking up the connection when Couchbase comes online. Hence
* the creation of the client is made using a scheduled task which is repeated
* until successful connection is made.
*
* @author Fredrik Jönsson "fjo@kth.se"
* @author Misagh Moayyed
* @since 4.2
*/
public class CouchbaseClientFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseClientFactory.class);
private Cluster cluster;
private Bucket bucket;
private List<View> views;
private final Set<String> nodes;
/* The name of the bucket, will use the default bucket unless otherwise specified. */
private String bucketName = "default";
/* Password for the bucket if any. */
private String password = StringUtils.EMPTY;
/* Design document and views to create in the bucket, if any. */
private String designDocument;
private long timeout = 5;
/**
* Instantiates a new Couchbase client factory.
* @param nodes cluster nodes
* @param bucketName bucket name
* @param password cluster password
* @param timeout connection timeout
*/
public CouchbaseClientFactory(final Set<String> nodes, final String bucketName, final String password, final long timeout) {
this.nodes = nodes;
this.bucketName = bucketName;
this.password = password;
this.timeout = timeout;
}
/**
* Start initializing the client. This will schedule a task that retries
* connection until successful.
*/
public void initialize() {
try {
LOGGER.debug("Trying to connect to couchbase bucket [{}]", this.bucketName);
this.cluster = CouchbaseCluster.create(new ArrayList<>(this.nodes));
this.bucket = this.cluster.openBucket(this.bucketName, this.password, this.timeout, TimeUnit.SECONDS);
LOGGER.info("Connected to Couchbase bucket [{}]", this.bucketName);
if (this.views != null) {
doEnsureIndexes(this.designDocument, this.views);
}
} catch (final Exception e) {
throw new RuntimeException("Failed to connect to Couchbase bucket", e);
}
}
/**
* Inverse of initialize, shuts down the client, cancelling connection
* task if not completed.
*/
public void shutdown() {
try {
if (this.cluster != null) {
this.cluster.disconnect();
}
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
/**
* Retrieve the Couchbase bucket.
*
* @return the bucket.
*/
public Bucket bucket() {
if (this.bucket != null) {
return this.bucket;
}
throw new RuntimeException("Connection to bucket " + this.bucketName + " not initialized yet.");
}
/**
* Register indexes to ensure in the bucket when the client is initialized.
*
* @param documentName name of the Couchbase design document.
* @param views the list of Couchbase views (i.e. indexes) to create in the document.
*/
public void ensureIndexes(final String documentName, final List<View> views) {
this.designDocument = documentName;
this.views = views;
}
/**
* Ensures that all views exists in the database.
*
* @param documentName the name of the design document.
* @param views the views to ensure exists in the database.
*/
private void doEnsureIndexes(final String documentName, final List<View> views) {
LOGGER.debug("Ensure that indexes exist in bucket [{}]", this.bucket.name());
final DesignDocument newDocument = DesignDocument.create(documentName, views);
if (!newDocument.equals(this.bucket.bucketManager().getDesignDocument(documentName))) {
LOGGER.warn("Missing indexes in bucket [{}] for document [{}]", this.bucket.name(), documentName);
this.bucket.bucketManager().upsertDesignDocument(newDocument);
}
}
}