com.jzboy.couchdb.http.CouchHttpClient.java Source code

Java tutorial

Introduction

Here is the source code for com.jzboy.couchdb.http.CouchHttpClient.java

Source

/*
 * Copyright (c) 2011. Elad Kehat.
 * This software is provided under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 */

package com.jzboy.couchdb.http;

import com.jzboy.couchdb.CouchDBException;
import com.jzboy.couchdb.util.JsonUtils;
import java.io.IOException;
import java.net.URI;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.codehaus.jackson.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class provides methods that generate HTTP-REST requests to CouchDB APIs.
 */
public class CouchHttpClient {

    private static final Logger logger = LoggerFactory.getLogger(CouchHttpClient.class);

    final HttpClient httpclient = new DefaultHttpClient();

    /**
     * Sends a GET request to the specified CouchDB endpoint and returns CouchDB's response as a JSON object.
     * Use this method on APIs that return a JSON String, where you want that JSON parsed for you.
     * @param uri   CouchDB API endpoint
     * @return the JSON tree parsed form the response body
     * @throws IOException      if the HttpClient throws an IOException
     * @throws CouchDBException   if CouchDB returns an error - response code >= 300. The response details are
     * encapsulated in the exception.
     */
    public JsonNode get(URI uri) throws IOException, CouchDBException {
        HttpGet req = new HttpGet(uri);
        return execRequest(req);
    }

    /**
     * Sends a GET request to the speficifed CouchDB endpoint and returns the response body as a String
     * @param uri   CouchDB API endpoint
     * @return the response body, as a String
     * @throws IOException      if the HttpClient throws an IOException
     * @throws CouchDBException   if CouchDB returns an error - response code >= 300. The response details are
     * encapsulated in the exception.
     */
    public String getBody(URI uri) throws IOException, CouchDBException {
        HttpGet req = new HttpGet(uri);
        CouchResponse res = httpclient.execute(req, new CouchResponseHandler());
        if (res.getStatusCode() >= 300) {
            throw new CouchDBException(res.getStatusCode(), res.getBody());
        }
        return res.getBody();
    }

    /**
     * Sends a GET request to the speficifed CouchDB endpoint and returns the raw HttpResponse object.
     * Make sure you consume the entire content in HttpResponse, so that the connection gets released.
     * @param uri   CouchDB API endpoint
     * @return the raw HttpRepsonse, untouched
     * @throws IOException      if the HttpClient throws an IOException
     */
    public HttpResponse getHttpResponse(URI uri) throws IOException {
        HttpGet req = new HttpGet(uri);
        return httpclient.execute(req);
    }

    /**
     * Sends a GET request to the speficifed CouchDB endpoint and returns a CouchResponse object.
     * Use this method if you want to avoid the default handling of errors and parsing to JSON.
     * @param uri   CouchDB API endpoint
     * @return the response, as a CouchResponse
     * @throws IOException      if the HttpClient throws an IOException
     */
    public CouchResponse getCouchResponse(URI uri) throws IOException {
        HttpGet req = new HttpGet(uri);
        return httpclient.execute(req, new CouchResponseHandler());
    }

    /**
     * Sends a POST request to the specified CouchDB endpoint.
     * @param uri   CouchDB API endpoint
     * @param json   request data
     * @return the JSON tree parsed form the response body
     * @throws IOException      if the HttpClient throws an IOException
     * @throws CouchDBException   if CouchDB returns an error - response code >= 300. The response details are
     * encapsulated in the exception.
     */
    public JsonNode post(URI uri, String json) throws IOException, CouchDBException {
        HttpPost req = new HttpPost(uri);
        req.addHeader("Content-Type", "application/json");
        if (json != null)
            req.setEntity(new StringEntity(json));
        return execRequest(req);
    }

    /**
     * Sends a POST request with no data to the specified CouchDB endpoint.
     * @param uri   CouchDB API endpoint
     * @return the JSON tree parsed form the response body
     * @throws IOException      if the HttpClient throws an IOException
     * @throws CouchDBException   if CouchDB returns an error - response code >= 300. The response details are
     * encapsulated in the exception.
     */
    public JsonNode post(URI uri) throws IOException, CouchDBException {
        return post(uri, null);
    }

    /**
     * Sends a PUT request to the specified CouchDB endpoint.
     * If the request fails due to a socket exception, e.g. the server refuses the request, it is retried several times.
     * @param uri   CouchDB API endpoint
     * @param data   request data
     * @param retry maximum number of retries if the request fails
     * @return the JSON tree parsed form the response body
     * @throws IOException      if the HttpClient throws an IOException
     * @throws CouchDBException   if CouchDB returns an error - response code >= 300. The response details are
     * encapsulated in the exception.
     */
    public JsonNode put(URI uri, String data, int retry) throws IOException, CouchDBException {
        HttpPut req = new HttpPut(uri);
        if (data != null)
            req.setEntity(new StringEntity(data));
        return execRequest(req, retry);
    }

    /**
     * Sends a PUT request to the specified CouchDB endpoint.
     * @param uri   CouchDB API endpoint
     * @param data   request data
     * @return the JSON tree parsed form the response body
     * @throws IOException      if the HttpClient throws an IOException
     * @throws CouchDBException   if CouchDB returns an error - response code >= 300. The response details are
     * encapsulated in the exception.
     */
    public JsonNode put(URI uri, String data) throws IOException, CouchDBException {
        HttpPut req = new HttpPut(uri);
        if (data != null)
            req.setEntity(new StringEntity(data));
        return execRequest(req);
    }

    /**
     * Sends a PUT request with no data to the specified CouchDB endpoint.
     * @param uri   CouchDB API endpoint
     * @return the JSON tree parsed form the response body
     * @throws IOException      if the HttpClient throws an IOException
     * @throws CouchDBException   if CouchDB returns an error - response code >= 300. The response details are
     * encapsulated in the exception.
     */
    public JsonNode put(URI uri) throws IOException, CouchDBException {
        return put(uri, null);
    }

    /**
     * Sends a PUT request to the specified CouchDB endpoint.
     * Use this version to add attachments.
     * @param uri      CouchDB API endpoint
     * @param data      request data
     * @param mimeType   the MIME type of the data. This is added as a Content-Type header to the request
     * @return the JSON tree parsed form the response body
     * @throws IOException      if the HttpClient throws an IOException
     * @throws CouchDBException   if CouchDB returns an error - response code >= 300. The response details are
     * encapsulated in the exception.
     */
    public JsonNode put(URI uri, byte[] data, String mimeType) throws IOException, CouchDBException {
        HttpPut req = new HttpPut(uri);
        req.setEntity(new ByteArrayEntity(data));
        req.addHeader("Content-Type", mimeType);
        return execRequest(req);
    }

    /**
     * Sends a DELETE request to the specified CouchDB endpoint.
     * @param uri   CouchDB API endpoint
     * @return the JSON tree parsed form the response body
     * @throws IOException      if the HttpClient throws an IOException
     * @throws CouchDBException   if CouchDB returns an error - response code >= 300. The response details are
     * encapsulated in the exception.
     */
    public JsonNode delete(URI uri) throws IOException, CouchDBException {
        HttpDelete req = new HttpDelete(uri);
        return execRequest(req);
    }

    private JsonNode execRequest(HttpRequestBase req, int retry) throws IOException, CouchDBException {
        int attemptsLeft = retry;
        CouchResponse res = null;
        while (res == null && attemptsLeft > 0) {
            try {
                res = httpclient.execute(req, new CouchResponseHandler());
            } catch (java.net.SocketException se) {
                logger.debug("Got {} on update attempt #{} on {}",
                        new Object[] { se.getMessage(), (3 - attemptsLeft + 1), req.getURI() });
                attemptsLeft--;
            }
        }
        if (res == null)
            throw new CouchDBException(
                    String.format("Operation failed after %d attempts: %s", retry, req.getURI()));
        throwOnError(res);
        return res.getBodyAsJson();
    }

    private JsonNode execRequest(HttpRequestBase req) throws IOException, CouchDBException {
        CouchResponse res = httpclient.execute(req, new CouchResponseHandler());
        throwOnError(res);
        return res.getBodyAsJson();
    }

    private void throwOnError(CouchResponse res) throws IOException, CouchDBException {
        if (res.getStatusCode() >= 300) {
            throw new CouchDBException(res.getStatusCode(), buildErrorMessageFromResponse(res));
        }
    }

    private static String buildErrorMessageFromResponse(final CouchResponse res) throws IOException {
        return new StringBuilder(JsonUtils.serializeError(res.getBodyAsJson())).append("(")
                .append(res.getStatusCode()).append(")").toString();
    }

}