/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * 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. */ /* * This file has been modified for * * SMART FP7 - Search engine for MultimediA enviRonment generated contenT * Webpage: http://smartfp7.eu * * The modifications are Copyright (c) 2012-2013 Athens Information Technology * All Rights Reserved * * Contributors: * Nikolaos Katsarakis nkat@ait.edu.gr * Menelaos Bakopoulos mbak@ait.edu.gr * * Changes done: * Added getRevision(String id) and saveJsonText(String jsonText) functions * */ package org.lightcouch; import static org.lightcouch.CouchDbUtil.*; import static org.lightcouch.URIBuilder.builder; import java.io.InputStream; import java.net.URI; import org.apache.http.HttpResponse; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; /** * <p>Presents a client to a CouchDB database instance. * <p>This is the main class to use to gain access to the various APIs defined by this client. * * <h3>Usage Example:</h3> * <p>Instantiating an instance of this class requires configuration options to be supplied. * Properties files may be used for this purpose. See overloaded constructors for available options. * <p>A typical example for creating an instance is by preparing a properties file named * <tt>couchdb.properties</tt> and placing it in your application classpath: * * <pre> * couchdb.name=my-db * couchdb.createdb.if-not-exist=true * couchdb.protocol=http * couchdb.host=127.0.0.1 * couchdb.port=5984 * couchdb.username= * couchdb.password= * </pre> * * <p>Then construct a new instance using the default constructor: * <pre> * CouchDbClient dbClient = new CouchDbClient(); // looks for <tt>classpath:couchdb.properties</tt> * // access the API here * </pre> * <p>Multiple client instances could be created to handle multiple database instances simultaneously in a thread-safe manner, * typically one client for each database. * * <p>A client instance provides access to various APIs, accessible under several locations or contexts. * <p>Document APIs are available directly under this instance: * <pre> * Foo foo = dbClient.find(Foo.class, "some-id"); * </pre> * * <p>Design documents API under the context <tt>design()</tt> {@link CouchDbDesign} contains usage example. * * <p>View APIs under the context <tt>view()</tt> {@link View} contains usage examples. * * <p>Change Notifications API under the context <tt>changes()</tt> see {@link Changes} for usage example. * * <p>Replication APIs under two contexts: <tt>replication()</tt> and <tt>replicator()</tt>, * the latter supports the replicator database introduced with CouchDB v 1.1.0 * {@link Replication} and {@link Replicator} provide usage examples. * * <p>Database APIs under the context <tt>context()</tt> * * <p>After completing usage of this client, it might be useful to shutdown it's * underlying connection manager to ensure proper release of resources: * <tt>dbClient.shutdown()</tt> * * @author Ahmed Yehia * */ public final class CouchDbClient extends CouchDbClientBase { // -------------------------------------------------------------------------- Constructors /** * Constructs a new instance of this class, expects a configuration file named * <code>couchdb.properties</code> to be available in your application classpath. */ public CouchDbClient() { super(); } /** * Constructs a new instance of this class. * @param configFileName The configuration file name. */ public CouchDbClient(String configFileName) { super(new CouchDbConfig(configFileName)); } /** * Constructs a new instance of this class. * @param dbName The database name. * @param createDbIfNotExist To create a new database if it does not already exist. * @param protocol The protocol to use (i.e http or https) * @param host The database host address * @param port The database listening port * @param username The Username credential * @param password The Password credential */ public CouchDbClient(String dbName, boolean createDbIfNotExist, String protocol, String host, int port, String username, String password) { super(new CouchDbConfig(dbName, createDbIfNotExist, protocol, host, port, username, password)); } private CouchDbContext context; private CouchDbDesign design; { context = new CouchDbContext(this, getConfig()); design = new CouchDbDesign(this); } /** * {@inheritDoc} */ @Override public void setGsonBuilder(GsonBuilder gsonBuilder) { super.setGsonBuilder(gsonBuilder); } /** * Provides access to the database APIs. */ public CouchDbContext context() { return context; } /** * Provides access to the database design documents API. */ public CouchDbDesign design() { return design; } /** * Provides access to the View APIs. */ public View view(String viewId) { return new View(this, viewId); } /** * Provides access to the replication APIs. */ public Replication replication() { return new Replication(this); } /** * Provides access to the replicator database APIs. */ public Replicator replicator() { return new Replicator(this); } /** * Provides access to the Change Notifications API. */ public Changes changes() { return new Changes(this); } /** * Finds an Object of the specified type. * @param <T> 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. */ public <T> T find(Class<T> classType, String id) { assertNotEmpty(classType, "Class Type"); assertNotEmpty(id, "id"); return get(builder(getDBUri()).path(id).build(), classType); } /** * Finds an Object of the specified type. * @param <T> Object type. * @param classType The class of type T. * @param id The document id to get. * @param rev The document revision. * @return An object of type T. * @throws NoDocumentException If the document is not found in the database. */ public <T> T find(Class<T> classType, String id, String rev) { assertNotEmpty(classType, "Class Type"); assertNotEmpty(id, "id"); assertNotEmpty(id, "rev"); URI uri = builder(getDBUri()).path(id).query("rev", rev).build(); return get(uri, classType); } /** * A General purpose find, that gives more control over the query. * <p>Unlike other finders, this method expects a fully formated and encoded URI to be supplied. * @param classType The class of type T. * @param uri The URI. * @return An object of type T. */ public <T> T findAny(Class<T> classType, String uri) { assertNotEmpty(classType, "Class Type"); assertNotEmpty(uri, "uri"); return get(URI.create(uri), classType); } /** * <p>Finds a document and returns the result as an {@link InputStream}.</p> * The stream should be properly closed after usage, as to avoid connection leaks. * @param id The document id. * @return The result of the request as an {@link InputStream} * @throws NoDocumentException If the document is not found in the database. * @see #find(String, String) */ public InputStream find(String id) { assertNotEmpty(id, "id"); return get(builder(getDBUri()).path(id).build()); } /** * <p>Finds a document given an id and revision, returns the result as {@link InputStream}.</p> * The stream should be properly closed after usage, as to avoid connection leaks. * @param id The document id. * @param rev The document revision. * @return The result of the request as an {@link InputStream} * @throws NoDocumentException If the document is not found in the database. */ public InputStream find(String id, String rev) { assertNotEmpty(id, "id"); assertNotEmpty(rev, "rev"); return get(builder(getDBUri()).path(id).query("rev", rev).build()); } /** * Checks if the database contains a document given an id. * @param id The document id. * @return true If the document is found, false otherwise. */ public boolean contains(String id) { assertNotEmpty(id, "id"); HttpResponse response = null; try { response = head(builder(getDBUri()).path(id).build()); } catch (NoDocumentException e) { return false; } finally { close(response); } return true; } /** * Returns the document revision given an id. * @param id The document id. * @return The document revision if the document is found, null otherwise. */ public String getRevision(String id) { assertNotEmpty(id, "id"); HttpResponse response = null; String rev=null; try { response = head(builder(getDBUri()).path(id).build()); org.apache.http.Header test[] = response.getHeaders("ETag"); if (test.length==1) { rev = test[0].getValue(); // Remove the start and end quotes rev = rev.substring(1, rev.length()-1); } } catch (NoDocumentException e) { return null; } finally { close(response); } return rev; } /** * Saves an object in the database. * @param object The object to save * @throws DocumentConflictException If a conflict is detected during the save. * @return {@link Response} */ public Response save(Object object) { return put(getDBUri(), object, true); } /** * Saves a json String in the database. * @param jsonText A String containing the JSON-formatted data to save * @throws DocumentConflictException If a conflict is detected during the save. * @return {@link Response} */ public Response saveJsonText(String jsonText) { return putJsonText(getDBUri(), jsonText); } /** * Saves the given object in a batch request. * @param object The object to save. */ public void batch(Object object) { HttpResponse response = null; try { URI uri = builder(getDBUri()).query("batch", "ok").build(); response = post(uri, getGson().toJson(object)); } finally { close(response); } } /** * <p>Saves an attachment under a new document with a generated UUID as the document id. * <p>To retrieve an attachment, see {@link #find(String)}. * @param instream The {@link InputStream} holding the binary data. * @param name The attachment name. * @param contentType The attachment "Content-Type". * @return {@link Response} */ public Response saveAttachment(InputStream instream, String name, String contentType) { assertNotEmpty(instream, "Input Stream"); assertNotEmpty(name, "Attachment Name"); assertNotEmpty(contentType, "Content-Type"); URI uri = builder(getDBUri()).path(generateUUID()).path("/").path(name).build(); return put(uri, instream, contentType); } /** * <p>Saves an attachment under an existing document given both a document id * and revision, or under a new document given only the document id. * <p>To retrieve an attachment, see {@link #find(String)}. * @param instream The {@link InputStream} holding 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. * @param docRev The document revision to save the attachment under, or {@code null} when saving to a new document. * @throws DocumentConflictException * @return {@link Response} */ public Response saveAttachment(InputStream instream, String name, String contentType, String docId, String docRev) { assertNotEmpty(instream, "Input Stream"); assertNotEmpty(name, "Attachment Name"); assertNotEmpty(contentType, "Content-Type"); assertNotEmpty(docId, "Document id"); URI uri = builder(getDBUri()).path(docId).path("/").path(name).query("rev", docRev).build(); return put(uri, instream, contentType); } /** * Updates an object in the database, the object must have the correct id and revision values. * @param object The object to update * @throws DocumentConflictException If a conflict is detected during the update. * @return {@link Response} */ public Response update(Object object) { return put(getDBUri(), object, false); } /** * Removes an object from the database, the object must have the correct id and revision values. * @param object The object to remove * @throws NoDocumentException If the document could not be found in the database. * @return {@link Response} */ public Response remove(Object object) { assertNotEmpty(object, "Entity"); JsonObject jsonObject = objectToJson(getGson(), object); String id = getElement(jsonObject, "_id"); String rev = getElement(jsonObject, "_rev"); return remove(id, rev); } /** * Removes a document from the database, given both an id and revision values. * @param id The document id * @param rev The document revision * @throws NoDocumentException If the document could not be found in the database. * @return {@link Response} */ public Response remove(String id, String rev) { assertNotEmpty(id, "id"); assertNotEmpty(rev, "revision"); return delete(builder(getDBUri()).path(id).query("rev", rev).build()); } /** * {@inheritDoc} */ @Override public URI getDBUri() { return super.getDBUri(); } /** * {@inheritDoc} */ @Override public void shutdown() { super.shutdown(); } }