com.cloudant.client.api.CloudantClient.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudant.client.api.CloudantClient.java

Source

/*
 * Copyright (c) 2015 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.close;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.createPost;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.getResponseList;
import static com.cloudant.client.org.lightcouch.internal.URIBuilder.buildUri;

import com.cloudant.client.api.model.ApiKey;
import com.cloudant.client.api.model.ConnectOptions;
import com.cloudant.client.api.model.Index;
import com.cloudant.client.api.model.IndexField;
import com.cloudant.client.api.model.Membership;
import com.cloudant.client.api.model.Permissions;
import com.cloudant.client.api.model.Shard;
import com.cloudant.client.api.model.Task;
import com.cloudant.client.api.views.Key;
import com.cloudant.client.org.lightcouch.Changes;
import com.cloudant.client.org.lightcouch.CouchDbClient;
import com.cloudant.client.org.lightcouch.CouchDbDesign;
import com.cloudant.client.org.lightcouch.CouchDbProperties;
import com.cloudant.client.org.lightcouch.Replication;
import com.cloudant.client.org.lightcouch.Replicator;
import com.cloudant.client.org.lightcouch.Response;
import com.cloudant.client.org.lightcouch.internal.CouchDbUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Exposes the Cloudant client API
 * <p>This class is the main object to use to gain access to the Cloudant APIs.
 * <h1>Usage Examples:</h1>
 *
 * <h2>Create a new Cloudant client instance</h2>
 * <code>
 * CloudantClient client = new CloudantClient("mycloudantaccount","myusername","mypassword");
 * </code>
 *
 * <h2>Client use of the API</h2>
 * <ul>
 * <li>Server APIs accessed by the client directly e.g.:
 * <p>
 * {@link CloudantClient#getAllDbs() client.getAllDbs()}
 * </p>
 * </li>
 * <li>DB is accessed by getting the {@link Database} from the client e.g.:
 * <p>
 * <code>Database db = client.database("customers",false);</code>
 * </p>
 * </li>
 * <li>Document <code>CRUD</code> APIs accessed from the {@link Database} e.g.:
 * <p>
 * {@link Database#find(Class, String) db.find(Foo.class, "doc-id")}
 * </p>
 * </li>
 * </ul>
 *
 * <h2>Cloudant Query</h2>
 * <ul>
 * <li>Create an index
 * {@link Database#createIndex(String, String, String, IndexField[])} e.g.:
 * <p>
 * <code>
 * db.createIndex("Person_name", "Person_name_design_doc", "json", new IndexField[] { new IndexField
 * ("Person_name",SortOrder.asc)})
 * </code>
 * </p>
 * </li>
 * <li>Find using an index
 * {@link Database#findByIndex(String, Class)} e.g.:
 * <p>
 * <code>
 * db.findByIndex(" "selector": {
 * "Person_name": "Alec Guinness" }", Movie.class)}
 * </code>
 * </p>
 * </li>
 * <li>Delete an index
 * {@link Database#deleteIndex(String, String)} e.g.:
 * <p>
 * <code>
 * db.deleteIndex("Person_name", "Person_name_design_doc")
 * </code>
 * </p>
 * </li>
 * </ul>
 *
 * <h2>Cloudant Search</h2>
 * {@link Search db.search("views101/animals)}
 *
 * <h2>View APIs</h2>
 * {@link com.cloudant.client.api.views}
 *
 * <h2>Change Notifications</h2>
 * {@link Changes db.changes()}
 *
 * <h2>Design Documents</h2>
 * {@link CouchDbDesign db.design()}
 *
 * <h2>Replication</h2>
 * Replication {@link Replication account.replication()} and {@link Replicator account.replicator()}
 *
 * <h2>Shutting down</h2>
 * At the end of a client usage; it's useful to call {@link #shutdown() client.shutdown()} to
 * ensure proper release of resources.
 *
 * @author Mario Briggs
 * @since 0.0.1
 */
public class CloudantClient {

    private CouchDbClient client;

    private String accountName;
    private String loginUsername;
    private String password;

    /**
     * Constructs a new instance of this class and connects to the cloudant server with the
     * specified credentials
     *
     * @param account       For cloudant.com, the cloudant account to connect to. For Cloudant
     *                      local, the server URL
     * @param loginUsername The apiKey (if using an APIKey, else pass in the account for this
     *                      parameter also)
     * @param password      The Password credential
     */
    public CloudantClient(String account, String loginUsername, String password) {
        super();
        Map<String, String> h = parseAccount(account);
        this.loginUsername = loginUsername;
        this.password = password;

        doInit(h.get("scheme"), h.get("hostname"), new Integer(h.get("port")).intValue(), loginUsername, password,
                null, //connectoptions
                null //authcookie
        );
    }

    /**
     * Constructs a new instance of this class and connects to the cloudant account with the
     * specified credentials
     *
     * @param account        For cloudant.com, the cloudant account to connect to. For Cloudant
     *                       local, the server URL
     * @param loginUsername  The apiKey (if using an APIKey, else pass in the account for this
     *                       parameter also)
     * @param password       The Password credential
     * @param connectOptions optional properties to connect e.g connectionTime,socketTimeout,etc
     */
    public CloudantClient(String account, String loginUsername, String password, ConnectOptions connectOptions) {
        super();
        Map<String, String> h = parseAccount(account);
        this.loginUsername = loginUsername;
        this.password = password;

        doInit(h.get("scheme"), h.get("hostname"), new Integer(h.get("port")).intValue(), loginUsername, password,
                connectOptions, null //authcookie
        );
    }

    /**
     * Constructs a new instance of this class and connects to the cloudant account with the
     * specified credentials
     *
     * @param account    For cloudant.com, the cloudant account to connect to. For Cloudant
     *                   local, the server URL
     * @param authCookie The cookie obtained from last login
     */
    public CloudantClient(String account, String authCookie) {
        super();
        Map<String, String> h = parseAccount(account);
        assertNotEmpty(authCookie, "AuthCookie");

        doInit(h.get("scheme"), h.get("hostname"), new Integer(h.get("port")).intValue(), null, null, null,
                authCookie);
    }

    /**
     * Constructs a new instance of this class and connects to the cloudant account with the
     * specified credentials
     *
     * @param account        For cloudant.com, the cloudant account to connect to. For Cloudant
     *                       local, the server URL
     * @param account        The cloudant account to connect to
     * @param authCookie     The cookie obtained from last login
     * @param connectOptions optional properties to connect e.g connectionTime,socketTimeout,etc
     */
    public CloudantClient(String account, String authCookie, ConnectOptions connectOptions) {
        super();
        Map<String, String> h = parseAccount(account);
        assertNotEmpty(authCookie, "AuthCookie");

        doInit(h.get("scheme"), h.get("hostname"), new Integer(h.get("port")).intValue(), null, null,
                connectOptions, authCookie);
    }

    /**
     * Generate an API key
     *
     * @return the generated key and password
     */
    public ApiKey generateApiKey() {
        URI uri = buildUri(getBaseUri()).path("_api/v2/api_keys").build();
        return client.executeRequest(createPost(uri, "", ""), ApiKey.class);
    }

    /**
     * Get all active tasks
     *
     * @return List of tasks
     */
    public List<Task> getActiveTasks() {
        HttpResponse response = null;
        HttpGet get = new HttpGet(buildUri(getBaseUri()).path("/_active_tasks").build());
        try {
            response = executeRequest(get);
            return getResponseList(response, client.getGson(), Task.class, new TypeToken<List<Task>>() {
            }.getType());
        } finally {
            close(response);
        }
    }

    /**
     * Get the cookieStore
     *
     * @return cookieStore
     */
    public String getCookie() {
        return client.getCookie();
    }

    /**
     * Get the list of nodes in a cluster
     *
     * @return cluster nodes and all nodes
     */
    public Membership getMembership() {
        Membership membership = client.get(buildUri(getBaseUri()).path("/_membership").build(), Membership.class);
        return membership;
    }

    /**
     * Get a database
     *
     * @param name   name of database to access
     * @param create flag indicating whether to create the database if it does not exist.
     * @return Database object
     */
    public Database database(String name, boolean create) {
        return new Database(this, client.database(name, create));
    }

    /**
     * Request to  delete a database.
     *
     * @param dbName  The database name
     * @param confirm A confirmation string with the value: <tt>delete database</tt>
     * @deprecated use {@link CloudantClient#deleteDB(String)}
     */
    @Deprecated
    public void deleteDB(String dbName, String confirm) {
        client.deleteDB(dbName, confirm);
    }

    /**
     * Request to  delete a database.
     *
     * @param dbName The database name
     */
    public void deleteDB(String dbName) {
        client.deleteDB(dbName);
    }

    /**
     * Request to create a new database; if one doesn't exist.
     *
     * @param dbName The Database name
     */
    public void createDB(String dbName) {
        client.createDB(dbName);
    }

    /**
     * @return The base URI.
     */
    public URI getBaseUri() {
        return client.getBaseUri();
    }

    /**
     * @return All Server databases.
     */
    public List<String> getAllDbs() {
        return client.getAllDbs();
    }

    /**
     * @return Cloudant Server version.
     */
    public String serverVersion() {
        return client.serverVersion();
    }

    /**
     * Provides access to Cloudant <tt>replication</tt> APIs.
     *
     * @see Replication
     */
    public com.cloudant.client.api.Replication replication() {
        Replication couchDbReplication = client.replication();
        com.cloudant.client.api.Replication replication = new com.cloudant.client.api.Replication(
                couchDbReplication);
        return replication;
    }

    /**
     * Provides access to Cloudant <tt>replication</tt> APIs.
     *
     * @see Replication
     */
    public com.cloudant.client.api.Replicator replicator() {
        Replicator couchDbReplicator = client.replicator();
        com.cloudant.client.api.Replicator replicator = new com.cloudant.client.api.Replicator(couchDbReplicator);
        return replicator;
    }

    /**
     * Executes a HTTP request. This method provides a mechanism to extend the API
     * <p><b>Note</b>: The response must be closed after use to release the connection.
     *
     * @param request The HTTP request to execute.
     * @return {@link HttpResponse}
     */
    public HttpResponse executeRequest(HttpRequestBase request) {

        HttpResponse response = client.executeRequest(request);
        return response;

    }

    /**
     * Shuts down the connection manager used by this client instance.
     */
    public void shutdown() {
        client.shutdown();
    }

    /**
     * Request cloudant to send a list of UUIDs.
     *
     * @param count The count of UUIDs.
     */
    public List<String> uuids(long count) {
        return client.uuids(count);
    }

    /**
     * Sets a {@link GsonBuilder} to create {@link Gson} instance.
     * <p>Useful for registering custom serializers/deserializers, such as Datetime formats.
     */
    public void setGsonBuilder(GsonBuilder gsonBuilder) {
        //register additional cloudant deserializers and then let lightcouch init too
        gsonBuilder.registerTypeAdapter(new TypeToken<List<Shard>>() {
        }.getType(), new ShardDeserializer()).registerTypeAdapter(new TypeToken<List<Index>>() {
        }.getType(), new IndexDeserializer())
                .registerTypeAdapter(new TypeToken<Map<String, EnumSet<Permissions>>>() {
                }.getType(), new SecurityDeserializer())
                .registerTypeAdapter(Key.ComplexKey.class, new Key.ComplexKeyDeserializer());
        client.setGsonBuilder(gsonBuilder);
    }

    /**
     * @return The Gson instance.
     */
    public Gson getGson() {
        return client.getGson();
    }

    // Helper methods

    String getLoginUsername() {
        return loginUsername;
    }

    String getPassword() {
        return password;
    }

    /**
     * @return the accountName
     */
    String getAccountName() {
        return accountName;
    }

    /**
     * Performs a HTTP GET request.
     *
     * @return An object of type T
     */
    <T> T get(URI uri, Class<T> classType) {
        return client.get(uri, classType);
    }

    Response put(URI uri, Object object, boolean newEntity, int writeQuorum, Gson gson) {
        assertNotEmpty(object, "object");
        HttpResponse response = null;
        try {
            final JsonObject json = gson.toJsonTree(object).getAsJsonObject();
            String id = CouchDbUtil.getAsString(json, "_id");
            String rev = CouchDbUtil.getAsString(json, "_rev");
            if (newEntity) { // save
                CouchDbUtil.assertNull(rev, "rev");
                id = (id == null) ? CouchDbUtil.generateUUID() : id;
            } else { // update
                assertNotEmpty(id, "id");
                assertNotEmpty(rev, "rev");
            }
            final HttpPut put = new HttpPut(buildUri(uri).pathToEncode(id).query("w", writeQuorum).buildEncoded());
            CouchDbUtil.setEntity(put, json.toString(), "application/json");
            response = client.executeRequest(put);
            return CouchDbUtil.getResponse(response, Response.class, gson);
        } finally {
            close(response);
        }
    }

    private Map<String, String> parseAccount(String account) {
        assertNotEmpty(account, "accountName");
        this.accountName = account;
        Map<String, String> h = new HashMap<String, String>();
        if (account.startsWith("http://") || account.startsWith("https://")) {
            // user is specifying a uri
            try {
                URI uri = new URI(account);
                uri.getPort();
                h.put("scheme", uri.getScheme());
                h.put("hostname", uri.getHost());
                h.put("port", new Integer(uri.getPort()).toString());
            } catch (URISyntaxException e) {

            }
        } else {
            h.put("scheme", "https");
            h.put("hostname", account + ".cloudant.com");
            h.put("port", "443");
        }
        return h;
    }

    /**
     * Initializes the internal lightCouch client
     *
     * @param scheme
     * @param hostname
     * @param port
     * @param loginUsername
     * @param password
     * @param connectOptions
     * @param authCookie
     */
    private void doInit(String scheme, String hostname, int port, String loginUsername, String password,
            ConnectOptions connectOptions, String authCookie) {

        CouchDbProperties props;
        if (authCookie == null) {
            props = new CouchDbProperties(scheme, hostname, port, loginUsername, password);
        } else {
            props = new CouchDbProperties(scheme, hostname, port, authCookie);
        }

        if (connectOptions != null) {
            props.setConnectionTimeout(connectOptions.getConnectionTimeout());
            props.setSocketTimeout(connectOptions.getSocketTimeout());
            props.setMaxConnections(connectOptions.getMaxConnections());

            props.setProxyHost(connectOptions.getProxyHost());
            props.setProxyPort(connectOptions.getProxyPort());
            props.disableSSLAuthentication(connectOptions.isSSLAuthenticationDisabled());
            props.setAuthenticatedModeSSLSocketFactory(connectOptions.getAuthenticatedModeSSLSocketFactory());
        }
        this.client = new CouchDbClient(props);

        // set a custom gsonbuilder that includes additional cloudant deserializers
        setGsonBuilder(new GsonBuilder());
    }
}