com.couchbase.client.java.bucket.DefaultAsyncBucketManager Maven / Gradle / Ivy
/**
* Copyright (C) 2014 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.java.bucket;
import static com.couchbase.client.java.query.Select.select;
import static com.couchbase.client.java.query.dsl.Expression.i;
import static com.couchbase.client.java.query.dsl.Expression.s;
import static com.couchbase.client.java.query.dsl.Expression.x;
import static com.couchbase.client.java.util.retry.RetryBuilder.anyOf;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.couchbase.client.core.ClusterFacade;
import com.couchbase.client.core.CouchbaseException;
import com.couchbase.client.core.logging.CouchbaseLogger;
import com.couchbase.client.core.logging.CouchbaseLoggerFactory;
import com.couchbase.client.core.message.config.BucketConfigRequest;
import com.couchbase.client.core.message.config.BucketConfigResponse;
import com.couchbase.client.core.message.config.GetDesignDocumentsRequest;
import com.couchbase.client.core.message.config.GetDesignDocumentsResponse;
import com.couchbase.client.core.message.view.GetDesignDocumentRequest;
import com.couchbase.client.core.message.view.GetDesignDocumentResponse;
import com.couchbase.client.core.message.view.RemoveDesignDocumentRequest;
import com.couchbase.client.core.message.view.RemoveDesignDocumentResponse;
import com.couchbase.client.core.message.view.UpsertDesignDocumentRequest;
import com.couchbase.client.core.message.view.UpsertDesignDocumentResponse;
import com.couchbase.client.core.time.Delay;
import com.couchbase.client.deps.io.netty.util.CharsetUtil;
import com.couchbase.client.java.CouchbaseAsyncBucket;
import com.couchbase.client.java.document.json.JsonArray;
import com.couchbase.client.java.document.json.JsonObject;
import com.couchbase.client.java.error.CannotRetryException;
import com.couchbase.client.java.error.DesignDocumentAlreadyExistsException;
import com.couchbase.client.java.error.DesignDocumentException;
import com.couchbase.client.java.error.IndexAlreadyExistsException;
import com.couchbase.client.java.error.IndexDoesNotExistException;
import com.couchbase.client.java.error.IndexesNotReadyException;
import com.couchbase.client.java.error.TranscodingException;
import com.couchbase.client.java.query.AsyncN1qlQueryResult;
import com.couchbase.client.java.query.AsyncN1qlQueryRow;
import com.couchbase.client.java.query.Index;
import com.couchbase.client.java.query.N1qlParams;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.query.Statement;
import com.couchbase.client.java.query.consistency.ScanConsistency;
import com.couchbase.client.java.query.core.N1qlQueryExecutor;
import com.couchbase.client.java.query.dsl.Expression;
import com.couchbase.client.java.query.dsl.Sort;
import com.couchbase.client.java.query.dsl.path.index.IndexType;
import com.couchbase.client.java.query.dsl.path.index.UsingWithPath;
import com.couchbase.client.java.query.util.IndexInfo;
import com.couchbase.client.java.view.DesignDocument;
import rx.Notification;
import rx.Observable;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.functions.Func1;
/**
* Default implementation of a {@link AsyncBucketManager}.
*
* @author Michael Nitschinger
* @since 2.0
*/
public class DefaultAsyncBucketManager implements AsyncBucketManager {
/** the name of the logger dedicated to index watching **/
public static final String INDEX_WATCH_LOG_NAME = "indexWatch";
//index watching related constants
private static final CouchbaseLogger INDEX_WATCH_LOG = CouchbaseLoggerFactory.getInstance(INDEX_WATCH_LOG_NAME);
//big enough as to only really consider the timeout, but without risk of overflowing
private static final int INDEX_WATCH_MAX_ATTEMPTS = Integer.MAX_VALUE - 5;
private static final Delay INDEX_WATCH_DELAY = Delay.linear(TimeUnit.MILLISECONDS, 1000, 50, 500);
private final ClusterFacade core;
private final String bucket;
private final String password;
private final N1qlQueryExecutor queryExecutor;
DefaultAsyncBucketManager(String bucket, String password, ClusterFacade core) {
this.bucket = bucket;
this.password = password;
this.core = core;
this.queryExecutor = new N1qlQueryExecutor(core, bucket, password);
}
public static DefaultAsyncBucketManager create(String bucket, String password, ClusterFacade core) {
return new DefaultAsyncBucketManager(bucket, password, core);
}
@Override
public Observable info() {
return Observable.defer(new Func0>() {
@Override
public Observable call() {
return core.send(new BucketConfigRequest("/pools/default/buckets/", null, bucket, password));
}
}).map(new Func1() {
@Override
public BucketInfo call(BucketConfigResponse response) {
try {
return DefaultBucketInfo.create(
CouchbaseAsyncBucket.JSON_OBJECT_TRANSCODER.stringToJsonObject(response.config())
);
} catch (Exception ex) {
throw new TranscodingException("Could not decode bucket info.", ex);
}
}
});
}
@Override
public Observable flush() {
return BucketFlusher.flush(core, bucket, password);
}
@Override
public Observable getDesignDocuments() {
return getDesignDocuments(false);
}
@Override
public Observable getDesignDocuments(final boolean development) {
return Observable.defer(new Func0>() {
@Override
public Observable call() {
return core.send(new GetDesignDocumentsRequest(bucket, password));
}
}).flatMap(new Func1>() {
@Override
public Observable call(GetDesignDocumentsResponse response) {
JsonObject converted;
try {
converted = CouchbaseAsyncBucket.JSON_OBJECT_TRANSCODER.stringToJsonObject(response.content());
} catch (Exception e) {
throw new TranscodingException("Could not decode design document.", e);
}
JsonArray rows = converted.getArray("rows");
List docs = new ArrayList();
for (Object doc : rows) {
JsonObject docObj = ((JsonObject) doc).getObject("doc");
String id = docObj.getObject("meta").getString("id");
String[] idSplit = id.split("/");
String fullName = idSplit[1];
boolean isDev = fullName.startsWith("dev_");
if (isDev != development) {
continue;
}
String name = fullName.replace("dev_", "");
docs.add(DesignDocument.from(name, docObj.getObject("json")));
}
return Observable.from(docs);
}
});
}
@Override
public Observable getDesignDocument(String name) {
return getDesignDocument(name, false);
}
@Override
public Observable getDesignDocument(final String name, final boolean development) {
return Observable.defer(new Func0>() {
@Override
public Observable call() {
return core.send(new GetDesignDocumentRequest(name, development, bucket, password));
}
}).filter(new Func1() {
@Override
public Boolean call(GetDesignDocumentResponse response) {
boolean success = response.status().isSuccess();
if (!success) {
if (response.content() != null && response.content().refCnt() > 0) {
response.content().release();
}
}
return success;
}
})
.map(new Func1() {
@Override
public DesignDocument call(GetDesignDocumentResponse response) {
JsonObject converted;
try {
converted = CouchbaseAsyncBucket.JSON_OBJECT_TRANSCODER.stringToJsonObject(
response.content().toString(CharsetUtil.UTF_8));
} catch (Exception e) {
throw new TranscodingException("Could not decode design document.", e);
} finally {
if (response.content() != null && response.content().refCnt() > 0) {
response.content().release();
}
}
return DesignDocument.from(response.name(), converted);
}
});
}
@Override
public Observable insertDesignDocument(final DesignDocument designDocument) {
return insertDesignDocument(designDocument, false);
}
@Override
public Observable insertDesignDocument(final DesignDocument designDocument, final boolean development) {
return getDesignDocument(designDocument.name(), development)
.isEmpty()
.flatMap(new Func1>() {
@Override
public Observable call(Boolean doesNotExist) {
if (doesNotExist) {
return upsertDesignDocument(designDocument, development);
} else {
return Observable.error(new DesignDocumentAlreadyExistsException());
}
}
});
}
@Override
public Observable upsertDesignDocument(DesignDocument designDocument) {
return upsertDesignDocument(designDocument, false);
}
@Override
public Observable upsertDesignDocument(final DesignDocument designDocument, final boolean development) {
String body;
try {
body = CouchbaseAsyncBucket.JSON_OBJECT_TRANSCODER.jsonObjectToString(designDocument.toJsonObject());
} catch (Exception e) {
throw new TranscodingException("Could not encode design document: ", e);
}
final String b = body;
return Observable.defer(new Func0>() {
@Override
public Observable call() {
return core.send(new UpsertDesignDocumentRequest(designDocument.name(), b, development, bucket, password));
}
}).map(new Func1() {
@Override
public DesignDocument call(UpsertDesignDocumentResponse response) {
try {
if (!response.status().isSuccess()) {
String msg = response.content().toString(CharsetUtil.UTF_8);
throw new DesignDocumentException("Could not store DesignDocument: " + msg);
}
} finally {
if (response.content() != null && response.content().refCnt() > 0) {
response.content().release();
}
}
return designDocument;
}
});
}
@Override
public Observable removeDesignDocument(String name) {
return removeDesignDocument(name, false);
}
@Override
public Observable removeDesignDocument(final String name, final boolean development) {
return Observable.defer(new Func0>() {
@Override
public Observable call() {
return core.send(new RemoveDesignDocumentRequest(name, development, bucket, password));
}
}).map(new Func1() {
@Override
public Boolean call(RemoveDesignDocumentResponse response) {
if (response.content() != null && response.content().refCnt() > 0) {
response.content().release();
}
return response.status().isSuccess();
}
});
}
@Override
public Observable publishDesignDocument(String name) {
return publishDesignDocument(name, false);
}
@Override
public Observable publishDesignDocument(final String name, final boolean overwrite) {
return getDesignDocument(name, false)
.isEmpty()
.flatMap(new Func1>() {
@Override
public Observable call(Boolean doesNotExist) {
if (!doesNotExist && !overwrite) {
return Observable.error(new DesignDocumentAlreadyExistsException("Document exists in " +
"production and not overwriting."));
}
return getDesignDocument(name, true);
}
})
.flatMap(new Func1>() {
@Override
public Observable call(DesignDocument designDocument) {
return upsertDesignDocument(designDocument);
}
});
}
/*==== INDEX MANAGEMENT ====*/
private static Func1, Observable> errorsToThrowable(final String messagePrefix) {
return new Func1, Observable>() {
@Override
public Observable call(List errors) {
return Observable.error(new CouchbaseException(messagePrefix + errors));
}
};
}
private static Func1 ROW_VALUE_TO_INDEXINFO =
new Func1() {
@Override
public IndexInfo call(AsyncN1qlQueryRow asyncN1qlQueryRow) {
return new IndexInfo(asyncN1qlQueryRow.value());
}
};
@Override
public Observable listN1qlIndexes() {
Expression whereClause = x("keyspace_id").eq(s(bucket))
.and(i("using").eq(s("gsi")));
Statement listIndexes = select("idx.*").from(x("system:indexes").as("idx")).where(whereClause)
.orderBy(Sort.desc("is_primary"), Sort.asc("name"));
final Func1, Observable> errorHandler = errorsToThrowable(
"Error while listing indexes: ");
return queryExecutor.execute(
N1qlQuery.simple(listIndexes, N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS)))
.flatMap(new Func1>() {
@Override
public Observable call(final AsyncN1qlQueryResult aqr) {
return aqr.finalSuccess()
.flatMap(new Func1>() {
@Override
public Observable call(Boolean success) {
if (success) {
return aqr.rows();
} else {
return aqr.errors().toList().flatMap(errorHandler);
}
}
});
}
}).map(ROW_VALUE_TO_INDEXINFO);
}
@Override
public Observable createN1qlPrimaryIndex(final boolean ignoreIfExist, boolean defer) {
Statement createIndex;
UsingWithPath usingWithPath = Index.createPrimaryIndex().on(bucket);
if (defer) {
createIndex = usingWithPath.withDefer();
} else {
createIndex = usingWithPath;
}
return queryExecutor.execute(N1qlQuery.simple(createIndex))
.compose(checkIndexCreation(ignoreIfExist, "Error creating primary index"));
}
@Override
public Observable createN1qlPrimaryIndex(final String customName, final boolean ignoreIfExist, boolean defer) {
Statement createIndex;
UsingWithPath usingWithPath = Index.createNamedPrimaryIndex(customName).on(bucket);
if (defer) {
createIndex = usingWithPath.withDefer();
} else {
createIndex = usingWithPath;
}
return queryExecutor.execute(N1qlQuery.simple(createIndex))
.compose(checkIndexCreation(ignoreIfExist, "Error creating custom primary index " + customName));
}
private static Expression expressionOrIdentifier(Object o) {
if (o instanceof Expression) {
return (Expression) o;
} else if (o instanceof String) {
return i((String) o);
} else {
throw new IllegalArgumentException("Fields for index must be either an Expression or a String identifier");
}
}
@Override
public Observable createN1qlIndex(final String indexName, final boolean ignoreIfExist, boolean defer, Object... fields) {
if (fields == null || fields.length < 1) {
throw new IllegalArgumentException("At least one field is required for secondary index");
}
return createN1qlIndex(indexName, Arrays.asList(fields), null, ignoreIfExist, defer);
}
@Override
public Observable createN1qlIndex(final String indexName, List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy