com.arangodb.http.HttpManager.java Source code

Java tutorial

Introduction

Here is the source code for com.arangodb.http.HttpManager.java

Source

/*
 * Copyright (C) 2012 tamtam180
 *
 * 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.arangodb.http;

import java.io.IOException;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
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.HttpPatch;
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.utils.URLEncodedUtils;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.arangodb.ArangoConfigure;
import com.arangodb.ArangoException;
import com.arangodb.http.HttpRequestEntity.RequestType;
import com.arangodb.util.IOUtils;

/**
 * @author tamtam180 - kirscheless at gmail.com
 * @author a-brandt
 * 
 */
public class HttpManager {

    private static final ContentType APPLICATION_JSON_UTF8 = ContentType.create("application/json", "utf-8");

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

    private PoolingHttpClientConnectionManager cm;
    private CloseableHttpClient client;

    private ArangoConfigure configure;

    private HttpResponseEntity preDefinedResponse;

    private HttpMode httpMode = HttpMode.SYNC;

    private List<String> jobIds = new ArrayList<String>();

    private Map<String, InvocationObject> jobs = new HashMap<String, InvocationObject>();

    public static enum HttpMode {
        SYNC, ASYNC, FIREANDFORGET
    }

    public HttpManager(ArangoConfigure configure) {
        this.configure = configure;
    }

    public ArangoConfigure getConfiguration() {
        return this.configure;
    }

    public void init() {
        // socket factory for HTTP
        ConnectionSocketFactory plainsf = new PlainConnectionSocketFactory();

        // socket factory for HTTPS
        SSLConnectionSocketFactory sslsf = null;
        if (configure.getSslContext() != null) {
            sslsf = new SSLConnectionSocketFactory(configure.getSslContext());
        } else {
            sslsf = new SSLConnectionSocketFactory(SSLContexts.createSystemDefault());
        }

        // register socket factories
        Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", plainsf).register("https", sslsf).build();

        // ConnectionManager
        cm = new PoolingHttpClientConnectionManager(r);
        cm.setDefaultMaxPerRoute(configure.getMaxPerConnection());
        cm.setMaxTotal(configure.getMaxTotalConnection());

        Builder custom = RequestConfig.custom();

        // RequestConfig
        if (configure.getConnectionTimeout() >= 0) {
            custom.setConnectTimeout(configure.getConnectionTimeout());
        }
        if (configure.getTimeout() >= 0) {
            custom.setConnectionRequestTimeout(configure.getTimeout());
            custom.setSocketTimeout(configure.getTimeout());
        }
        custom.setStaleConnectionCheckEnabled(configure.isStaleConnectionCheck());

        RequestConfig requestConfig = custom.build();

        HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig);
        builder.setConnectionManager(cm);

        // KeepAlive Strategy
        ConnectionKeepAliveStrategy keepAliveStrategy = new ConnectionKeepAliveStrategy() {

            @Override
            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                // Honor 'keep-alive' header
                HeaderElementIterator it = new BasicHeaderElementIterator(
                        response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while (it.hasNext()) {
                    HeaderElement he = it.nextElement();
                    String param = he.getName();
                    String value = he.getValue();
                    if (value != null && param.equalsIgnoreCase("timeout")) {
                        try {
                            return Long.parseLong(value) * 1000;
                        } catch (NumberFormatException ignore) {
                        }
                    }
                }
                // otherwise keep alive for 30 seconds
                return 30 * 1000;
            }

        };
        builder.setKeepAliveStrategy(keepAliveStrategy);

        // Retry Handler
        builder.setRetryHandler(new DefaultHttpRequestRetryHandler(configure.getRetryCount(), false));

        // Proxy
        if (configure.getProxyHost() != null && configure.getProxyPort() != 0) {
            HttpHost proxy = new HttpHost(configure.getProxyHost(), configure.getProxyPort(), "http");

            DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
            builder.setRoutePlanner(routePlanner);
        }

        // Client
        client = builder.build();

        // Basic Auth
        // if (configure.getUser() != null && configure.getPassword() != null) {
        // AuthScope scope = AuthScope.ANY; // TODO
        // this.credentials = new
        // UsernamePasswordCredentials(configure.getUser(),
        // configure.getPassword());
        // client.getCredentialsProvider().setCredentials(scope, credentials);
        // }

    }

    public void destroy() {
        if (cm != null) {
            cm.shutdown();
        }
        configure = null;
    }

    public HttpMode getHttpMode() {
        return httpMode;
    }

    public void setHttpMode(HttpMode httpMode) {
        this.httpMode = httpMode;
    }

    public HttpResponseEntity doGet(String url) throws ArangoException {
        return doGet(url, null);
    }

    public HttpResponseEntity doGet(String url, Map<String, Object> params) throws ArangoException {
        return doHeadGetDelete(RequestType.GET, url, null, params);
    }

    public HttpResponseEntity doGet(String url, Map<String, Object> headers, Map<String, Object> params)
            throws ArangoException {
        return doHeadGetDelete(RequestType.GET, url, headers, params);
    }

    public HttpResponseEntity doGet(String url, Map<String, Object> headers, Map<String, Object> params,
            String username, String password) throws ArangoException {
        return doHeadGetDelete(RequestType.GET, url, headers, params, username, password);
    }

    public HttpResponseEntity doHead(String url, Map<String, Object> params) throws ArangoException {
        return doHeadGetDelete(RequestType.HEAD, url, null, params);
    }

    public HttpResponseEntity doDelete(String url, Map<String, Object> params) throws ArangoException {
        return doHeadGetDelete(RequestType.DELETE, url, null, params);
    }

    public HttpResponseEntity doDelete(String url, Map<String, Object> headers, Map<String, Object> params)
            throws ArangoException {
        return doHeadGetDelete(RequestType.DELETE, url, headers, params);
    }

    public HttpResponseEntity doHeadGetDelete(RequestType type, String url, Map<String, Object> headers,
            Map<String, Object> params) throws ArangoException {
        return doHeadGetDelete(type, url, headers, params, null, null);
    }

    public HttpResponseEntity doHeadGetDelete(RequestType type, String url, Map<String, Object> headers,
            Map<String, Object> params, String username, String password) throws ArangoException {
        HttpRequestEntity requestEntity = new HttpRequestEntity();
        requestEntity.type = type;
        requestEntity.url = url;
        requestEntity.headers = headers;
        requestEntity.parameters = params;
        requestEntity.username = username;
        requestEntity.password = password;
        return execute(requestEntity);
    }

    public HttpResponseEntity doPost(String url, Map<String, Object> headers, Map<String, Object> params,
            String bodyText) throws ArangoException {
        return doPostPutPatch(RequestType.POST, url, headers, params, bodyText, null);
    }

    public HttpResponseEntity doPost(String url, Map<String, Object> params, String bodyText)
            throws ArangoException {
        return doPostPutPatch(RequestType.POST, url, null, params, bodyText, null);
    }

    public HttpResponseEntity doPost(String url, Map<String, Object> params, HttpEntity entity)
            throws ArangoException {
        return doPostPutPatch(RequestType.POST, url, null, params, null, entity);
    }

    public HttpResponseEntity doPostWithHeaders(String url, Map<String, Object> params, HttpEntity entity,
            Map<String, Object> headers, String body) throws ArangoException {
        return doPostPutPatch(RequestType.POST, url, headers, params, body, entity);
    }

    public HttpResponseEntity doPut(String url, Map<String, Object> headers, Map<String, Object> params,
            String bodyText) throws ArangoException {
        return doPostPutPatch(RequestType.PUT, url, headers, params, bodyText, null);
    }

    public HttpResponseEntity doPut(String url, Map<String, Object> params, String bodyText)
            throws ArangoException {
        return doPostPutPatch(RequestType.PUT, url, null, params, bodyText, null);
    }

    public HttpResponseEntity doPatch(String url, Map<String, Object> headers, Map<String, Object> params,
            String bodyText) throws ArangoException {
        return doPostPutPatch(RequestType.PATCH, url, headers, params, bodyText, null);
    }

    public HttpResponseEntity doPatch(String url, Map<String, Object> params, String bodyText)
            throws ArangoException {
        return doPostPutPatch(RequestType.PATCH, url, null, params, bodyText, null);
    }

    private HttpResponseEntity doPostPutPatch(RequestType type, String url, Map<String, Object> headers,
            Map<String, Object> params, String bodyText, HttpEntity entity) throws ArangoException {
        HttpRequestEntity requestEntity = new HttpRequestEntity();
        requestEntity.type = type;
        requestEntity.url = url;
        requestEntity.headers = headers;
        requestEntity.parameters = params;
        requestEntity.bodyText = bodyText;
        requestEntity.entity = entity;
        return execute(requestEntity);
    }

    /**
     * Executes the request and handles connect exceptions
     * 
     * @param requestEntity
     *            the request
     * @return the response of the request
     * 
     * @throws ArangoException
     */
    public HttpResponseEntity execute(HttpRequestEntity requestEntity) throws ArangoException {
        int retries = 0;
        int connectRetryCount = configure.getConnectRetryCount();

        while (true) {
            try {
                return executeInternal(configure.getBaseUrl(), requestEntity);
            } catch (SocketException ex) {
                retries++;
                if (connectRetryCount > 0 && retries > connectRetryCount) {
                    logger.error(ex.getMessage(), ex);
                    throw new ArangoException(ex);
                }

                if (configure.hasFallbackHost()) {
                    configure.changeCurrentHost();
                }

                logger.warn(ex.getMessage(), ex);
                try {
                    // 1000 milliseconds is one second.
                    Thread.sleep(configure.getConnectRetryWait());
                } catch (InterruptedException iex) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    /**
     * Executes the request
     * 
     * @param requestEntity
     *            the request
     * @return the response of the request
     * @throws ArangoException
     */
    private HttpResponseEntity executeInternal(String baseUrl, HttpRequestEntity requestEntity)
            throws ArangoException, SocketException {

        String url = buildUrl(baseUrl, requestEntity);

        if (logger.isDebugEnabled()) {
            if (requestEntity.type == RequestType.POST || requestEntity.type == RequestType.PUT
                    || requestEntity.type == RequestType.PATCH) {
                logger.debug("[REQ]http-{}: url={}, headers={}, body={}",
                        new Object[] { requestEntity.type, url, requestEntity.headers, requestEntity.bodyText });
            } else {
                logger.debug("[REQ]http-{}: url={}, headers={}",
                        new Object[] { requestEntity.type, url, requestEntity.headers });
            }
        }

        HttpRequestBase request = null;
        switch (requestEntity.type) {
        case GET:
            request = new HttpGet(url);
            break;
        case POST:
            HttpPost post = new HttpPost(url);
            configureBodyParams(requestEntity, post);
            request = post;
            break;
        case PUT:
            HttpPut put = new HttpPut(url);
            configureBodyParams(requestEntity, put);
            request = put;
            break;
        case PATCH:
            HttpPatch patch = new HttpPatch(url);
            configureBodyParams(requestEntity, patch);
            request = patch;
            break;
        case HEAD:
            request = new HttpHead(url);
            break;
        case DELETE:
            request = new HttpDelete(url);
            break;
        }

        // common-header
        String userAgent = "Mozilla/5.0 (compatible; ArangoDB-JavaDriver/1.1; +http://mt.orz.at/)";
        request.setHeader("User-Agent", userAgent);

        // optinal-headers
        if (requestEntity.headers != null) {
            for (Entry<String, Object> keyValue : requestEntity.headers.entrySet()) {
                request.setHeader(keyValue.getKey(), keyValue.getValue().toString());
            }
        }

        // Basic Auth
        Credentials credentials = null;
        if (requestEntity.username != null && requestEntity.password != null) {
            credentials = new UsernamePasswordCredentials(requestEntity.username, requestEntity.password);
        } else if (configure.getUser() != null && configure.getPassword() != null) {
            credentials = new UsernamePasswordCredentials(configure.getUser(), configure.getPassword());
        }
        if (credentials != null) {
            BasicScheme basicScheme = new BasicScheme();
            try {
                request.addHeader(basicScheme.authenticate(credentials, request, null));
            } catch (AuthenticationException e) {
                throw new ArangoException(e);
            }
        }

        if (this.getHttpMode().equals(HttpMode.ASYNC)) {
            request.addHeader("x-arango-async", "store");
        } else if (this.getHttpMode().equals(HttpMode.FIREANDFORGET)) {
            request.addHeader("x-arango-async", "true");
        }
        // CURL/httpie Logger
        if (configure.isEnableCURLLogger()) {
            CURLLogger.log(url, requestEntity, userAgent, credentials);
        }
        HttpResponse response = null;
        if (preDefinedResponse != null) {
            return preDefinedResponse;
        }
        try {
            response = client.execute(request);
            if (response == null) {
                return null;
            }

            HttpResponseEntity responseEntity = new HttpResponseEntity();

            // http status
            StatusLine status = response.getStatusLine();
            responseEntity.statusCode = status.getStatusCode();
            responseEntity.statusPhrase = status.getReasonPhrase();

            logger.debug("[RES]http-{}: statusCode={}", requestEntity.type, responseEntity.statusCode);

            // ??
            // // TODO etag???
            Header etagHeader = response.getLastHeader("etag");
            if (etagHeader != null) {
                responseEntity.etag = Long.parseLong(etagHeader.getValue().replace("\"", ""));
            }
            // Map???
            responseEntity.headers = new TreeMap<String, String>();
            for (Header header : response.getAllHeaders()) {
                responseEntity.headers.put(header.getName(), header.getValue());
            }

            // ???
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                Header contentType = entity.getContentType();
                if (contentType != null) {
                    responseEntity.contentType = contentType.getValue();
                    if (responseEntity.isDumpResponse()) {
                        responseEntity.stream = entity.getContent();
                        logger.debug("[RES]http-{}: stream, {}", requestEntity.type, contentType.getValue());
                    }
                }
                // Close stream in this method.
                if (responseEntity.stream == null) {
                    responseEntity.text = IOUtils.toString(entity.getContent());
                    logger.debug("[RES]http-{}: text={}", requestEntity.type, responseEntity.text);
                }
            }

            if (this.getHttpMode().equals(HttpMode.ASYNC)) {
                Map<String, String> map = responseEntity.getHeaders();
                this.addJob(map.get("X-Arango-Async-Id"), this.getCurrentObject());
            } else if (this.getHttpMode().equals(HttpMode.FIREANDFORGET)) {
                return null;
            }

            return responseEntity;
        } catch (SocketException ex) {
            throw ex;
        } catch (ClientProtocolException e) {
            throw new ArangoException(e);
        } catch (IOException e) {
            throw new ArangoException(e);
        }
    }

    public static String buildUrl(String baseUrl, HttpRequestEntity requestEntity) {
        if (requestEntity.parameters != null && !requestEntity.parameters.isEmpty()) {
            String paramString = URLEncodedUtils.format(toList(requestEntity.parameters), "utf-8");
            if (requestEntity.url.contains("?")) {
                return baseUrl + requestEntity.url + "&" + paramString;
            } else {
                return baseUrl + requestEntity.url + "?" + paramString;
            }
        }
        return baseUrl + requestEntity.url;
    }

    private static List<NameValuePair> toList(Map<String, Object> parameters) {
        ArrayList<NameValuePair> paramList = new ArrayList<NameValuePair>(parameters.size());
        for (Entry<String, Object> param : parameters.entrySet()) {
            if (param.getValue() != null) {
                paramList.add(new BasicNameValuePair(param.getKey(), param.getValue().toString()));
            }
        }
        return paramList;
    }

    public static void configureBodyParams(HttpRequestEntity requestEntity,
            HttpEntityEnclosingRequestBase request) {

        if (requestEntity.entity != null) {
            request.setEntity(requestEntity.entity);
        } else if (requestEntity.bodyText != null) {
            request.setEntity(new StringEntity(requestEntity.bodyText, APPLICATION_JSON_UTF8));
        }

    }

    public static boolean is400Error(ArangoException e) {
        return e.getCode() == HttpStatus.SC_BAD_REQUEST;
    }

    public static boolean is404Error(ArangoException e) {
        return e.getCode() == HttpStatus.SC_NOT_FOUND;
    }

    public static boolean is412Error(ArangoException e) {
        return e.getCode() == HttpStatus.SC_PRECONDITION_FAILED;
    }

    public static boolean is200(HttpResponseEntity res) {
        return res.getStatusCode() == HttpStatus.SC_OK;
    }

    public static boolean is400Error(HttpResponseEntity res) {
        return res.getStatusCode() == HttpStatus.SC_BAD_REQUEST;
    }

    public static boolean is404Error(HttpResponseEntity res) {
        return res.getStatusCode() == HttpStatus.SC_NOT_FOUND;
    }

    public static boolean is412Error(HttpResponseEntity res) {
        return res.getStatusCode() == HttpStatus.SC_PRECONDITION_FAILED;
    }

    public CloseableHttpClient getClient() {
        return client;
    }

    public InvocationObject getCurrentObject() {
        return null;
    }

    public void setCurrentObject(InvocationObject currentObject) {
    }

    public void setPreDefinedResponse(HttpResponseEntity preDefinedResponse) {
        this.preDefinedResponse = preDefinedResponse;
    }

    public List<String> getJobIds() {
        return jobIds;
    }

    public Map<String, InvocationObject> getJobs() {
        return jobs;
    }

    public void addJob(String jobId, InvocationObject invocationObject) {
        jobIds.add(jobId);
        jobs.put(jobId, invocationObject);
    }

    public String getLastJobId() {
        return jobIds.size() == 0 ? null : jobIds.get(jobIds.size() - 1);
    }

    public void resetJobs() {
        this.jobIds = new ArrayList<String>();
        this.jobs.clear();

    }
}