org.lightcouch.CouchDbClientBase.java Source code

Java tutorial

Introduction

Here is the source code for org.lightcouch.CouchDbClientBase.java

Source

/*
 * 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.
 */

package org.lightcouch;

import static java.lang.String.format;
import static org.lightcouch.CouchDbUtil.assertNotEmpty;
import static org.lightcouch.CouchDbUtil.assertNull;
import static org.lightcouch.CouchDbUtil.close;
import static org.lightcouch.CouchDbUtil.generateUUID;
import static org.lightcouch.CouchDbUtil.getElement;
import static org.lightcouch.URIBuilder.builder;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
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.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
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.client.protocol.ClientContext;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SchemeSocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.apache.lucene.util.IOUtils;
import org.bbaw.bts.btsmodel.BTSDBBaseObject;
import org.bbaw.bts.btsmodel.BTSIdentifiableItem;
import org.bbaw.bts.commons.BTSConstants;
import org.bbaw.bts.modelUtils.EmfModelHelper;
import org.eclipse.emf.ecore.EObject;
import org.eclipselabs.emfjson.EMFJs;
import org.eclipselabs.emfjson.internal.JSONSave;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.reflect.TypeToken;

/**
 * Base client class to be extended by a concrete subclass, responsible for establishing
 * a connection with the database and the definition of the basic HTTP request handling and validation. 
 * @see CouchDbClient
 * @author Ahmed Yehia
 */
abstract class CouchDbClientBase {

    static final Log log = LogFactory.getLog(CouchDbClientBase.class);

    private HttpClient httpClient;
    private URI baseURI;
    private URI dbURI;
    private Gson gson;
    private CouchDbConfig config;

    private HttpHost host;
    private BasicHttpContext context;

    protected CouchDbClientBase() {
        this(new CouchDbConfig());
    }

    protected CouchDbClientBase(CouchDbConfig config) {
        CouchDbProperties props = config.getProperties();
        this.httpClient = createHttpClient(props);
        this.gson = initGson(new GsonBuilder());
        this.config = config;
        baseURI = builder().scheme(props.getProtocol()).host(props.getHost()).port(props.getPort()).path("/")
                .build();
        dbURI = builder(baseURI).path(props.getDbName()).path("/").build();
    }

    // ---------------------------------------------- Getters

    /**
     * @return The database URI.
     */
    protected URI getDBUri() {
        return dbURI;
    }

    /**
     * @return The base URI.
     */
    protected URI getBaseUri() {
        return baseURI;
    }

    /**
     * @return The Gson instance.
     */
    protected Gson getGson() {
        return gson;
    }

    protected CouchDbConfig getConfig() {
        return config;
    }

    // HTTP Requests

    /**
     * Performs a HTTP GET request. 
     * @return {@link InputStream} 
     */
    InputStream get(HttpGet httpGet) {
        HttpResponse response = executeRequest(httpGet);
        return getStream(response);
    }

    /**
     * Performs a HTTP GET request. 
     * @return {@link InputStream} 
     */
    InputStream get(URI uri) {
        HttpGet get = new HttpGet(uri);
        get.addHeader("Accept", "application/json");
        return get(get);
    }

    /**
     * Performs a HTTP GET request. 
     * @return An object of type T
     */
    <T> T get(URI uri, Class<T> classType) {
        InputStream instream = null;
        try {
            instream = get(uri);
            return deserialize(instream, classType);
        } finally {
            close(instream);
        }
    }

    /**
     * Performs a HTTP HEAD request. 
     * @return {@link HttpResponse}
     */
    HttpResponse head(URI uri) {
        return executeRequest(new HttpHead(uri));
    }

    /**
     * Performs a HTTP PUT request, saves or updates a document.
     * @return {@link Response}
     */
    public static String modelToString(Object object) {
        //      throw new UnsupportedOperationException();
        String string = null;
        Map options = new HashMap<Object, Object>();
        options.put(EMFJs.OPTION_INDENT_OUTPUT, false);
        JSONSave js = new JSONSave(options);
        if (object instanceof EObject) {
            EObject eo = (EObject) object;
            org.codehaus.jackson.JsonNode node = js.writeEObject(eo, eo.eResource());
            string = node.toString();
        } else {
            ByteArrayOutputStream os = new ByteArrayOutputStream();

            js.writeValue(os, object);

            try {
                string = os.toString(BTSConstants.ENCODING);
            } catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
        return string;
    }

    //cplutte integrated eObject handling
    Response put(URI uri, Object object, boolean newEntity) {
        assertNotEmpty(object, "object");
        HttpResponse response = null;
        try {
            String jsonString = null;
            String id = null;
            String rev = null;
            if (object instanceof BTSIdentifiableItem) {
                //            EObjectMapper mapper = getEObjectMapper();
                //            Resource resource = ((EObject) object).eResource();
                //            final JSONSave loader = new JSONSave(new HashMap<Object, Object>());
                //            loader.
                //            loader.fillResource(resource);
                //            ObjectNode node = mapper.to((EObject) object, resource);
                jsonString = modelToString(object);
                id = ((BTSIdentifiableItem) object).get_id();

                rev = ((BTSDBBaseObject) object).get_rev();
            } else {
                JsonObject json = getGson().toJsonTree(object).getAsJsonObject();
                id = getElement(json, "_id");
                rev = getElement(json, "_rev");

                jsonString = json.toString();
            }
            if (newEntity) { // save
                assertNull(rev, "revision");
                id = (id == null) ? generateUUID() : id;
            } else { // update
                assertNotEmpty(id, "id");
                assertNotEmpty(rev, "revision");
            }
            HttpPut put = new HttpPut(builder(uri).path(id).build());
            setEntity(put, jsonString);
            response = executeRequest(put);
            return getResponse(response);
        } finally {
            close(response);
        }
    }

    /**
     * Performs a HTTP PUT request, saves an attachment.
     * @return {@link Response}
     */
    Response put(URI uri, InputStream instream, String contentType) {
        HttpResponse response = null;
        try {
            HttpPut httpPut = new HttpPut(uri);
            InputStreamEntity entity = new InputStreamEntity(instream, -1);
            entity.setContentType(contentType);
            httpPut.setEntity(entity);
            response = executeRequest(httpPut);
            return getResponse(response);
        } finally {
            close(response);
        }
    }

    /**
     * Performs a HTTP POST request.
     * @return {@link HttpResponse}
     */
    HttpResponse post(URI uri, String json) {
        HttpPost post = new HttpPost(uri);
        setEntity(post, json);
        return executeRequest(post);
    }

    /**
     * Performs a HTTP DELETE request.
     * @return {@link Response}
     */
    Response delete(URI uri) {
        HttpResponse response = null;
        try {
            HttpDelete delete = new HttpDelete(uri);
            response = executeRequest(delete);
            return getResponse(response);
        } finally {
            close(response);
        }
    }

    /**
     * Executes a HTTP request.
     * @param request The HTTP request to execute.
     * @return {@link HttpResponse}
     */
    public HttpResponse executeRequest(HttpRequestBase request) {
        HttpResponse response = null;
        try {
            response = httpClient.execute(host, request, context);
        } catch (IOException e) {
            request.abort();
            log.error("Error executing request. " + e.getMessage());
            throw new CouchDbException(e);
        }
        return response;
    }

    // Helpers

    /**
     * @return {@link DefaultHttpClient} instance.
     */
    private HttpClient createHttpClient(CouchDbProperties props) {
        DefaultHttpClient httpclient = null;
        try {
            SchemeSocketFactory ssf = null;
            if (props.getProtocol().equals("https")) {
                TrustManager trustManager = new X509TrustManager() {
                    public void checkClientTrusted(X509Certificate[] chain, String authType)
                            throws CertificateException {
                    }

                    public void checkServerTrusted(X509Certificate[] chain, String authType)
                            throws CertificateException {
                    }

                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                };
                SSLContext sslcontext = SSLContext.getInstance("TLS");
                sslcontext.init(null, new TrustManager[] { trustManager }, null);
                ssf = new SSLSocketFactory(sslcontext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
                SSLSocket socket = (SSLSocket) ssf.createSocket(null);
                socket.setEnabledCipherSuites(new String[] { "SSL_RSA_WITH_RC4_128_MD5" });
            } else {
                ssf = PlainSocketFactory.getSocketFactory();
            }
            SchemeRegistry schemeRegistry = new SchemeRegistry();
            schemeRegistry.register(new Scheme(props.getProtocol(), props.getPort(), ssf));
            PoolingClientConnectionManager ccm = new PoolingClientConnectionManager(schemeRegistry);
            httpclient = new DefaultHttpClient(ccm);
            host = new HttpHost(props.getHost(), props.getPort(), props.getProtocol());
            context = new BasicHttpContext();
            // Http params
            httpclient.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, "UTF-8");
            httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, props.getSocketTimeout());
            httpclient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
                    props.getConnectionTimeout());
            int maxConnections = props.getMaxConnections();
            if (maxConnections != 0) {
                ccm.setMaxTotal(maxConnections);
                ccm.setDefaultMaxPerRoute(maxConnections);
            }
            if (props.getProxyHost() != null) {
                HttpHost proxy = new HttpHost(props.getProxyHost(), props.getProxyPort());
                httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
            }
            // basic authentication
            if (props.getUsername() != null && props.getPassword() != null) {
                httpclient.getCredentialsProvider().setCredentials(new AuthScope(props.getHost(), props.getPort()),
                        new UsernamePasswordCredentials(props.getUsername(), props.getPassword()));
                props.clearPassword();
                AuthCache authCache = new BasicAuthCache();
                BasicScheme basicAuth = new BasicScheme();
                authCache.put(host, basicAuth);
                context.setAttribute(ClientContext.AUTH_CACHE, authCache);
            }
            // request interceptor
            httpclient.addRequestInterceptor(new HttpRequestInterceptor() {
                public void process(final HttpRequest request, final HttpContext context) throws IOException {
                    if (log.isInfoEnabled())
                        log.info(">> " + request.getRequestLine());
                }
            });
            // response interceptor
            httpclient.addResponseInterceptor(new HttpResponseInterceptor() {
                public void process(final HttpResponse response, final HttpContext context) throws IOException {
                    validate(response);
                    if (log.isInfoEnabled())
                        log.info("<< Status: " + response.getStatusLine().getStatusCode());
                }
            });
        } catch (Exception e) {
            log.error("Error Creating HTTP client. " + e.getMessage());
            throw new IllegalStateException(e);
        }
        return httpclient;
    }

    /**
     * Validates a HTTP response; on error cases logs status and throws relevant exceptions.
     * @param response The HTTP response.
     */
    private void validate(HttpResponse response) throws IOException {
        int code = response.getStatusLine().getStatusCode();
        if (code == 200 || code == 201 || code == 202) { // success (ok | created | accepted)
            return;
        }
        String msg = format("<< Status: %s (%s) ", code, response.getStatusLine().getReasonPhrase());
        switch (code) {
        case HttpStatus.SC_NOT_FOUND: {
            log.info(msg);
            throw new NoDocumentException(msg);
        }
        case HttpStatus.SC_CONFLICT: {
            log.warn(msg);
            throw new DocumentConflictException(msg);
        }
        default: { // other errors: 400 | 401 | 500 etc.
            log.error(msg += EntityUtils.toString(response.getEntity()));
            throw new CouchDbException(msg);
        }
        }
    }

    /**
     * @param response The {@link HttpResponse}
     * @return {@link Response}
     */
    Response getResponse(HttpResponse response) throws CouchDbException {
        return deserialize(getStream(response), Response.class);
    }

    /**
     * @param response The {@link HttpResponse}
     * @return {@link Response}
     */
    List<Response> getResponseList(HttpResponse response) throws CouchDbException {
        InputStream instream = getStream(response);
        Reader reader = null;
        try {
            reader = new InputStreamReader(instream, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return getGson().fromJson(reader, new TypeToken<List<Response>>() {
        }.getType());
    }

    /**
     * Sets a JSON String as a request entity.
     * @param httpRequest The request to set entity.
     * @param json The JSON String to set.
     */
    protected void setEntity(HttpEntityEnclosingRequestBase httpRequest, String json) {
        try {
            StringEntity entity = new StringEntity(json, "UTF-8");
            entity.setContentType("application/json");
            httpRequest.setEntity(entity);
        } catch (UnsupportedEncodingException e) {
            log.error("Error setting request data. " + e.getMessage());
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * @return {@link InputStream} from a {@link HttpResponse}
     */
    InputStream getStream(HttpResponse response) {
        try {
            return response.getEntity().getContent();
        } catch (Exception e) {
            log.error("Error reading response. " + e.getMessage());
            throw new CouchDbException(e);
        }
    }

    //cplutte branch inserted
    <T> T deserialize(InputStream instream, Class<T> classType) {
        Reader reader = null;
        try {
            reader = new InputStreamReader(instream, "UTF-8");
        } catch (UnsupportedEncodingException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        //FIXME check dynamically eobject
        if (classType.isAssignableFrom(EObject.class)) {
            String asString = "";

            try {

                String UTF8 = "UTF-8"; //"utf8"; changed cplutte
                int BUFFER_SIZE = 8192;

                BufferedReader br = new BufferedReader(new InputStreamReader(instream, UTF8), BUFFER_SIZE);
                String str;
                while ((str = br.readLine()) != null) {
                    asString += str;
                }
            } catch (Exception e) {

            }
        }
        return getGson().fromJson(reader, classType);
    }

    /**
     * <p>The supplied {@link GsonBuilder} is used to create a new {@link Gson} instance.
     * Useful for registering custom serializers/deserializers, for example JodaTime DateTime class.
     */
    protected void setGsonBuilder(GsonBuilder gsonBuilder) {
        this.gson = initGson(gsonBuilder);
    }

    /**
     * Builds {@link Gson} and registers any required serializer/deserializer.
     * @return {@link Gson} instance
     */
    private Gson initGson(GsonBuilder gsonBuilder) {
        gsonBuilder.registerTypeAdapter(JsonObject.class, new JsonDeserializer<JsonObject>() {
            public JsonObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                    throws JsonParseException {
                return json.getAsJsonObject();
            }
        });
        gsonBuilder.registerTypeAdapter(JsonObject.class, new JsonSerializer<JsonObject>() {
            public JsonElement serialize(JsonObject src, Type typeOfSrc, JsonSerializationContext context) {
                return src.getAsJsonObject();
            }

        });
        return gsonBuilder.create();
    }

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

    //   private EObjectMapper getEObjectMapper() {
    //      EObjectMapper mapper = StaticAccessController.getContext().get(EObjectMapper.class);
    //      if (mapper == null)
    //      {
    //         mapper = new EObjectMapper();
    //         StaticAccessController.getContext().set(EObjectMapper.class, mapper);
    //      }
    //      return mapper;
    //   }
}