main.java.com.cloudant.client.api.Database Maven / Gradle / Ivy
Show all versions of cloudant-client Show documentation
/*
* Copyright © 2016, 2019 IBM Corp. All rights reserved.
*
* 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.cloudant.client.api;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.assertNotEmpty;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.assertNotNull;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.close;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.createPost;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.getAsString;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.getResponse;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.getResponseList;
import com.cloudant.client.api.model.DbInfo;
import com.cloudant.client.api.model.FindByIndexOptions;
import com.cloudant.client.api.model.Index;
import com.cloudant.client.api.model.IndexField;
import com.cloudant.client.api.model.Params;
import com.cloudant.client.api.model.PartitionInfo;
import com.cloudant.client.api.model.Permissions;
import com.cloudant.client.api.query.QueryResult;
import com.cloudant.client.api.model.Shard;
import com.cloudant.client.api.query.Indexes;
import com.cloudant.client.api.query.JsonIndex;
import com.cloudant.client.api.views.AllDocsRequestBuilder;
import com.cloudant.client.api.views.ViewRequestBuilder;
import com.cloudant.client.internal.DatabaseURIHelper;
import com.cloudant.client.internal.URIBase;
import com.cloudant.client.internal.query.Helpers;
import com.cloudant.client.internal.util.DeserializationTypes;
import com.cloudant.client.internal.views.AllDocsRequestBuilderImpl;
import com.cloudant.client.internal.views.AllDocsRequestResponse;
import com.cloudant.client.internal.views.ViewQueryParameters;
import com.cloudant.client.org.lightcouch.CouchDatabase;
import com.cloudant.client.org.lightcouch.CouchDbException;
import com.cloudant.client.org.lightcouch.DocumentConflictException;
import com.cloudant.client.org.lightcouch.NoDocumentException;
import com.cloudant.client.org.lightcouch.Response;
import com.cloudant.http.Http;
import com.cloudant.http.HttpConnection;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.reflect.TypeToken;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Logger;
/**
* Contains a Database Public API implementation.
*
* Methods may throw a {@link NoDocumentException} if the database does not exist.
*
* @author Mario Briggs
* @since 0.0.1
*/
public class Database {
static final Logger log = Logger.getLogger(Database.class.getCanonicalName());
private CouchDatabase db;
private CloudantClient client;
private final URI apiV2DBSecurityURI;
/**
* Internal constructor for creating a new Database instance.
*
* @param client the CloudantClient instance to connect with
* @param db the CouchDatabase for executing operations
*/
Database(CloudantClient client, CouchDatabase db) {
super();
this.client = client;
this.db = db;
apiV2DBSecurityURI = new URIBase(client.getBaseUri()).path("_api").path("v2").path("db")
.path(db.getDbName()).path("_security").build();
}
/**
* Constructor for subclasses that want to override Database methods. The supplied Database
* instance is used to instantiate the super class. As such for non-overridden methods the
* implementation of the extending class will be identical to the original Database instance.
*
* Usage example:
*
* {@code
* public class ExtendedDatabase extends Database {
* public ExtendedDatabase(Database database) {
* super(database);
* }
* }
* }
*
*
* @param db existing Database instance
* @since 2.3.0
*/
protected Database(Database db) {
this(db.client, db.db);
}
/**
* Set permissions for a user/apiKey on this database.
*
* Note this method is only applicable to databases that support the
*
* Cloudant authorization API
* such as Cloudant DBaaS. For unsupported databases consider using the /db/_security
* endpoint.
*
* Example usage to set read-only access for a new key on the "example" database:
*
* {@code
* // generate an API key
* ApiKey key = client.generateApiKey();
*
* // get the "example" database
* Database db = client.database("example", false);
*
* // set read-only permissions
* db.setPermissions(key.getKey(), EnumSet.of(Permissions._reader));
* }
*
*
* @param userNameorApikey the user or key to apply permissions to
* @param permissions permissions to grant
* @throws UnsupportedOperationException if called on a database that does not provide the
* Cloudant authorization API
* @see CloudantClient#generateApiKey()
* @see Roles
* @see Modifying permissions
*/
public void setPermissions(String userNameorApikey, EnumSet permissions) {
assertNotEmpty(userNameorApikey, "userNameorApikey");
assertNotEmpty(permissions, "permissions");
final JsonArray jsonPermissions = new JsonArray();
for (Permissions s : permissions) {
final JsonPrimitive permission = new JsonPrimitive(s.toString());
jsonPermissions.add(permission);
}
// get existing permissions
JsonObject perms = getPermissionsObject();
// now set back
JsonElement elem = perms.get("cloudant");
if (elem == null) {
perms.addProperty("_id", "_security");
elem = new JsonObject();
perms.add("cloudant", elem);
}
elem.getAsJsonObject().add(userNameorApikey, jsonPermissions);
HttpConnection put = Http.PUT(apiV2DBSecurityURI, "application/json");
put.setRequestBody(client.getGson().toJson(perms));
// CouchDbExceptions will be thrown for non-2XX cases
client.couchDbClient.executeToResponse(put);
}
/**
* @return /api/v2/db/$dbname/_security JSON data
* @throws UnsupportedOperationException if called on a database that does not provide the
* Cloudant authorization API
*/
private JsonObject getPermissionsObject() {
try {
return client.couchDbClient.get(apiV2DBSecurityURI, JsonObject.class);
} catch (CouchDbException exception) {
//currently we can't inspect the HttpResponse code
//being in this catch block means it was not a 20x code
//look for the "bad request" that implies the endpoint is not supported
if (exception.getMessage().toLowerCase(Locale.ENGLISH).contains("bad request")) {
throw new UnsupportedOperationException("The methods getPermissions and " +
"setPermissions are not supported for this database, consider using the " +
"/db/_security endpoint.");
} else {
throw exception;
}
}
}
/**
* Returns the Permissions of this database.
*
* Note this method is only applicable to databases that support the
*
* Cloudant authorization API
* such as Cloudant DBaaS. For unsupported databases consider using the /db/_security
* endpoint.
*
*
* @return the map of userNames to their Permissions
* @throws UnsupportedOperationException if called on a database that does not provide the
* Cloudant authorization API
* @see Roles
* @see Viewing permissions
*/
public Map> getPermissions() {
JsonObject perms = getPermissionsObject();
return client.getGson().getAdapter(DeserializationTypes.PERMISSIONS_MAP_TOKEN).fromJsonTree
(perms);
}
/**
* Get info about the shards in the database.
*
* @return List of shards
* @see _shards
*/
public List getShards() {
InputStream response = null;
try {
response = client.couchDbClient.get(new DatabaseURIHelper(db.getDBUri()).path("_shards")
.build());
return getResponseList(response, client.getGson(), DeserializationTypes.SHARDS);
} finally {
close(response);
}
}
/**
* Get info about the shard a document belongs to.
*
* @param docId document ID
* @return Shard info
* @see _shards
*/
public Shard getShard(String docId) {
assertNotEmpty(docId, "docId");
return client.couchDbClient.get(new DatabaseURIHelper(db.getDBUri()).path("_shards")
.path(docId).build(),
Shard.class);
}
/**
* Create a new JSON index
*
* Example usage creating an index that sorts ascending on name, then by year:
*
*
* {@code
* db.createIndex("Person_name", "Person_name", null, new IndexField[]{
* new IndexField("Person_name",SortOrder.asc),
* new IndexField("Movie_year",SortOrder.asc)});
* }
*
*
* Example usage creating an index that sorts ascending by year:
*
*
* {@code
* db.createIndex("Movie_year", "Movie_year", null, new IndexField[]{
* new IndexField("Movie_year",SortOrder.asc)});
* }
*
*
* @param indexName optional name of the index (if not provided one will be generated)
* @param designDocName optional name of the design doc in which the index will be created
* @param indexType optional, type of index (only "json" for this method)
* @param fields array of fields in the index
* @see Database#createIndex(String)
*/
@Deprecated
public void createIndex(String indexName, String designDocName, String indexType,
IndexField[] fields) {
if (indexType == null || "json".equalsIgnoreCase(indexType)) {
JsonIndex.Builder b = JsonIndex.builder().name(indexName).designDocument(designDocName);
for (IndexField f : fields) {
switch (f.getOrder()) {
case desc:
b.desc(f.getName());
break;
case asc:
default:
b.asc(f.getName());
break;
}
}
createIndex(b.definition());
} else {
throw new CouchDbException("Unsupported index type " + indexType);
}
}
/**
*
* Create a new index from a string of JSON representing the index definition
*
*
* Helpers are available to construct the index definition string for JSON and text indexes.
*
*
* Example usage creating a JSON index with a generated name for the field named "Movie_year"
* with ascending sort order:
*
*
* {@code
* db.createIndex(JsonIndex.builder().asc("Movie_year").definition());
* }
*
*
* Example usage creating a partial JSON index named "movies-after-2010-json" which will
* index all movies with "Movie_year" greater than 2010, returning the field "Movie_year" in
* descending order:
*
*
* {@code
* Selector selector = gt("Movie_year", 2010);
* String indexDefinition = JsonIndex.builder().
* name("movies-after-2010-json").
* desc("Movie_year").
* partialFilterSelector(selector).
* definition();
* db.createIndex(indexDefinition);
* }
*
*
* Example usage creating a text index with a generated name for the string field named
* "Movie_title":
*
*
* {@code
* db.createIndex(TextIndex.builder().string("Movie_title").definition());
* }
*
*
* Example usage creating a partial text index named "movies-after-2010-text" for the string field
* named "Movie_title" which will index all movies titles for movies with "Movie_year" greater
* than 2010:
*
*
* {@code
* Selector selector = gt("Movie_year", 2010);
* String indexDefinition = TextIndex.builder().
* string("Movie_title").
* name("movies-after-2010-text").
* partialFilterSelector(selector).
* definition();
* db.createIndex(indexDefinition);
* }
*
*
* @param indexDefinition String representation of the index definition JSON
* @see com.cloudant.client.api.query.JsonIndex.Builder
* @see com.cloudant.client.api.query.TextIndex.Builder
* @see index definition
*/
public void createIndex(String indexDefinition) {
assertNotEmpty(indexDefinition, "indexDefinition");
InputStream putresp = null;
URI uri = new DatabaseURIHelper(db.getDBUri()).path("_index").build();
try {
putresp = client.couchDbClient.executeToInputStream(createPost(uri, indexDefinition,
"application/json"));
String result = getAsString(putresp, "result");
if (result.equalsIgnoreCase("created")) {
log.info(String.format("Created Index: '%s'", indexDefinition));
} else {
log.warning(String.format("Index already exists : '%s'", indexDefinition));
}
} finally {
close(putresp);
}
}
/**
* Find documents using an index
*
* @param selectorJson String representation of a JSON object describing criteria used to
* select documents. For example:
* {@code "{ \"selector\": {} }"}.
* @param classOfT The class of Java objects to be returned
* @param the type of the Java object to be returned
* @return List of classOfT objects
* @see #findByIndex(String, Class, FindByIndexOptions)
* @see selector syntax
* @deprecated Use {@link #query(String, Class)} instead
*/
@Deprecated
public List findByIndex(String selectorJson, Class classOfT) {
return findByIndex(selectorJson, classOfT, new FindByIndexOptions());
}
/**
* Find documents using an index
*
* Example usage to return the name and year of movies starring
* Alec Guinness since 1960 with the results sorted by year descending:
*
*
* {@code
* List movies = db.findByIndex("\"selector\": {
* \"Movie_year\": {\"$gt\": 1960}, \"Person_name\": \"Alec Guinness\"
* }",
* Movie.class,
* new FindByIndexOptions()
* .sort(new IndexField("Movie_year", SortOrder.desc))
* .fields("Movie_name").fields("Movie_year"));
* }
*
*
* @param selectorJson String representation of a JSON object describing criteria used to
* select documents. For example:
* {@code "{ \"selector\": {} }"}.
* @param options {@link FindByIndexOptions query Index options}
* @param classOfT The class of Java objects to be returned
* @param the type of the Java object to be returned
* @return List of classOfT objects
* @see selector syntax
* @deprecated Use {@link #query(String, Class)} instead
*/
@Deprecated
public List findByIndex(String selectorJson, Class classOfT, FindByIndexOptions
options) {
JsonObject selector = Helpers.getSelectorFromString(selectorJson);
assertNotEmpty(options, "options");
JsonObject body = getFindByIndexBody(selector, options);
return query(body.toString(), classOfT).getDocs();
}
/**
*
* Query documents using an index and a query selector.
*
*
* Note: the most convenient way to generate query selectors is using a
* {@link com.cloudant.client.api.query.QueryBuilder}.
*
*
* Example usage to return the name and year of movies starring Alec Guinness since 1960
* with the results sorted by year descending:
*
*
* {@code
* QueryResult movies = db.query(new QueryBuilder(and(
* gt("Movie_year", 1960),
* eq("Person_name", "Alec Guinness"))).
* sort(Sort.desc("Movie_year")).
* fields("Movie_name", "Movie_year").
* build(), Movie.class);
* }
*
* @param query String representation of a JSON object describing criteria used to
* select documents.
* @param classOfT The class of Java objects to be returned in the {@code docs} field of result.
* @param The type of the Java object to be returned in the {@code docs} field of result.
* @return A {@link QueryResult} object, containing the documents matching the query
* in the {@code docs} field.
* @see com.cloudant.client.api.query.QueryBuilder
* @see selector syntax
*/
public QueryResult query(String query, final Class classOfT) {
URI uri = new DatabaseURIHelper(db.getDBUri()).path("_find").build();
return this.query(uri, query, classOfT);
}
/**
* Execute a partitioned query using an index and a query selector.
*
* Only available in partitioned databases. To verify a database is partitioned call
* {@link Database#info()} and check that {@link DbInfo.Props#getPartitioned()} returns
* {@code true}.
*
* Example usage:
*
* {@code
* // Query database partition 'Coppola'.
* QueryResult movies = db.query("Coppola", new QueryBuilder(and(
* gt("Movie_year", 1960),
* eq("Person_name", "Al Pacino"))).
* fields("Movie_name", "Movie_year").
* build(), Movie.class);
* }
*
*
* @param partitionKey Database partition to query.
* @param query String representation of a JSON object describing criteria used to
* select documents.
* @param classOfT The class of Java objects to be returned in the {@code docs} field of
* result.
* @param The type of the Java object to be returned in the {@code docs} field of
* result.
* @return A {@link QueryResult} object, containing the documents matching the query
* in the {@code docs} field.
* @see com.cloudant.client.api.Database#query(String, Class)
*/
public QueryResult query(String partitionKey, String query, final Class classOfT) {
URI uri = new DatabaseURIHelper(db.getDBUri()).partition(partitionKey).path("_find").build();
return this.query(uri, query, classOfT);
}
private QueryResult query(URI uri, String query, final Class classOfT) {
InputStream stream = null;
try {
stream = client.couchDbClient.executeToInputStream(createPost(uri, query,
"application/json"));
Reader reader = new InputStreamReader(stream, "UTF-8");
Type type = TypeToken.getParameterized(QueryResult.class, classOfT).getType();
return client.getGson().fromJson(reader, type);
} catch (UnsupportedEncodingException e) {
// This should never happen as every implementation of the java platform is required
// to support UTF-8.
throw new RuntimeException(e);
} finally {
close(stream);
}
}
/**
* List all indices
* Example usage:
*
* {@code
* List indices = db.listIndices();
* }
*
*
* @return List of Index objects
* @see Database#listIndexes()
*/
@Deprecated
public List listIndices() {
InputStream response = null;
try {
URI uri = new DatabaseURIHelper(db.getDBUri()).path("_index").build();
response = client.couchDbClient.get(uri);
return getResponseList(response, client.getGson(), DeserializationTypes.INDICES);
} finally {
close(response);
}
}
/**
* List the indexes in the database. The returned object allows for listing indexes by type.
*
* @return indexes object with methods for getting indexes of a particular type
*/
public Indexes listIndexes() {
URI uri = new DatabaseURIHelper(db.getDBUri()).path("_index").build();
return client.couchDbClient.get(uri, Indexes.class);
}
/**
* Delete a JSON index
*
* @param indexName name of the index
* @param designDocId ID of the design doc
*/
public void deleteIndex(String indexName, String designDocId) {
deleteIndex(indexName, designDocId, "json");
}
/**
* Delete an index with the specified name and type in the given design document.
*
* @param indexName name of the index
* @param designDocId ID of the design doc (the _design prefix will be added if not present)
* @param type type of the index, valid values or "text" or "json"
*/
public void deleteIndex(String indexName, String designDocId, String type) {
assertNotEmpty(indexName, "indexName");
assertNotEmpty(designDocId, "designDocId");
assertNotNull(type, "type");
if (!designDocId.startsWith("_design")) {
designDocId = "_design/" + designDocId;
}
URI uri = new DatabaseURIHelper(db.getDBUri()).path("_index").path(designDocId)
.path(type).path(indexName).build();
InputStream response = null;
try {
HttpConnection connection = Http.DELETE(uri);
response = client.couchDbClient.executeToInputStream(connection);
getResponse(response, Response.class, client.getGson());
} finally {
close(response);
}
}
/**
* Provides access to Cloudant Search APIs.
*
* Example usage:
*
* {@code
* // Search query using design document _id '_design/views101' and search index 'animals'
* List birds = db.search("views101/animals")
* .limit(10)
* .includeDocs(true)
* .query("class:bird", Bird.class);
* }
*
*
* @param searchIndexId the design document with the name of the index to search
* @return Search object for searching the index
* @see Search
*/
public Search search(String searchIndexId) {
return new Search(client, this, null, searchIndexId);
}
/**
* Provides access to partitioned Cloudant Search APIs.
*
* Only available in partitioned databases. To verify a database is partitioned call
* {@link Database#info()} and check that {@link DbInfo.Props#getProps()} returns
* {@code true}.
*
* Example usage:
*
* {@code
* // Search query over partition 'aves' using design document _id '_design/views101' and
* // search index 'partitioned_animals'.
* List birds = db.search("aves", "views101/partitioned_animals")
* .query("name:puffin", Bird.class);
* }
*
*
* @param partitionKey database partition key
* @param searchIndexId the design document with the name of the index to search
* @return Search object for searching the index
* @see Search
*/
public Search search(String partitionKey, String searchIndexId) {
return new Search(client, this, partitionKey, searchIndexId);
}
/**
* Get a manager that has convenience methods for managing design documents.
*
* @return a {@link DesignDocumentManager} for this database
* @see DesignDocumentManager
*/
public DesignDocumentManager getDesignDocumentManager() {
return new DesignDocumentManager(client, this);
}
/**
* @param designDoc containing the view
* @param viewName the view name
* @return a builder to build view requests for the specified design document and view of
* this database
* @see Using views
*/
public ViewRequestBuilder getViewRequestBuilder(String designDoc, String viewName) {
return new ViewRequestBuilder(client, this, designDoc, viewName);
}
/**
* Build a request for the _all_docs endpoint.
*
* Example usage:
*
*
* {@code
* getAllDocsRequestBuilder().build().getResponse();
* }
*
*
* @return a request builder for the _all_docs endpoint of this database
*/
public AllDocsRequestBuilder getAllDocsRequestBuilder() {
return new AllDocsRequestBuilderImpl(new ViewQueryParameters(client, this, "", "", String.class,
AllDocsRequestResponse.AllDocsValue.class) {
protected DatabaseURIHelper getViewURIBuilder() {
return new DatabaseURIHelper(db.getDBUri()).partition(partition).path("_all_docs");
}
});
}
/**
* Provides access for interacting with the changes feed.
*
* See the {@link com.cloudant.client.api.Changes} API for examples.
*
*
* @return a Changes object for using the changes feed
* @see com.cloudant.client.api.Changes
* @see Databases - get changes
*/
public Changes changes() {
return new Changes(client, this);
}
/**
* Retrieve the document with the specified ID from the database and deserialize to an
* instance of the POJO of type T.
*
* @param object type
* @param classType the class of type T
* @param id the document id
* @return an object of type T
* @throws NoDocumentException if the document is not found in the database
* @see #find(Class, String, String)
* @see Documents - read
*/
public T find(Class classType, String id) {
return db.find(classType, id);
}
/**
* Retrieve the document with the specified ID from the database and deserialize to an
* instance of the POJO of type T. Uses the additional parameters specified when making the
* {@code GET} request.
* Example usage to get inline attachments:
*
* {@code
* Foo foo = db.find(Foo.class, "exampleId", new Params().attachments());
* String attachmentData = foo.getAttachments().get("attachment.txt").getData();
* }
*
*
* @param object type
* @param classType the class of type T
* @param id the document id
* @param params extra parameters to append
* @return An object of type T
* @throws NoDocumentException if the document is not found in the database.
* @see Params
* @see Documents - read
*/
public T find(Class classType, String id, Params params) {
assertNotEmpty(params, "params");
return db.find(classType, id, params.getInternalParams());
}
/**
* Retrieve the document with the specified ID at the specified revision from the database
* and deserialize to an instance of the POJO of type T.
* Example usage:
*
* {@code
* Foo foo = db.find(Foo.class, "exampleId", "1-12345exampleRev");
* }
*
*
* @param object type
* @param classType the class of type T
* @param id the document _id field
* @param rev the document _rev field
* @return an object of type T
* @throws NoDocumentException if the document is not found in the database.
* @see Documents - read
*/
public T find(Class classType, String id, String rev) {
return db.find(classType, id, rev);
}
/**
* This method finds any document given a URI.
* The URI must be URI-encoded.
*
* Example usage retrieving the Foo POJO with document ID "exampleId" from the database
* "exampleDb" in the "example" Cloudant account.
*
*
* {@code
* Foo foo = db.findAny(Foo.class, "https://example.cloudant.com/exampleDb/exampleId");
* }
*
*
* @param classType the class of type T
* @param uri the URI as string
* @param the type of Java object to return
* @return an object of type T
*/
public T findAny(Class classType, String uri) {
return db.findAny(classType, uri);
}
/**
* Finds the document with the specified document ID and returns it as an {@link InputStream}.
* Note: The stream must be closed after use to release the connection.
*
* @param id the document _id field
* @return the result as {@link InputStream}
* @throws NoDocumentException If the document is not found in the database.
* @see #find(String, String)
*/
public InputStream find(String id) {
return db.find(id);
}
/**
* Finds the document with the specified document ID and revision and returns it as {@link
* InputStream}.
* Note: The stream must be closed after use to release the connection.
* Example usage:
*
* {@code
* InputStream inputStream = null;
* try{
* inputStream = db.find("exampleId", "1-12345exampleRev");
* //do stuff with the stream
* } finally {
* //close the input stream
* inputStream.close();
* }
* }
*
*
* @param id the document _id field
* @param rev the document _rev field
* @return the result as {@link InputStream}
* @throws NoDocumentException if the document is not found in the database at the specified
* revision
* @see Documents - read
*/
public InputStream find(String id, String rev) {
return db.find(id, rev);
}
/**
* Checks if a document exists in the database.
*
* @param id the document _id field
* @return {@code true} if the document is found, {@code false} otherwise
*/
public boolean contains(String id) {
return db.contains(id);
}
/**
* Saves a document in the database.
* If the serialized object's JSON does not contain an {@code _id} field, then a UUID will
* be generated for the document ID.
*
*
* Example of inserting a JsonObject into the database:
*
*
* {@code
* JsonObject json = new JsonObject();
* json.addProperty("_id", "test-doc-id-2");
* json.add("json-array", new JsonArray());
* Response response = db.save(json);
* }
*
*
* Example of inserting a POJO into the database:
*
*
* {@code
* Foo foo = new Foo();
* Response response = db.save(foo);
* }
*
*
* Example of inserting a Map into the database:
*
*
* {@code
* Map map = new HashMap<>();
* map.put("_id", "test-doc-id-1");
* map.put("title", "test-doc");
* Response response = db.save(map);
* }
*
*
*
* Note that the Java object is not modified by the save operation and so will not include the
* revision generated by the database server. You can obtain the server revision for this write
* from the response, for example:
*
*
* {@code
* Foo foo = new Foo();
* Response response = db.save(foo);
* foo.setRevision(response.getRevision());
* }
*
*
* @param object the object to save
* @return {@link com.cloudant.client.api.model.Response}
* @throws DocumentConflictException If a conflict is detected during the save.
*/
public com.cloudant.client.api.model.Response save(Object object) {
Response couchDbResponse = db.save(object);
com.cloudant.client.api.model.Response response = new com.cloudant.client.api.model
.Response(couchDbResponse);
return response;
}
/**
* Saves a document in the database similarly to {@link Database#save(Object)} but using a
* specific write quorum.
*
* @param object the object to save
* @param writeQuorum the write quorum
* @return {@link com.cloudant.client.api.model.Response}
* @throws DocumentConflictException If a conflict is detected during the save.
* @see Database#save(Object)
* @see Documents - quorum
*/
public com.cloudant.client.api.model.Response save(Object object, int writeQuorum) {
Response couchDbResponse = client.couchDbClient.put(getDBUri(), object, true, writeQuorum);
com.cloudant.client.api.model.Response response = new com.cloudant.client.api.model
.Response(couchDbResponse);
return response;
}
/**
* Creates a document in the database using a HTTP {@code POST} request.
* If the serialized object's JSON does not contain an {@code _id} field, then the server
* will generate a document ID.
*
* Example of creating a document in the database for a POJO:
*
*
* {@code
* Foo foo = new Foo();
* Response response = db.post(foo);
* }
*
*
* Note that the Java object is not modified by the create operation and so will not include the
* revision generated by the database server. You can obtain the server revision for this write
* from the response, for example:
*
*
* {@code
* Foo foo = new Foo();
* Response response = db.post(foo);
* foo.setRevision(response.getRevision());
* }
*
*
* @param object The object to save
* @return {@link com.cloudant.client.api.model.Response}
* @see Documents - create
*/
public com.cloudant.client.api.model.Response post(Object object) {
Response couchDbResponse = db.post(object);
com.cloudant.client.api.model.Response response = new com.cloudant.client.api.model
.Response(couchDbResponse);
return response;
}
/**
* Creates a document in the database similarly to {@link Database#post(Object)} but using a
* specific write quorum.
*
* @param object The object to save
* @param writeQuorum the write Quorum
* @return {@link com.cloudant.client.api.model.Response}
* @see Database#post(Object)
* @see Documents - quorum
*/
public com.cloudant.client.api.model.Response post(Object object, int writeQuorum) {
assertNotEmpty(object, "object");
InputStream response = null;
try {
URI uri = new DatabaseURIHelper(db.getDBUri()).query("w", writeQuorum).build();
response = client.couchDbClient.executeToInputStream(createPost(uri, client.getGson()
.toJson(object),
"application/json"));
Response couchDbResponse = getResponse(response, Response.class, client.getGson());
com.cloudant.client.api.model.Response cloudantResponse = new com.cloudant.client.api
.model.Response(couchDbResponse);
return cloudantResponse;
} finally {
close(response);
}
}
/**
* Updates an object in the database, the object must have the correct {@code _id} and
* {@code _rev} values.
* Example usage:
*
* {@code
* //get a Bar object from the database
* Bar bar = db.find(Bar.class, "exampleId");
* //change something about bar
* bar.setSomeProperty(true);
* //now update the remote Bar
* Response responseUpdate = db.update(bar);
* }
*
*
* Note that the Java object is not modified by the update operation and so will not include the
* revision generated by the database server. You can obtain the server revision for this write
* from the response, for example:
*
*
* {@code
* Foo foo = new Foo();
* Response response = db.update(foo);
* foo.setRevision(response.getRevision());
* }
*
*
* @param object the object to update
* @return {@link com.cloudant.client.api.model.Response}
* @throws DocumentConflictException if a conflict is detected during the update.
* @see Documents - update
*/
public com.cloudant.client.api.model.Response update(Object object) {
Response couchDbResponse = db.update(object);
com.cloudant.client.api.model.Response response = new com.cloudant.client.api.model
.Response(couchDbResponse);
return response;
}
/**
* Updates an object in the database similarly to {@link #update(Object)}, but specifying the
* write quorum.
*
* @param object the object to update
* @param writeQuorum the write quorum
* @return {@link com.cloudant.client.api.model.Response}
* @throws DocumentConflictException if a conflict is detected during the update.
* @see Database#update(Object)
* @see Documents - quorum
*/
public com.cloudant.client.api.model.Response update(Object object, int writeQuorum) {
Response couchDbResponse = client.couchDbClient.put(getDBUri(), object, false, writeQuorum);
com.cloudant.client.api.model.Response response = new com.cloudant.client.api.model
.Response(couchDbResponse);
return response;
}
/**
* Removes a document from the database.
* The object must have the correct {@code _id} and {@code _rev} values.
* Example usage:
*
* {@code
* //get a Bar object from the database
* Bar bar = db.find(Bar.class, "exampleId");
* //now remove the remote Bar
* Response response = db.remove(bar);
* }
*
*
* @param object the document to remove as an object
* @return {@link com.cloudant.client.api.model.Response}
* @throws NoDocumentException If the document is not found in the database.
* @see Documents - delete
*/
public com.cloudant.client.api.model.Response remove(Object object) {
Response couchDbResponse = db.remove(object);
com.cloudant.client.api.model.Response response = new com.cloudant.client.api.model
.Response(couchDbResponse);
return response;
}
/**
* Removes the document from the database with the specified {@code _id} and {@code _rev}
* values.
* Example usage:
*
* {@code
* Response response = db.remove("exampleId", "1-12345exampleRev");
* }
*
*
* @param id the document _id field
* @param rev the document _rev field
* @return {@link com.cloudant.client.api.model.Response}
* @throws NoDocumentException If the document is not found in the database.
* @see Documents - delete
*/
public com.cloudant.client.api.model.Response remove(String id, String rev) {
Response couchDbResponse = db.remove(id, rev);
com.cloudant.client.api.model.Response response = new com.cloudant.client.api.model
.Response(couchDbResponse);
return response;
}
/**
* Uses the {@code _bulk_docs} endpoint to insert multiple documents into the database in a
* single HTTP request.
* Example usage:
*
* {@code
* //create a list
* List
*
* Note that the value returned by {@code getStatusCode()} on each {@code Response} object is
* the overall status returned from {@code bulk_docs} and will therefore be the same for all
* {@code Response} objects.
*
*
* The returned list of {@code Response}s should be examined to ensure that all of the documents submitted
* in the original request were successfully added to the database.
*
*
* When a document (or document revision) is not correctly committed to the database because of
* an error, you must check the value of {@code getError()} to determine error type and course
* of action. See
*
* Bulk Document Validation and Conflict Errors for more information.
*
*
* @param objects the {@link List} of objects
* @return {@code List} one per object
* @see Documents - bulk operations
*/
public List bulk(List> objects) {
List couchDbResponseList = db.bulk(objects, false);
List cloudantResponseList = new ArrayList(couchDbResponseList.size());
for (Response couchDbResponse : couchDbResponseList) {
com.cloudant.client.api.model.Response response = new com.cloudant.client.api.model
.Response(couchDbResponse);
cloudantResponseList.add(response);
}
return cloudantResponseList;
}
/**
* Reads an attachment from the database.
*
* The stream must be closed after usage, otherwise http connection leaks will occur.
*
* @param docId the document id
* @param attachmentName the attachment name
* @return the attachment in the form of an {@code InputStream}.
*/
public InputStream getAttachment(String docId, String attachmentName) {
return getAttachment(docId, attachmentName, null);
}
/**
* Reads an attachment from the database.
*
* The stream must be closed after usage, otherwise http connection leaks will occur.
*
* @param docId the document id
* @param attachmentName the attachment name
* @param revId the document revision id or {@code null}
* @return the attachment in the form of an {@code InputStream}.
*/
public InputStream getAttachment(String docId, String attachmentName, String revId) {
return db.getAttachment(docId, attachmentName, revId);
}
/**
* Creates an attachment from the specified InputStream and a new document with a generated
* document ID.
* Example usage:
*
* {@code
* byte[] bytesToDB = "binary data".getBytes();
* ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesToDB);
* Response response = db.saveAttachment(bytesIn, "foo.txt", "text/plain");
* }
*
* To retrieve an attachment, see {@link #find(Class, String, Params)}
*
* @param in The {@link InputStream} providing the binary data.
* @param name The attachment name.
* @param contentType The attachment "Content-Type".
* @return {@link com.cloudant.client.api.model.Response}
* @see Attachments
*/
public com.cloudant.client.api.model.Response saveAttachment(InputStream in, String name,
String contentType) {
Response couchDbResponse = db.saveAttachment(in, name, contentType);
com.cloudant.client.api.model.Response response = new com.cloudant.client.api.model
.Response(couchDbResponse);
return response;
}
/**
* Creates or updates an attachment on the given document ID and revision.
*
* If {@code docId} and {@code docRev} are {@code null} a new document will be created.
*
*
* Example usage:
*
*
* {@code
* byte[] bytesToDB = "binary data".getBytes();
* ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesToDB);
* Response response = db.saveAttachment(bytesIn, "foo.txt", "text/plain","exampleId","3-rev");
* }
*
* Saves an attachment to an existing document given both a document id
* and revision, or save to a new document given only the id, and rev as {@code null}.
* To retrieve an attachment, see {@link #find(Class, String, Params)}
*
* @param in The {@link InputStream} providing the binary data.
* @param name The attachment name.
* @param contentType The attachment "Content-Type".
* @param docId The document ID to save the attachment under, or {@code null} to save
* under a new document with a generated ID.
* @param docRev The document revision to save the attachment under, or {@code null}
* when saving to a new document.
* @return {@link Response}
* @throws DocumentConflictException if the attachment cannot be saved because of a conflict
* @see Attachments
*/
public com.cloudant.client.api.model.Response saveAttachment(InputStream in, String name,
String contentType, String
docId, String docRev) {
Response couchDbResponse = db.saveAttachment(in, name, contentType, docId, docRev);
com.cloudant.client.api.model.Response response = new com.cloudant.client.api.model
.Response(couchDbResponse);
return response;
}
/**
* Removes an attachment from the specified document.
* The object must have the correct {@code _id} and {@code _rev} values.
* Example usage:
*
* {@code
* //get a Bar object from the database
* Bar bar = db.find(Bar.class, "exampleId");
* String attachmentName = "example.jpg";
* //now remove the remote Bar attachment
* Response response = db.removeAttachment(bar, attachmentName);
* }
*
*
* @param object the document to remove as an object
* @param attachmentName the attachment name to remove
* @return {@link com.cloudant.client.api.model.Response}
* @throws NoDocumentException If the document is not found in the database.
* @throws DocumentConflictException If a conflict is detected during the removal.
* @see Documents - delete
*/
public com.cloudant.client.api.model.Response removeAttachment(Object object, String attachmentName) {
Response couchDbResponse = db.removeAttachment(object, attachmentName);
com.cloudant.client.api.model.Response response = new com.cloudant.client.api.model
.Response(couchDbResponse);
return response;
}
/**
* Removes the attachment from a document the specified {@code _id} and {@code _rev} and {@code attachmentName}
* values.
* Example usage:
*
* {@code
* Response response = db.removeAttachment("exampleId", "1-12345exampleRev", "example.jpg");
* }
*
*
* @param id the document _id field
* @param rev the document _rev field
* @param attachmentName the attachment name
* @return {@link com.cloudant.client.api.model.Response}
* @throws NoDocumentException If the document is not found in the database.
* @throws DocumentConflictException if the attachment cannot be removed because of a conflict
* @see Documents - delete
*/
public com.cloudant.client.api.model.Response removeAttachment(String id, String rev, String attachmentName) {
Response couchDbResponse = db.removeAttachment(id, rev, attachmentName);
com.cloudant.client.api.model.Response response = new com.cloudant.client.api.model
.Response(couchDbResponse);
return response;
}
/**
* Invokes an Update Handler.
* Example usage:
*
* {@code
* final String newValue = "foo bar";
* Params params = new Params()
* .addParam("field", "title")
* .addParam("value", newValue);
* String output = db.invokeUpdateHandler("example/example_update", "exampleId", params);
*
* }
*
*
* Params params = new Params()
* .addParam("field", "foo")
* .addParam("value", "bar");
* String output = dbClient.invokeUpdateHandler("designDoc/update1", "docId", params);
*
*
* @param updateHandlerUri The Update Handler URI, in the format: designDoc/update1
* @param docId The document id to update.
* If no id is provided, then a document will be created.
* @param params The query parameters as {@link Params}.
* @return The output of the request.
* @see Design documents - update handlers
*/
public String invokeUpdateHandler(String updateHandlerUri, String docId,
Params params) {
assertNotEmpty(params, "params");
return db.invokeUpdateHandler(updateHandlerUri, docId, params.getInternalParams());
}
/**
* @return The database URI.
*/
public URI getDBUri() {
return db.getDBUri();
}
/**
* Get information about this database.
*
* @return DbInfo encapsulating the database info
* @see Databases - read
*/
public DbInfo info() {
return client.couchDbClient.get(new DatabaseURIHelper(db.getDBUri()).getDatabaseUri(),
DbInfo.class);
}
/**
* Get information about a partition in this database.
*
* @param partitionKey database partition key
* @return {@link com.cloudant.client.api.model.PartitionInfo} encapsulating the database partition info.
* @throws UnsupportedOperationException if called with {@code null} partition key.
*/
public PartitionInfo partitionInfo(String partitionKey) {
if (partitionKey == null) {
throw new UnsupportedOperationException("Cannot get partition information for null partition key.");
}
URI uri = new DatabaseURIHelper(db.getDBUri()).partition(partitionKey).build();
return client.couchDbClient.get(uri, PartitionInfo.class);
}
/**
* Requests the database commits any recent changes to disk.
*
* @see
* CouchDB _ensure_full_commit
*
*/
public void ensureFullCommit() {
db.ensureFullCommit();
}
// private helper methods
/**
*
* @param selector sanitized selector JSON object (excluding "selector" key)
* @param options find by index options
* @return query object to POST
*/
private JsonObject getFindByIndexBody(JsonObject selector,
FindByIndexOptions options) {
JsonArray fieldsArray = new JsonArray();
if (options.getFields().size() > 0) {
for (String field : options.getFields()) {
JsonPrimitive jsonField = client.getGson().fromJson(field,
JsonPrimitive.class);
fieldsArray.add(jsonField);
}
}
JsonArray sortArray = new JsonArray();
if (options.getSort().size() > 0) {
for (IndexField sort : options.getSort()) {
JsonObject sortObject = new JsonObject();
sortObject.addProperty(sort.getName(), sort.getOrder().toString());
sortArray.add(sortObject);
}
}
JsonObject indexObject = new JsonObject();
indexObject.add("selector", selector);
if (fieldsArray.size() > 0) {
indexObject.add("fields", fieldsArray);
}
if (sortArray.size() > 0) {
indexObject.add("sort", sortArray);
}
if (options.getLimit() != null) {
indexObject.addProperty("limit", options.getLimit());
}
if (options.getSkip() != null) {
indexObject.addProperty("skip", options.getSkip());
}
if (options.getReadQuorum() != null) {
indexObject.addProperty("r", options.getReadQuorum());
}
if (options.getUseIndex() != null) {
indexObject.add("use_index", getGson().fromJson(options.getUseIndex(),
JsonElement.class));
}
return indexObject;
}
Gson getGson() {
return client.getGson();
}
}