Java tutorial
/** * Copyright (C) 2009-2013 Couchbase, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING * IN THE SOFTWARE. */ package com.couchbase.client; import com.couchbase.client.clustermanager.FlushResponse; import com.couchbase.client.internal.HttpFuture; import com.couchbase.client.internal.ViewFuture; import com.couchbase.client.protocol.views.AbstractView; import com.couchbase.client.protocol.views.DesignDocFetcherOperation; import com.couchbase.client.protocol.views.DesignDocFetcherOperationImpl; import com.couchbase.client.protocol.views.DesignDocOperationImpl; import com.couchbase.client.protocol.views.DesignDocument; import com.couchbase.client.protocol.views.DocsOperationImpl; import com.couchbase.client.protocol.views.HttpOperation; import com.couchbase.client.protocol.views.HttpOperationImpl; import com.couchbase.client.protocol.views.InvalidViewException; import com.couchbase.client.protocol.views.NoDocsOperationImpl; import com.couchbase.client.protocol.views.Paginator; import com.couchbase.client.protocol.views.Query; import com.couchbase.client.protocol.views.ReducedOperationImpl; import com.couchbase.client.protocol.views.SpatialView; import com.couchbase.client.protocol.views.SpatialViewFetcherOperation; import com.couchbase.client.protocol.views.SpatialViewFetcherOperationImpl; import com.couchbase.client.protocol.views.View; import com.couchbase.client.protocol.views.ViewFetcherOperation; import com.couchbase.client.protocol.views.ViewFetcherOperationImpl; import com.couchbase.client.protocol.views.ViewOperation.ViewCallback; import com.couchbase.client.protocol.views.ViewResponse; import com.couchbase.client.protocol.views.ViewRow; import com.couchbase.client.vbucket.Reconfigurable; import com.couchbase.client.vbucket.VBucketNodeLocator; import com.couchbase.client.vbucket.config.Bucket; import com.couchbase.client.vbucket.config.Config; import com.couchbase.client.vbucket.config.ConfigType; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; import net.spy.memcached.AddrUtil; import net.spy.memcached.BroadcastOpFactory; import net.spy.memcached.CASResponse; import net.spy.memcached.CASValue; import net.spy.memcached.CachedData; import net.spy.memcached.MemcachedClient; import net.spy.memcached.MemcachedNode; import net.spy.memcached.ObserveResponse; import net.spy.memcached.OperationTimeoutException; import net.spy.memcached.PersistTo; import net.spy.memcached.ReplicateTo; import net.spy.memcached.internal.OperationFuture; import net.spy.memcached.ops.GetlOperation; import net.spy.memcached.ops.ObserveOperation; import net.spy.memcached.ops.Operation; import net.spy.memcached.ops.OperationCallback; import net.spy.memcached.ops.OperationStatus; import net.spy.memcached.ops.StatsOperation; import net.spy.memcached.transcoders.Transcoder; import org.apache.http.HttpRequest; import org.apache.http.HttpVersion; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpEntityEnclosingRequest; import org.apache.http.message.BasicHttpRequest; /** * A client for Couchbase Server. * * This class acts as your main entry point while working with your Couchbase * cluster (if you want to work with TAP, see the TapClient instead). * * If you are working with Couchbase Server 2.0, remember to set the appropriate * view mode depending on your environment. */ public class CouchbaseClient extends MemcachedClient implements CouchbaseClientIF, Reconfigurable { private static final String MODE_PRODUCTION = "production"; private static final String MODE_DEVELOPMENT = "development"; private static final String DEV_PREFIX = "dev_"; private static final String PROD_PREFIX = ""; public static final String MODE_PREFIX; private static final String MODE_ERROR; private ViewConnection vconn = null; protected volatile boolean reconfiguring = false; private final CouchbaseConnectionFactory cbConnFactory; /** * Try to load the cbclient.properties file and check for the viewmode. * * If no viewmode (either through "cbclient.viewmode" or "viewmode") * property is set, the fallback is always "production". Possible options * are either "development" or "production". */ static { CouchbaseProperties.setPropertyFile("cbclient.properties"); String viewmode = CouchbaseProperties.getProperty("viewmode"); if (viewmode == null) { viewmode = CouchbaseProperties.getProperty("viewmode", true); } if (viewmode == null) { MODE_ERROR = "viewmode property isn't defined. Setting viewmode to" + " production mode"; MODE_PREFIX = PROD_PREFIX; } else if (viewmode.equals(MODE_PRODUCTION)) { MODE_ERROR = "viewmode set to production mode"; MODE_PREFIX = PROD_PREFIX; } else if (viewmode.equals(MODE_DEVELOPMENT)) { MODE_ERROR = "viewmode set to development mode"; MODE_PREFIX = DEV_PREFIX; } else { MODE_ERROR = "unknown value \"" + viewmode + "\" for property viewmode" + " Setting to production mode"; MODE_PREFIX = PROD_PREFIX; } } /** * Get a CouchbaseClient based on the initial server list provided. * * This constructor should be used if the bucket name is the same as the * username (which is normally the case). If your bucket does not have * a password (likely the "default" bucket), use an empty string instead. * * This method is only a convenience method so you don't have to create a * CouchbaseConnectionFactory for yourself. * * @param baseList the URI list of one or more servers from the cluster * @param bucketName the bucket name in the cluster you wish to use * @param pwd the password for the bucket * @throws IOException if connections could not be made * @throws ConfigurationException if the configuration provided by the server * has issues or is not compatible */ public CouchbaseClient(final List<URI> baseList, final String bucketName, final String pwd) throws IOException { this(new CouchbaseConnectionFactory(baseList, bucketName, pwd)); } /** * Get a CouchbaseClient based on the initial server list provided. * * Currently, Couchbase Server does not support a different username than the * bucket name. Therefore, this method ignores the given username for now * but will likely use it in the future. * * This constructor should be used if the bucket name is NOT the same as the * username. If your bucket does not have a password (likely the "default" * bucket), use an empty string instead. * * This method is only a convenience method so you don't have to create a * CouchbaseConnectionFactory for yourself. * * @param baseList the URI list of one or more servers from the cluster * @param bucketName the bucket name in the cluster you wish to use * @param user the username for the bucket * @param pwd the password for the bucket * @throws IOException if connections could not be made * @throws ConfigurationException if the configuration provided by the server * has issues or is not compatible */ public CouchbaseClient(final List<URI> baseList, final String bucketName, final String user, final String pwd) throws IOException { this(new CouchbaseConnectionFactory(baseList, bucketName, pwd)); } /** * Get a CouchbaseClient based on the settings from the given * CouchbaseConnectionFactory. * * If your bucket does not have a password (likely the "default" bucket), use * an empty string instead. * * The URI list provided here is only used during the initial connection to * the cluster. Afterwards, the actual cluster-map is synchronized from the * cluster and maintained internally by the client. This allows the client to * update the map as needed (when the cluster topology changes). * * Note that when specifying a ConnectionFactory you must specify a * BinaryConnectionFactory (which is the case if you use the * CouchbaseConnectionFactory). Also the ConnectionFactory's protocol and * locator values are always overwritten. The protocol will always be binary * and the locator will be chosen based on the bucket type you are connecting * to. * * The subscribe variable determines whether or not we will subscribe to * the configuration changes feed. This constructor should be used when * calling super from subclasses of CouchbaseClient since the subclass might * want to start the changes feed later. * * @param cf the ConnectionFactory to use to create connections * @throws IOException if connections could not be made * @throws ConfigurationException if the configuration provided by the server * has issues or is not compatible */ public CouchbaseClient(CouchbaseConnectionFactory cf) throws IOException { super(cf, AddrUtil.getAddresses(cf.getVBucketConfig().getServers())); cbConnFactory = cf; if (cf.getVBucketConfig().getConfigType() == ConfigType.COUCHBASE) { List<InetSocketAddress> addrs = AddrUtil.getAddressesFromURL(cf.getVBucketConfig().getCouchServers()); vconn = cf.createViewConnection(addrs); } getLogger().info(MODE_ERROR); cf.getConfigurationProvider().subscribe(cf.getBucketName(), this); } /** * This method is called when there is a topology change in the cluster. * * This method is intended for internal use only. */ public void reconfigure(Bucket bucket) { reconfiguring = true; if (bucket.isNotUpdating()) { getLogger().info("Bucket configuration is disconnected from cluster " + "configuration updates, attempting to reconnect."); CouchbaseConnectionFactory cbcf = (CouchbaseConnectionFactory) connFactory; cbcf.requestConfigReconnect(cbcf.getBucketName(), this); cbcf.checkConfigUpdate(); } try { cbConnFactory.getConfigurationProvider().updateBucket(cbConnFactory.getBucketName(), bucket); if (vconn != null) { vconn.reconfigure(bucket); } if (mconn instanceof CouchbaseConnection) { CouchbaseConnection cbConn = (CouchbaseConnection) mconn; cbConn.reconfigure(bucket); } else { CouchbaseMemcachedConnection cbMConn = (CouchbaseMemcachedConnection) mconn; cbMConn.reconfigure(bucket); } } catch (IllegalArgumentException ex) { getLogger().warn("Failed to reconfigure client, staying with " + "previous configuration.", ex); } finally { reconfiguring = false; } } /** * Gets access to a view contained in a design document from the cluster. * * The purpose of a view is take the structured data stored within the * Couchbase Server database as JSON documents, extract the fields and * information, and to produce an index of the selected information. * * The result is a view on the stored data. The view that is created * during this process allows you to iterate, select and query the * information in your database from the raw data objects that have * been stored. * * Note that since an HttpFuture is returned, the caller must also check to * see if the View is null. The HttpFuture does provide a getStatus() method * which can be used to check whether or not the view request has been * successful. * * @param designDocumentName the name of the design document. * @param viewName the name of the view to get. * @return a View object from the cluster. * @throws InterruptedException if the operation is interrupted while in * flight * @throws ExecutionException if an error occurs during execution */ public HttpFuture<View> asyncGetView(String designDocumentName, final String viewName) { CouchbaseConnectionFactory factory = (CouchbaseConnectionFactory) connFactory; designDocumentName = MODE_PREFIX + designDocumentName; String bucket = factory.getBucketName(); String uri = "/" + bucket + "/_design/" + designDocumentName; final CountDownLatch couchLatch = new CountDownLatch(1); final HttpFuture<View> crv = new HttpFuture<View>(couchLatch, factory.getViewTimeout()); final HttpRequest request = new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1); final HttpOperation op = new ViewFetcherOperationImpl(request, bucket, designDocumentName, viewName, new ViewFetcherOperation.ViewFetcherCallback() { private View view = null; @Override public void receivedStatus(OperationStatus status) { crv.set(view, status); } @Override public void complete() { couchLatch.countDown(); } @Override public void gotData(View v) { view = v; } }); crv.setOperation(op); addOp(op); assert crv != null : "Problem retrieving view"; return crv; } /** * Gets access to a spatial view contained in a design document from the * cluster. * * * Note that since an HttpFuture is returned, the caller must also check to * see if the View is null. The HttpFuture does provide a getStatus() method * which can be used to check whether or not the view request has been * successful. * * @param designDocumentName the name of the design document. * @param viewName the name of the spatial view to get. * @return a HttpFuture<SpatialView> object from the cluster. * @throws InterruptedException if the operation is interrupted while in * flight * @throws ExecutionException if an error occurs during execution */ public HttpFuture<SpatialView> asyncGetSpatialView(String designDocumentName, final String viewName) { CouchbaseConnectionFactory factory = (CouchbaseConnectionFactory) connFactory; designDocumentName = MODE_PREFIX + designDocumentName; String bucket = factory.getBucketName(); String uri = "/" + bucket + "/_design/" + designDocumentName; final CountDownLatch couchLatch = new CountDownLatch(1); final HttpFuture<SpatialView> crv = new HttpFuture<SpatialView>(couchLatch, factory.getViewTimeout()); final HttpRequest request = new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1); final HttpOperation op = new SpatialViewFetcherOperationImpl(request, bucket, designDocumentName, viewName, new SpatialViewFetcherOperation.ViewFetcherCallback() { private SpatialView view = null; @Override public void receivedStatus(OperationStatus status) { crv.set(view, status); } @Override public void complete() { couchLatch.countDown(); } @Override public void gotData(SpatialView v) { view = v; } }); crv.setOperation(op); addOp(op); assert crv != null : "Problem retrieving spatial view"; return crv; } /** * Gets a future with a design document from the cluster. * * If no design document was found, the enclosed DesignDocument inside * the future will be null. * * @param designDocumentName the name of the design document. * @return a future containing a DesignDocument from the cluster. */ public HttpFuture<DesignDocument> asyncGetDesignDocument(String designDocumentName) { designDocumentName = MODE_PREFIX + designDocumentName; String bucket = ((CouchbaseConnectionFactory) connFactory).getBucketName(); String uri = "/" + bucket + "/_design/" + designDocumentName; final CountDownLatch couchLatch = new CountDownLatch(1); final HttpFuture<DesignDocument> crv = new HttpFuture<DesignDocument>(couchLatch, 60000); final HttpRequest request = new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1); final HttpOperation op = new DesignDocFetcherOperationImpl(request, designDocumentName, new DesignDocFetcherOperation.DesignDocFetcherCallback() { private DesignDocument design = null; @Override public void receivedStatus(OperationStatus status) { crv.set(design, status); } @Override public void complete() { couchLatch.countDown(); } @Override public void gotData(DesignDocument d) { design = d; } }); crv.setOperation(op); addOp(op); return crv; } /** * Gets access to a view contained in a design document from the cluster. * * The purpose of a view is take the structured data stored within the * Couchbase Server database as JSON documents, extract the fields and * information, and to produce an index of the selected information. * * The result is a view on the stored data. The view that is created * during this process allows you to iterate, select and query the * information in your database from the raw data objects that have * been stored. * * @param designDocumentName the name of the design document. * @param viewName the name of the view to get. * @return a View object from the cluster. * @throws InvalidViewException if no design document or view was found. * @throws CancellationException if operation was canceled. */ public View getView(final String designDocumentName, final String viewName) { try { View view = asyncGetView(designDocumentName, viewName).get(); if (view == null) { throw new InvalidViewException( "Could not load view \"" + viewName + "\" for design doc \"" + designDocumentName + "\""); } return view; } catch (InterruptedException e) { throw new RuntimeException("Interrupted getting views", e); } catch (ExecutionException e) { if (e.getCause() instanceof CancellationException) { throw (CancellationException) e.getCause(); } else { throw new RuntimeException("Failed getting views", e); } } } /** * Gets access to a spatial view contained in a design document from the * cluster. * * Spatial views enable you to return recorded geometry data in the bucket * and perform queries which return information based on whether the recorded * geometries existing within a given two-dimensional range such as a * bounding box. * * @param designDocumentName the name of the design document. * @param viewName the name of the view to get. * @return a SpatialView object from the cluster. * @throws InvalidViewException if no design document or view was found. * @throws CancellationException if operation was canceled. */ public SpatialView getSpatialView(final String designDocumentName, final String viewName) { try { SpatialView view = asyncGetSpatialView(designDocumentName, viewName).get(); if (view == null) { throw new InvalidViewException("Could not load spatial view \"" + viewName + "\" for design doc \"" + designDocumentName + "\""); } return view; } catch (InterruptedException e) { throw new RuntimeException("Interrupted getting spatial view", e); } catch (ExecutionException e) { if (e.getCause() instanceof CancellationException) { throw (CancellationException) e.getCause(); } else { throw new RuntimeException("Failed getting views", e); } } } /** * Returns a representation of a design document stored in the cluster. * * @param designDocumentName the name of the design document. * @return a DesignDocument object from the cluster. * @throws InvalidViewException if no design document or view was found. * @throws CancellationException if operation was canceled. */ public DesignDocument getDesignDocument(final String designDocumentName) { try { DesignDocument design = asyncGetDesignDocument(designDocumentName).get(); if (design == null) { throw new InvalidViewException("Could not load design document \"" + designDocumentName + "\""); } return design; } catch (InterruptedException e) { throw new RuntimeException("Interrupted getting design document", e); } catch (ExecutionException e) { if (e.getCause() instanceof CancellationException) { throw (CancellationException) e.getCause(); } else { throw new RuntimeException("Failed getting design document", e); } } } /** * Store a design document in the cluster. * * @param doc the design document to store. * @return the result of the creation operation. * @throws CancellationException if operation was canceled. */ public Boolean createDesignDoc(final DesignDocument doc) { try { return asyncCreateDesignDoc(doc).get(); } catch (InterruptedException e) { throw new RuntimeException("Interrupted creating design document", e); } catch (ExecutionException e) { if (e.getCause() instanceof CancellationException) { throw (CancellationException) e.getCause(); } else { throw new RuntimeException("Failed creating design document", e); } } catch (UnsupportedEncodingException e) { throw new RuntimeException("Failed creating design document", e); } } /** * Store a design document in the cluster. * * @param name the name of the design document. * @param value the full design document definition as a string. * @return a future containing the result of the creation operation. */ public HttpFuture<Boolean> asyncCreateDesignDoc(String name, String value) throws UnsupportedEncodingException { getLogger().info("Creating Design Document:" + name); String bucket = ((CouchbaseConnectionFactory) connFactory).getBucketName(); final String uri = "/" + bucket + "/_design/" + MODE_PREFIX + name; final CountDownLatch couchLatch = new CountDownLatch(1); final HttpFuture<Boolean> crv = new HttpFuture<Boolean>(couchLatch, 60000); HttpRequest request = new BasicHttpEntityEnclosingRequest("PUT", uri, HttpVersion.HTTP_1_1); request.setHeader(new BasicHeader("Content-Type", "application/json")); StringEntity entity = new StringEntity(value); ((BasicHttpEntityEnclosingRequest) request).setEntity(entity); HttpOperationImpl op = new DesignDocOperationImpl(request, new OperationCallback() { @Override public void receivedStatus(OperationStatus status) { crv.set(status.getMessage().equals("Error Code: 201"), status); } @Override public void complete() { couchLatch.countDown(); } }); crv.setOperation(op); addOp(op); return crv; } /** * Store a design document in the cluster. * * @param doc the design document to store. * @return a future containing the result of the creation operation. */ public HttpFuture<Boolean> asyncCreateDesignDoc(final DesignDocument doc) throws UnsupportedEncodingException { return asyncCreateDesignDoc(doc.getName(), doc.toJson()); } /** * Delete a design document in the cluster. * * @param name the design document to delete. * @return the result of the deletion operation. * @throws CancellationException if operation was canceled. */ public Boolean deleteDesignDoc(final String name) { try { return asyncDeleteDesignDoc(name).get(); } catch (InterruptedException e) { throw new RuntimeException("Interrupted deleting design document", e); } catch (ExecutionException e) { if (e.getCause() instanceof CancellationException) { throw (CancellationException) e.getCause(); } else { throw new RuntimeException("Failed deleting design document", e); } } catch (UnsupportedEncodingException e) { throw new RuntimeException("Failed deleting design document", e); } } /** * Delete a design document in the cluster. * * @param name the design document to delete. * @return a future containing the result of the deletion operation. */ public HttpFuture<Boolean> asyncDeleteDesignDoc(final String name) throws UnsupportedEncodingException { getLogger().info("Deleting Design Document:" + name); String bucket = ((CouchbaseConnectionFactory) connFactory).getBucketName(); final String uri = "/" + bucket + "/_design/" + MODE_PREFIX + name; final CountDownLatch couchLatch = new CountDownLatch(1); final HttpFuture<Boolean> crv = new HttpFuture<Boolean>(couchLatch, 60000); HttpRequest request = new BasicHttpEntityEnclosingRequest("DELETE", uri, HttpVersion.HTTP_1_1); request.setHeader(new BasicHeader("Content-Type", "application/json")); HttpOperationImpl op = new DesignDocOperationImpl(request, new OperationCallback() { @Override public void receivedStatus(OperationStatus status) { crv.set(status.getMessage().equals("Error Code: 200"), status); } @Override public void complete() { couchLatch.countDown(); } }); crv.setOperation(op); addOp(op); return crv; } public HttpFuture<ViewResponse> asyncQuery(AbstractView view, Query query) { if (view.hasReduce() && !query.getArgs().containsKey("reduce")) { query.setReduce(true); } if (query.willReduce()) { return asyncQueryAndReduce(view, query); } else if (query.willIncludeDocs()) { return asyncQueryAndIncludeDocs(view, query); } else { return asyncQueryAndExcludeDocs(view, query); } } /** * Asynchronously queries a Couchbase view and returns the result. * The result can be accessed row-wise via an iterator. This * type of query will return the view result along with all of the documents * for each row in the query. * * @param view the view to run the query against. * @param query the type of query to run against the view. * @return a Future containing the results of the query. */ private HttpFuture<ViewResponse> asyncQueryAndIncludeDocs(AbstractView view, Query query) { assert view != null : "Who passed me a null view"; assert query != null : "who passed me a null query"; String viewUri = view.getURI(); String queryToRun = query.toString(); assert viewUri != null : "view URI seems to be null"; assert queryToRun != null : "query seems to be null"; String uri = viewUri + queryToRun; getLogger().info("lookin for:" + uri); final CountDownLatch couchLatch = new CountDownLatch(1); int timeout = ((CouchbaseConnectionFactory) connFactory).getViewTimeout(); final ViewFuture crv = new ViewFuture(couchLatch, timeout, view); final HttpRequest request = new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1); final HttpOperation op = new DocsOperationImpl(request, view, new ViewCallback() { private ViewResponse vr = null; @Override public void receivedStatus(OperationStatus status) { if (vr != null) { Collection<String> ids = new LinkedList<String>(); Iterator<ViewRow> itr = vr.iterator(); while (itr.hasNext()) { ids.add(itr.next().getId()); } crv.set(vr, asyncGetBulk(ids), status); } else { crv.set(null, null, status); } } @Override public void complete() { couchLatch.countDown(); } @Override public void gotData(ViewResponse response) { vr = response; } }); crv.setOperation(op); addOp(op); return crv; } /** * Asynchronously queries a Couchbase view and returns the result. * The result can be accessed row-wise via an iterator. This * type of query will return the view result but will not * get the documents associated with each row of the query. * * @param view the view to run the query against. * @param query the type of query to run against the view. * @return a Future containing the results of the query. */ private HttpFuture<ViewResponse> asyncQueryAndExcludeDocs(AbstractView view, Query query) { String uri = view.getURI() + query.toString(); final CountDownLatch couchLatch = new CountDownLatch(1); int timeout = ((CouchbaseConnectionFactory) connFactory).getViewTimeout(); final HttpFuture<ViewResponse> crv = new HttpFuture<ViewResponse>(couchLatch, timeout); final HttpRequest request = new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1); final HttpOperation op = new NoDocsOperationImpl(request, view, new ViewCallback() { private ViewResponse vr = null; @Override public void receivedStatus(OperationStatus status) { crv.set(vr, status); } @Override public void complete() { couchLatch.countDown(); } @Override public void gotData(ViewResponse response) { vr = response; } }); crv.setOperation(op); addOp(op); return crv; } /** * Asynchronously queries a Couchbase view and returns the result. * The result can be accessed row-wise via an iterator. * * @param view the view to run the query against. * @param query the type of query to run against the view. * @return a Future containing the results of the query. */ private HttpFuture<ViewResponse> asyncQueryAndReduce(final AbstractView view, final Query query) { if (!view.hasReduce()) { throw new RuntimeException("This view doesn't contain a reduce function"); } String uri = view.getURI() + query.toString(); final CountDownLatch couchLatch = new CountDownLatch(1); int timeout = ((CouchbaseConnectionFactory) connFactory).getViewTimeout(); final HttpFuture<ViewResponse> crv = new HttpFuture<ViewResponse>(couchLatch, timeout); final HttpRequest request = new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1); final HttpOperation op = new ReducedOperationImpl(request, view, new ViewCallback() { private ViewResponse vr = null; @Override public void receivedStatus(OperationStatus status) { crv.set(vr, status); } @Override public void complete() { couchLatch.countDown(); } @Override public void gotData(ViewResponse response) { vr = response; } }); crv.setOperation(op); addOp(op); return crv; } /** * Queries a Couchbase view and returns the result. * The result can be accessed row-wise via an iterator. * This type of query will return the view result along * with all of the documents for each row in * the query. * * @param view the view to run the query against. * @param query the type of query to run against the view. * @return a ViewResponseWithDocs containing the results of the query. * @throws CancellationException if operation was canceled. */ public ViewResponse query(AbstractView view, Query query) { try { return asyncQuery(view, query).get(); } catch (InterruptedException e) { throw new RuntimeException("Interrupted while accessing the view", e); } catch (ExecutionException e) { if (e.getCause() instanceof CancellationException) { throw (CancellationException) e.getCause(); } else { throw new RuntimeException("Failed to access the view", e); } } } /** * A paginated query allows the user to get the results of a large query in * small chunks allowing for better performance. The result allows you * to iterate through the results of the query and when you get to the end * of the current result set the client will automatically fetch the next set * of results. * * @param view the view to query against. * @param query the query for this request. * @param docsPerPage the amount of documents per page. * @return A Paginator (iterator) to use for reading the results of the query. */ public Paginator paginatedQuery(View view, Query query, int docsPerPage) { return new Paginator(this, view, query, docsPerPage); } /** * Adds an operation to the queue where it waits to be sent to Couchbase. This * function is for internal use only. */ public void addOp(final HttpOperation op) { if (vconn != null) { vconn.checkState(); vconn.addOp(op); } } /** * Gets and locks the given key asynchronously. By default the maximum allowed * timeout is 30 seconds. Timeouts greater than this will be set to 30 * seconds. * * @param key the key to fetch and lock * @param exp the amount of time the lock should be valid for in seconds. * @param tc the transcoder to serialize and unserialize value * @return a future that will hold the return value of the fetch * @throws IllegalStateException in the rare circumstance where queue is too * full to accept any more requests */ public <T> OperationFuture<CASValue<T>> asyncGetAndLock(final String key, int exp, final Transcoder<T> tc) { final CountDownLatch latch = new CountDownLatch(1); final OperationFuture<CASValue<T>> rv = new OperationFuture<CASValue<T>>(key, latch, operationTimeout); Operation op = opFact.getl(key, exp, new GetlOperation.Callback() { private CASValue<T> val = null; public void receivedStatus(OperationStatus status) { if (!status.isSuccess()) { val = new CASValue<T>(-1, null); } rv.set(val, status); } public void gotData(String k, int flags, long cas, byte[] data) { assert key.equals(k) : "Wrong key returned"; assert cas > 0 : "CAS was less than zero: " + cas; val = new CASValue<T>(cas, tc.decode(new CachedData(flags, data, tc.getMaxSize()))); } public void complete() { latch.countDown(); } }); rv.setOperation(op); mconn.enqueueOperation(key, op); return rv; } /** * Get and lock the given key asynchronously and decode with the default * transcoder. By default the maximum allowed timeout is 30 seconds. Timeouts * greater than this will be set to 30 seconds. * * @param key the key to fetch and lock * @param exp the amount of time the lock should be valid for in seconds. * @return a future that will hold the return value of the fetch * @throws IllegalStateException in the rare circumstance where queue is too * full to accept any more requests */ public OperationFuture<CASValue<Object>> asyncGetAndLock(final String key, int exp) { return asyncGetAndLock(key, exp, transcoder); } /** * Getl with a single key. By default the maximum allowed timeout is 30 * seconds. Timeouts greater than this will be set to 30 seconds. * * @param key the key to get and lock * @param exp the amount of time the lock should be valid for in seconds. * @param tc the transcoder to serialize and unserialize value * @return the result from the cache (null if there is none) * @throws OperationTimeoutException if the global operation timeout is * exceeded * @throws IllegalStateException in the rare circumstance where queue is too * full to accept any more requests * @throws CancellationException if operation was canceled */ public <T> CASValue<T> getAndLock(String key, int exp, Transcoder<T> tc) { try { return asyncGetAndLock(key, exp, tc).get(operationTimeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new RuntimeException("Interrupted waiting for value", e); } catch (ExecutionException e) { if (e.getCause() instanceof CancellationException) { throw (CancellationException) e.getCause(); } else { throw new RuntimeException("Exception waiting for value", e); } } catch (TimeoutException e) { throw new OperationTimeoutException("Timeout waiting for value", e); } } /** * Get and lock with a single key and decode using the default transcoder. By * default the maximum allowed timeout is 30 seconds. Timeouts greater than * this will be set to 30 seconds. * * @param key the key to get and lock * @param exp the amount of time the lock should be valid for in seconds. * @return the result from the cache (null if there is none) * @throws OperationTimeoutException if the global operation timeout is * exceeded * @throws IllegalStateException in the rare circumstance where queue is too * full to accept any more requests */ public CASValue<Object> getAndLock(String key, int exp) { return getAndLock(key, exp, transcoder); } /** * Unlock the given key asynchronously from the cache. * * @param key the key to unlock * @param casId the CAS identifier * @param tc the transcoder to serialize and unserialize value * @return whether or not the operation was performed * @throws IllegalStateException in the rare circumstance where queue is too * full to accept any more requests */ public <T> OperationFuture<Boolean> asyncUnlock(final String key, long casId, final Transcoder<T> tc) { final CountDownLatch latch = new CountDownLatch(1); final OperationFuture<Boolean> rv = new OperationFuture<Boolean>(key, latch, operationTimeout); Operation op = opFact.unlock(key, casId, new OperationCallback() { @Override public void receivedStatus(OperationStatus s) { rv.set(s.isSuccess(), s); } @Override public void complete() { latch.countDown(); } }); rv.setOperation(op); mconn.enqueueOperation(key, op); return rv; } /** * Unlock the given key asynchronously from the cache with the default * transcoder. * * @param key the key to unlock * @param casId the CAS identifier * @return whether or not the operation was performed * @throws IllegalStateException in the rare circumstance where queue is too * full to accept any more requests */ public OperationFuture<Boolean> asyncUnlock(final String key, long casId) { return asyncUnlock(key, casId, transcoder); } /** * Unlock the given key synchronously from the cache. * * @param key the key to unlock * @param casId the CAS identifier * @param tc the transcoder to serialize and unserialize value * @return whether or not the operation was performed * @throws IllegalStateException in the rare circumstance where queue is too * full to accept any more requests * @throws CancellationException if operation was canceled */ public <T> Boolean unlock(final String key, long casId, final Transcoder<T> tc) { try { return asyncUnlock(key, casId, tc).get(operationTimeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new RuntimeException("Interrupted waiting for value", e); } catch (ExecutionException e) { if (e.getCause() instanceof CancellationException) { throw (CancellationException) e.getCause(); } else { throw new RuntimeException("Exception waiting for value", e); } } catch (TimeoutException e) { throw new OperationTimeoutException("Timeout waiting for value", e); } } /** * Unlock the given key synchronously from the cache with the default * transcoder. * * @param key the key to unlock * @param casId the CAS identifier * @return whether or not the operation was performed * @throws IllegalStateException in the rare circumstance where queue is too * full to accept any more requests */ public Boolean unlock(final String key, long casId) { return unlock(key, casId, transcoder); } /** * Delete a value with durability options. * * The durability options here operate similarly to those documented in * the set method. * * @param key the key to set * @param req the Persistence to Master value * @param rep the Persistence to Replicas * @return whether or not the operation was performed */ public OperationFuture<Boolean> delete(String key, PersistTo req, ReplicateTo rep) { OperationFuture<Boolean> deleteOp = delete(key); boolean deleteStatus = false; try { deleteStatus = deleteOp.get(); } catch (InterruptedException e) { deleteOp.set(false, new OperationStatus(false, "Delete get timed out")); } catch (ExecutionException e) { if (e.getCause() instanceof CancellationException) { deleteOp.set(false, new OperationStatus(false, "Delete get " + "cancellation exception ")); } else { deleteOp.set(false, new OperationStatus(false, "Delete get " + "execution exception ")); } } if (!deleteStatus) { return deleteOp; } try { observePoll(key, deleteOp.getCas(), req, rep, true); deleteOp.set(true, deleteOp.getStatus()); } catch (ObservedException e) { deleteOp.set(false, new OperationStatus(false, e.getMessage())); } catch (ObservedTimeoutException e) { deleteOp.set(false, new OperationStatus(false, e.getMessage())); } catch (ObservedModifiedException e) { deleteOp.set(false, new OperationStatus(false, e.getMessage())); } return deleteOp; } /** * Delete a value with durability options for persistence. * * @param key the key to set * @param req the persistence option requested * @return whether or not the operation was performed * */ public OperationFuture<Boolean> delete(String key, PersistTo req) { return delete(key, req, ReplicateTo.ZERO); } /** * Delete a value with durability options for replication. * * @param key the key to set * @param req the replication option requested * @return whether or not the operation was performed * */ public OperationFuture<Boolean> delete(String key, ReplicateTo req) { return delete(key, PersistTo.ZERO, req); } /** * Set a value with durability options. * * To make sure that a value is stored the way you want it to in the * cluster, you can use the PersistTo and ReplicateTo arguments. The * operation will block until the desired state is satisfied or * otherwise an exception is raised. There are many reasons why this could * happen, the more frequent ones are as follows: * * - The given replication settings are invalid. * - The operation could not be completed within the timeout. * - Something goes wrong and a cluster failover is triggered. * * The client does not attempt to guarantee the given durability * constraints, it just reports whether the operation has been completed * or not. If it is not achieved, it is the responsibility of the * application code using this API to re-retrieve the items to verify * desired state, redo the operation or both. * * Note that even if an exception during the observation is raised, * this doesn't mean that the operation has failed. A normal set() * operation is initiated and after the OperationFuture has returned, * the key itself is observed with the given durability options (watch * out for Observed*Exceptions) in this case. * * @param key the key to store. * @param exp the expiry value to use. * @param value the value of the key. * @param req the amount of nodes the item should be persisted to before * returning. * @param rep the amount of nodes the item should be replicated to before * returning. * @return the future result of the set operation. */ public OperationFuture<Boolean> set(String key, int exp, Object value, PersistTo req, ReplicateTo rep) { OperationFuture<Boolean> setOp = set(key, exp, value); boolean setStatus = false; try { setStatus = setOp.get(); } catch (InterruptedException e) { setOp.set(false, new OperationStatus(false, "Set get timed out")); } catch (ExecutionException e) { if (e.getCause() instanceof CancellationException) { setOp.set(false, new OperationStatus(false, "Set get " + "cancellation exception ")); } else { setOp.set(false, new OperationStatus(false, "Set get " + "execution exception ")); } } if (!setStatus) { return setOp; } try { observePoll(key, setOp.getCas(), req, rep, false); setOp.set(true, setOp.getStatus()); } catch (ObservedException e) { setOp.set(false, new OperationStatus(false, e.getMessage())); } catch (ObservedTimeoutException e) { setOp.set(false, new OperationStatus(false, e.getMessage())); } catch (ObservedModifiedException e) { setOp.set(false, new OperationStatus(false, e.getMessage())); } return setOp; } /** * Set a value with durability options. * * This is a shorthand method so that you only need to provide a * PersistTo value if you don't care if the value is already replicated. * A PersistTo.TWO durability setting implies a replication to at least * one node. * * For more information on how the durability options work, see the docblock * for the set() operation with both PersistTo and ReplicateTo settings. * * @param key the key to store. * @param exp the expiry value to use. * @param value the value of the key. * @param req the amount of nodes the item should be persisted to before * returning. * @return the future result of the set operation. */ public OperationFuture<Boolean> set(String key, int exp, Object value, PersistTo req) { return set(key, exp, value, req, ReplicateTo.ZERO); } /** * Set a value with durability options. * * This method allows you to express durability at the replication level * only and is the functional equivalent of PersistTo.ZERO. * * A common use case for this would be to achieve good insert-performance * and at the same time making sure that the data is at least replicated * to the given amount of nodes to provide a better level of data safety. * * For more information on how the durability options work, see the docblock * for the set() operation with both PersistTo and ReplicateTo settings. * * @param key the key to store. * @param exp the expiry value to use. * @param value the value of the key. * @param rep the amount of nodes the item should be replicated to before * returning. * @return the future result of the set operation. */ public OperationFuture<Boolean> set(String key, int exp, Object value, ReplicateTo rep) { return set(key, exp, value, PersistTo.ZERO, rep); } /** * Add a value with durability options. * * To make sure that a value is stored the way you want it to in the * cluster, you can use the PersistTo and ReplicateTo arguments. The * operation will block until the desired state is satisfied or * otherwise an exception is raised. There are many reasons why this could * happen, the more frequent ones are as follows: * * - The given replication settings are invalid. * - The operation could not be completed within the timeout. * - Something goes wrong and a cluster failover is triggered. * * The client does not attempt to guarantee the given durability * constraints, it just reports whether the operation has been completed * or not. If it is not achieved, it is the responsibility of the * application code using this API to re-retrieve the items to verify * desired state, redo the operation or both. * * Note that even if an exception during the observation is raised, * this doesn't mean that the operation has failed. A normal add() * operation is initiated and after the OperationFuture has returned, * the key itself is observed with the given durability options (watch * out for Observed*Exceptions) in this case. * * @param key the key to store. * @param exp the expiry value to use. * @param value the value of the key. * @param req the amount of nodes the item should be persisted to before * returning. * @param rep the amount of nodes the item should be replicated to before * returning. * @return the future result of the add operation. */ public OperationFuture<Boolean> add(String key, int exp, Object value, PersistTo req, ReplicateTo rep) { OperationFuture<Boolean> addOp = add(key, exp, value); boolean addStatus = false; try { addStatus = addOp.get(); } catch (InterruptedException e) { addOp.set(false, new OperationStatus(false, "Add get timed out")); } catch (ExecutionException e) { if (e.getCause() instanceof CancellationException) { addOp.set(false, new OperationStatus(false, "Add get " + "cancellation exception ")); } else { addOp.set(false, new OperationStatus(false, "Add get " + "execution exception ")); } } if (!addStatus) { return addOp; } try { observePoll(key, addOp.getCas(), req, rep, false); addOp.set(true, addOp.getStatus()); } catch (ObservedException e) { addOp.set(false, new OperationStatus(false, e.getMessage())); } catch (ObservedTimeoutException e) { addOp.set(false, new OperationStatus(false, e.getMessage())); } catch (ObservedModifiedException e) { addOp.set(false, new OperationStatus(false, e.getMessage())); } return addOp; } /** * Add a value with durability options. * * This is a shorthand method so that you only need to provide a * PersistTo value if you don't care if the value is already replicated. * A PersistTo.TWO durability setting implies a replication to at least * one node. * * For more information on how the durability options work, see the docblock * for the add() operation with both PersistTo and ReplicateTo settings. * * @param key the key to store. * @param exp the expiry value to use. * @param value the value of the key. * @param req the amount of nodes the item should be persisted to before * returning. * @return the future result of the add operation. */ public OperationFuture<Boolean> add(String key, int exp, Object value, PersistTo req) { return add(key, exp, value, req, ReplicateTo.ZERO); } /** * Add a value with durability options. * * This method allows you to express durability at the replication level * only and is the functional equivalent of PersistTo.ZERO. * * A common use case for this would be to achieve good insert-performance * and at the same time making sure that the data is at least replicated * to the given amount of nodes to provide a better level of data safety. * * For more information on how the durability options work, see the docblock * for the add() operation with both PersistTo and ReplicateTo settings. * * @param key the key to store. * @param exp the expiry value to use. * @param value the value of the key. * @param rep the amount of nodes the item should be replicated to before * returning. * @return the future result of the add operation. */ public OperationFuture<Boolean> add(String key, int exp, Object value, ReplicateTo rep) { return add(key, exp, value, PersistTo.ZERO, rep); } /** * Replace a value with durability options. * * To make sure that a value is stored the way you want it to in the * cluster, you can use the PersistTo and ReplicateTo arguments. The * operation will block until the desired state is satisfied or * otherwise an exception is raised. There are many reasons why this could * happen, the more frequent ones are as follows: * * - The given replication settings are invalid. * - The operation could not be completed within the timeout. * - Something goes wrong and a cluster failover is triggered. * * The client does not attempt to guarantee the given durability * constraints, it just reports whether the operation has been completed * or not. If it is not achieved, it is the responsibility of the * application code using this API to re-retrieve the items to verify * desired state, redo the operation or both. * * Note that even if an exception during the observation is raised, * this doesn't mean that the operation has failed. A normal replace() * operation is initiated and after the OperationFuture has returned, * the key itself is observed with the given durability options (watch * out for Observed*Exceptions) in this case. * * @param key the key to store. * @param exp the expiry value to use. * @param value the value of the key. * @param req the amount of nodes the item should be persisted to before * returning. * @param rep the amount of nodes the item should be replicated to before * returning. * @return the future result of the replace operation. */ public OperationFuture<Boolean> replace(String key, int exp, Object value, PersistTo req, ReplicateTo rep) { OperationFuture<Boolean> replaceOp = replace(key, exp, value); boolean replaceStatus = false; try { replaceStatus = replaceOp.get(); } catch (InterruptedException e) { replaceOp.set(false, new OperationStatus(false, "Replace get timed out")); } catch (ExecutionException e) { if (e.getCause() instanceof CancellationException) { replaceOp.set(false, new OperationStatus(false, "Replace get " + "cancellation exception ")); } else { replaceOp.set(false, new OperationStatus(false, "Replace get " + "execution exception ")); } } if (!replaceStatus) { return replaceOp; } try { observePoll(key, replaceOp.getCas(), req, rep, false); replaceOp.set(true, replaceOp.getStatus()); } catch (ObservedException e) { replaceOp.set(false, new OperationStatus(false, e.getMessage())); } catch (ObservedTimeoutException e) { replaceOp.set(false, new OperationStatus(false, e.getMessage())); } catch (ObservedModifiedException e) { replaceOp.set(false, new OperationStatus(false, e.getMessage())); } return replaceOp; } /** * Replace a value with durability options. * * This is a shorthand method so that you only need to provide a * PersistTo value if you don't care if the value is already replicated. * A PersistTo.TWO durability setting implies a replication to at least * one node. * * For more information on how the durability options work, see the docblock * for the replace() operation with both PersistTo and ReplicateTo settings. * * @param key the key to store. * @param exp the expiry value to use. * @param value the value of the key. * @param req the amount of nodes the item should be persisted to before * returning. * @return the future result of the replace operation. */ public OperationFuture<Boolean> replace(String key, int exp, Object value, PersistTo req) { return replace(key, exp, value, req, ReplicateTo.ZERO); } /** * Replace a value with durability options. * * This method allows you to express durability at the replication level * only and is the functional equivalent of PersistTo.ZERO. * * A common use case for this would be to achieve good insert-performance * and at the same time making sure that the data is at least replicated * to the given amount of nodes to provide a better level of data safety. * * For more information on how the durability options work, see the docblock * for the replace() operation with both PersistTo and ReplicateTo settings. * * @param key the key to store. * @param exp the expiry value to use. * @param value the value of the key. * @param rep the amount of nodes the item should be replicated to before * returning. * @return the future result of the replace operation. */ public OperationFuture<Boolean> replace(String key, int exp, Object value, ReplicateTo rep) { return replace(key, exp, value, PersistTo.ZERO, rep); } /** * Set a value with a CAS and durability options. * * To make sure that a value is stored the way you want it to in the * cluster, you can use the PersistTo and ReplicateTo arguments. The * operation will block until the desired state is satisfied or * otherwise an exception is raised. There are many reasons why this could * happen, the more frequent ones are as follows: * * - The given replication settings are invalid. * - The operation could not be completed within the timeout. * - Something goes wrong and a cluster failover is triggered. * * The client does not attempt to guarantee the given durability * constraints, it just reports whether the operation has been completed * or not. If it is not achieved, it is the responsibility of the * application code using this API to re-retrieve the items to verify * desired state, redo the operation or both. * * Note that even if an exception during the observation is raised, * this doesn't mean that the operation has failed. A normal asyncCAS() * operation is initiated and after the OperationFuture has returned, * the key itself is observed with the given durability options (watch * out for Observed*Exceptions) in this case. * * @param key the key to store. * @param cas the CAS value to use. * @param value the value of the key. * @param req the amount of nodes the item should be persisted to before * returning. * @param rep the amount of nodes the item should be replicated to before * returning. * @return the future result of the CAS operation. */ public CASResponse cas(String key, long cas, Object value, PersistTo req, ReplicateTo rep) { OperationFuture<CASResponse> casOp = asyncCAS(key, cas, value); CASResponse casr = null; try { casr = casOp.get(); } catch (InterruptedException e) { casr = CASResponse.EXISTS; } catch (ExecutionException e) { casr = CASResponse.EXISTS; } if (casr != CASResponse.OK) { return casr; } try { observePoll(key, casOp.getCas(), req, rep, false); } catch (ObservedException e) { casr = CASResponse.OBSERVE_ERROR_IN_ARGS; } catch (ObservedTimeoutException e) { casr = CASResponse.OBSERVE_TIMEOUT; } catch (ObservedModifiedException e) { casr = CASResponse.OBSERVE_MODIFIED; } return casr; } /** * Set a value with a CAS and durability options. * * This is a shorthand method so that you only need to provide a * PersistTo value if you don't care if the value is already replicated. * A PersistTo.TWO durability setting implies a replication to at least * one node. * * For more information on how the durability options work, see the docblock * for the cas() operation with both PersistTo and ReplicateTo settings. * * @param key the key to store. * @param cas the CAS value to use. * @param value the value of the key. * @param req the amount of nodes the item should be persisted to before * returning. * @return the future result of the CAS operation. */ public CASResponse cas(String key, long cas, Object value, PersistTo req) { return cas(key, cas, value, req, ReplicateTo.ZERO); } /** * Set a value with a CAS and durability options. * * This method allows you to express durability at the replication level * only and is the functional equivalent of PersistTo.ZERO. * * A common use case for this would be to achieve good insert-performance * and at the same time making sure that the data is at least replicated * to the given amount of nodes to provide a better level of data safety. * * For more information on how the durability options work, see the docblock * for the cas() operation with both PersistTo and ReplicateTo settings. * * @param key the key to store. * @param cas the CAS value to use. * @param value the value of the key. * @param rep the amount of nodes the item should be replicated to before * returning. * @return the future result of the CAS operation. */ public CASResponse cas(String key, long cas, Object value, ReplicateTo rep) { return cas(key, cas, value, PersistTo.ZERO, rep); } /** * Observe a key with a associated CAS. * * This method allows you to check immediately on the state of a given * key/CAS combination. It is normally used by higher-level methods when * used in combination with durability constraints (ReplicateTo, * PersistTo), but can also be used separately. * * @param key the key to observe. * @param cas the CAS of the key (0 will ignore it). * @return ObserveReponse the Response on master and replicas. * @throws IllegalStateException in the rare circumstance where queue is too * full to accept any more requests. */ public Map<MemcachedNode, ObserveResponse> observe(final String key, final long cas) { Config cfg = ((CouchbaseConnectionFactory) connFactory).getVBucketConfig(); VBucketNodeLocator locator = ((VBucketNodeLocator) ((CouchbaseConnection) mconn).getLocator()); final int vb = locator.getVBucketIndex(key); List<MemcachedNode> bcastNodes = new ArrayList<MemcachedNode>(); bcastNodes.add(locator.getServerByIndex(cfg.getMaster(vb))); for (int i = 1; i <= cfg.getReplicasCount(); i++) { int replica = cfg.getReplica(vb, i - 1); if (replica >= 0) { bcastNodes.add(locator.getServerByIndex(replica)); } } final Map<MemcachedNode, ObserveResponse> response = new HashMap<MemcachedNode, ObserveResponse>(); CountDownLatch blatch = broadcastOp(new BroadcastOpFactory() { public Operation newOp(final MemcachedNode n, final CountDownLatch latch) { return opFact.observe(key, cas, vb, new ObserveOperation.Callback() { public void receivedStatus(OperationStatus s) { } public void gotData(String key, long retCas, MemcachedNode node, ObserveResponse or) { if (cas == retCas) { response.put(node, or); } else /* cas doesn't match */ { if (or == ObserveResponse.NOT_FOUND_PERSISTED) { // CAS doesn't matter in this case. tell the caller it's gone response.put(node, or); } else { response.put(node, ObserveResponse.MODIFIED); } } } public void complete() { latch.countDown(); } }); } }, bcastNodes); try { blatch.await(operationTimeout, TimeUnit.MILLISECONDS); return response; } catch (InterruptedException e) { throw new RuntimeException("Interrupted waiting for value", e); } } /** * Gets the number of vBuckets that are contained in the cluster. This * function is for internal use only and should rarely be since there * are few use cases in which it is necessary. */ @Override public int getNumVBuckets() { return ((CouchbaseConnectionFactory) connFactory).getVBucketConfig().getVbucketsCount(); } @Override public boolean shutdown(long timeout, TimeUnit unit) { boolean shutdownResult = false; try { shutdownResult = super.shutdown(timeout, unit); CouchbaseConnectionFactory cf = (CouchbaseConnectionFactory) connFactory; cf.getConfigurationProvider().shutdown(); if (vconn != null) { vconn.shutdown(); } } catch (IOException ex) { Logger.getLogger(CouchbaseClient.class.getName()).log(Level.SEVERE, "Unexpected IOException in shutdown", ex); throw new RuntimeException(null, ex); } return shutdownResult; } private void checkObserveReplica(String key, int numPersist, int numReplica) { Config cfg = ((CouchbaseConnectionFactory) connFactory).getVBucketConfig(); VBucketNodeLocator locator = ((VBucketNodeLocator) ((CouchbaseConnection) mconn).getLocator()); if (numReplica > 0) { int vBucketIndex = locator.getVBucketIndex(key); int currentReplicaNum = cfg.getReplica(vBucketIndex, numReplica - 1); if (currentReplicaNum < 0) { throw new ObservedException("Currently, there is no replica available " + "for the given replica index. This can be the case because of a " + "failed over node which has not yet been rebalanced."); } } int replicaCount = Math.min(locator.getAll().size() - 1, cfg.getReplicasCount()); if (numReplica > replicaCount) { throw new ObservedException("Requested replication to " + numReplica + " node(s), but only " + replicaCount + " are avaliable"); } else if (numPersist > replicaCount + 1) { throw new ObservedException("Requested persistence to " + numPersist + " node(s), but only " + (replicaCount + 1) + " are available."); } } /** * Poll and observe a key with the given CAS and persist settings. * * Based on the given persistence and replication settings, it observes the * key and raises an exception if a timeout has been reached. This method is * normally utilized through higher-level methods but can also be used * directly. * * If persist is null, it will default to PersistTo.ZERO and if replicate is * null, it will default to ReplicateTo.ZERO. This is the default behavior * and is the same as not observing at all. * * @param key the key to observe. * @param cas the CAS value for the key. * @param persist the persistence settings. * @param replicate the replication settings. * @param isDelete if the key is to be deleted. */ public void observePoll(String key, long cas, PersistTo persist, ReplicateTo replicate, boolean isDelete) { if (persist == null) { persist = PersistTo.ZERO; } if (replicate == null) { replicate = ReplicateTo.ZERO; } ((CouchbaseConnectionFactory) connFactory).checkConfigAgainstPersistence(persist, replicate); int persistReplica = persist.getValue() > 0 ? persist.getValue() - 1 : 0; int replicateTo = replicate.getValue(); int obsPolls = 0; int obsPollMax = cbConnFactory.getObsPollMax(); long obsPollInterval = cbConnFactory.getObsPollInterval(); boolean persistMaster = persist.getValue() > 0; Config cfg = ((CouchbaseConnectionFactory) connFactory).getVBucketConfig(); VBucketNodeLocator locator = ((VBucketNodeLocator) ((CouchbaseConnection) mconn).getLocator()); checkObserveReplica(key, persistReplica, replicateTo); int replicaPersistedTo = 0; int replicatedTo = 0; boolean persistedMaster = false; while (replicateTo > replicatedTo || persistReplica - 1 > replicaPersistedTo || (!persistedMaster && persistMaster)) { checkObserveReplica(key, persistReplica, replicateTo); if (++obsPolls >= obsPollMax) { long timeTried = obsPollMax * obsPollInterval; TimeUnit tu = TimeUnit.MILLISECONDS; throw new ObservedTimeoutException("Observe Timeout - Polled" + " Unsuccessfully for at least " + tu.toSeconds(timeTried) + " seconds."); } Map<MemcachedNode, ObserveResponse> response = observe(key, cas); int vb = locator.getVBucketIndex(key); MemcachedNode master = locator.getServerByIndex(cfg.getMaster(vb)); replicaPersistedTo = 0; replicatedTo = 0; persistedMaster = false; for (Entry<MemcachedNode, ObserveResponse> r : response.entrySet()) { boolean isMaster = r.getKey() == master ? true : false; if (isMaster && r.getValue() == ObserveResponse.MODIFIED) { throw new ObservedModifiedException("Key was modified"); } if (!isDelete) { if (!isMaster && r.getValue() == ObserveResponse.FOUND_NOT_PERSISTED) { replicatedTo++; } if (r.getValue() == ObserveResponse.FOUND_PERSISTED) { if (isMaster) { persistedMaster = true; } else { replicatedTo++; replicaPersistedTo++; } } } else { if (r.getValue() == ObserveResponse.NOT_FOUND_NOT_PERSISTED) { replicatedTo++; } if (r.getValue() == ObserveResponse.NOT_FOUND_PERSISTED) { replicatedTo++; replicaPersistedTo++; if (isMaster) { persistedMaster = true; } else { replicaPersistedTo++; } } } } try { Thread.sleep(obsPollInterval); } catch (InterruptedException e) { getLogger().error("Interrupted while in observe loop.", e); throw new ObservedException("Observe was Interrupted "); } } } public OperationFuture<Map<String, String>> getKeyStats(String key) { final CountDownLatch latch = new CountDownLatch(1); final OperationFuture<Map<String, String>> rv = new OperationFuture<Map<String, String>>(key, latch, operationTimeout); Operation op = opFact.keyStats(key, new StatsOperation.Callback() { private Map<String, String> stats = new HashMap<String, String>(); public void gotStat(String name, String val) { stats.put(name, val); } public void receivedStatus(OperationStatus status) { rv.set(stats, status); } public void complete() { latch.countDown(); } }); rv.setOperation(op); mconn.enqueueOperation(key, op); return rv; } /** * Flush all data from the bucket immediately. * * Note that if the bucket is of type memcached the flush will be nearly * instantaneous. Running a flush() on a Couchbase bucket can take quite * a while, depending on the amount of data and the load on the system. * * @return a OperationFuture indicating the result of the flush. */ @Override public OperationFuture<Boolean> flush() { return flush(-1); } /** * Flush all caches from all servers with a delay of application. * * @param delay the period of time to delay, in seconds * @return whether or not the operation was accepted */ @Override public OperationFuture<Boolean> flush(final int delay) { final CountDownLatch latch = new CountDownLatch(1); final FlushRunner flushRunner = new FlushRunner(latch); final OperationFuture<Boolean> rv = new OperationFuture<Boolean>("", latch, operationTimeout) { private CouchbaseConnectionFactory factory = (CouchbaseConnectionFactory) connFactory; @Override public boolean cancel() { throw new UnsupportedOperationException("Flush cannot be" + " canceled"); } @Override public boolean isDone() { return flushRunner.status(); } @Override public Boolean get(long duration, TimeUnit units) throws InterruptedException, TimeoutException, ExecutionException { if (!latch.await(duration, units)) { throw new TimeoutException("Flush not completed within" + " timeout."); } return flushRunner.status(); } @Override public Boolean get() throws InterruptedException, ExecutionException { try { return get(factory.getViewTimeout(), TimeUnit.MILLISECONDS); } catch (TimeoutException e) { throw new RuntimeException("Timed out waiting for operation", e); } } @Override public Long getCas() { throw new UnsupportedOperationException("Flush has no CAS" + " value."); } @Override public String getKey() { throw new UnsupportedOperationException("Flush has no" + " associated key."); } @Override public OperationStatus getStatus() { throw new UnsupportedOperationException("Flush has no" + " OperationStatus."); } @Override public boolean isCancelled() { throw new UnsupportedOperationException("Flush cannot be" + " canceled."); } }; Thread flusher = new Thread(flushRunner, "Temporary Flusher"); flusher.setDaemon(true); flusher.start(); return rv; } /** * Flush the current bucket. */ private boolean flushBucket() { FlushResponse res = cbConnFactory.getClusterManager().flushBucket(cbConnFactory.getBucketName()); return res.equals(FlushResponse.OK); } // This is a bit of a hack since we don't have async http on this // particular interface, but it conforms to the specification private class FlushRunner implements Runnable { private final CountDownLatch flatch; private Boolean flushStatus = false; public FlushRunner(CountDownLatch latch) { flatch = latch; } public void run() { flushStatus = flushBucket(); flatch.countDown(); } private boolean status() { return flushStatus.booleanValue(); } } }