org.sentilo.common.rest.impl.RESTClientImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.sentilo.common.rest.impl.RESTClientImpl.java

Source

/*
 * Sentilo
 * 
 * Copyright (C) 2013 Institut Municipal dInformtica, Ajuntament de Barcelona.
 * 
 * This program is licensed and may be used, modified and redistributed under the terms of the
 * European Public License (EUPL), either version 1.1 or (at your option) any later version as soon
 * as they are approved by the European Commission.
 * 
 * Alternatively, you may redistribute and/or modify this program under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 * 
 * 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 licenses for the specific language governing permissions, limitations and more details.
 * 
 * You should have received a copy of the EUPL1.1 and the LGPLv3 licenses along with this program;
 * if not, you may find them at:
 * 
 * https://joinup.ec.europa.eu/software/page/eupl/licence-eupl http://www.gnu.org/licenses/ and
 * https://www.gnu.org/licenses/lgpl.txt
 */
package org.sentilo.common.rest.impl;

import java.security.GeneralSecurityException;

import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
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.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.util.EntityUtils;
import org.sentilo.common.exception.RESTClientException;
import org.sentilo.common.rest.RESTClient;
import org.sentilo.common.rest.RequestParameters;
import org.sentilo.common.rest.hmac.HMACBuilder;
import org.sentilo.common.utils.DateUtils;
import org.sentilo.common.utils.RESTUtils;
import org.sentilo.common.utils.SentiloConstants;
import org.sentilo.common.utils.URIUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;

public class RESTClientImpl implements RESTClient, InitializingBean {

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

    private static final int DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS = (60 * 1000);
    private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
    private static final int DEFAULT_SUCCESS_HTTP_CODE = HttpStatus.SC_OK;

    private HttpClient httpClient;
    private Credentials credentials;
    private final AuthScope authScope = AuthScope.ANY;
    private HttpRequestInterceptor[] interceptors;

    private String host;
    private String secretKey;
    private int successHttpCode = DEFAULT_SUCCESS_HTTP_CODE;

    public RESTClientImpl() {
    }

    public String get(final String path) throws RESTClientException {
        return get(path, (String) null);
    }

    public String get(final String path, final String identityToken) throws RESTClientException {
        return get(path, (RequestParameters) null, identityToken);
    }

    public String get(final String path, final RequestParameters parameters) throws RESTClientException {
        return get(path, parameters, (String) null);
    }

    public String get(final String path, final RequestParameters parameters, final String identityToken)
            throws RESTClientException {

        final String url = URIUtils.getURI(host, path, parameters);
        final HttpGet get = new HttpGet(url);

        return executeHttpCall(get, identityToken);
    }

    public String post(final String path, final String body) throws RESTClientException {
        return post(path, body, null);
    }

    public String post(final String path, final String body, final String identityToken)
            throws RESTClientException {
        logger.debug("Send post message to host {} and path {}", host, path);
        final String url = URIUtils.getURI(host, path);
        final HttpPost post = new HttpPost(url);
        logger.debug("Token {}", identityToken);
        logger.debug("Body {}", body);
        return executeHttpCall(post, body, identityToken);
    }

    public String put(final String path, final String body) throws RESTClientException {
        return put(path, body, null);
    }

    public String put(final String path, final String body, final String identityToken) throws RESTClientException {
        final String url = URIUtils.getURI(host, path);
        final HttpPut put = new HttpPut(url);

        return executeHttpCall(put, body, identityToken);
    }

    public String delete(final String path) throws RESTClientException {
        return delete(path, null);
    }

    public String delete(final String path, final String identityToken) throws RESTClientException {
        return delete(path, identityToken, null);
    }

    public String delete(final String path, final String body, final String identityToken)
            throws RESTClientException {

        // As a request DELETE cannot have body, we simulate the call to DELETE doing a PUT request with
        // the parameter method==delete
        final String url = (StringUtils.hasText(body) ? URIUtils.getURI(host, path, RequestParameters.buildDelete())
                : URIUtils.getURI(host, path));
        final HttpRequestBase delete = (StringUtils.hasText(body) ? new HttpPut(url) : new HttpDelete(url));

        return executeHttpCall(delete, body, identityToken);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (httpClient == null) {
            final PoolingClientConnectionManager pccm = new PoolingClientConnectionManager();
            // Increase max total connection to 400
            pccm.setMaxTotal(400);
            // Increase default max connection per route to 50
            pccm.setDefaultMaxPerRoute(50);

            httpClient = new DefaultHttpClient(pccm);
            // Set the timeouts for read the response and create a connection
            setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS);
            setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
        }

        if (interceptors != null && httpClient instanceof DefaultHttpClient) {
            for (final HttpRequestInterceptor interceptor : interceptors) {
                ((DefaultHttpClient) httpClient).addRequestInterceptor(interceptor);
            }
        }

        if (credentials != null && httpClient instanceof DefaultHttpClient) {
            ((DefaultHttpClient) httpClient).getCredentialsProvider().setCredentials(authScope, credentials);

        }
    }

    public void destroy() throws Exception {
        // As recommended by HttpClient API, when the client is destroyed the related connectionManager
        // must be closed
        httpClient.getConnectionManager().shutdown();
    }

    private void validateResponse(final HttpResponse response) throws RESTClientException {
        if (response.getStatusLine().getStatusCode() != successHttpCode) {
            final StatusLine line = response.getStatusLine();
            final StringBuilder sb = new StringBuilder();
            try {
                if (response.getEntity() != null) {
                    sb.append(EntityUtils.toString(response.getEntity()));
                } else {
                    sb.append(line.getReasonPhrase());
                }
            } catch (final Exception e) {
                // Ignored
            }
            throw new RESTClientException(line.getStatusCode(), sb.toString());
        }
    }

    private String executeHttpCall(final HttpRequestBase httpRequest, final String identityToken)
            throws RESTClientException {
        return executeHttpCall(httpRequest, null, identityToken);
    }

    private String executeHttpCall(final HttpRequestBase httpRequest, final String body, final String identityToken)
            throws RESTClientException {
        try {
            logger.debug("Executing http call {} ", httpRequest.toString());
            if (StringUtils.hasText(body)) {
                ((HttpEntityEnclosingRequestBase) httpRequest)
                        .setEntity(new StringEntity(body, ContentType.APPLICATION_JSON));
            }

            if (StringUtils.hasText(identityToken)) {
                httpRequest.addHeader(RESTUtils.buildIdentityHeader(identityToken));
            }

            if (StringUtils.hasText(secretKey)) {
                addSignedHeader(httpRequest, body);
            }

            final HttpResponse response = httpClient.execute(httpRequest);
            validateResponse(response);
            return EntityUtils.toString(response.getEntity());
        } catch (final RESTClientException e) {
            throw e;
        } catch (final Exception e) {
            final String msg = String.format("Error while executing http call: %s ", httpRequest.toString());
            throw new RESTClientException(msg, e);
        }
    }

    private void addSignedHeader(final HttpRequestBase httpRequest, final String body)
            throws GeneralSecurityException {
        final String currentDate = DateUtils.timestampToString(System.currentTimeMillis());
        final String hmac = HMACBuilder.buildHeader(body, host, secretKey, currentDate);
        httpRequest.addHeader(SentiloConstants.HMAC_HEADER, hmac);
        httpRequest.addHeader(SentiloConstants.DATE_HEADER, currentDate);
        logger.debug("Add header {} with value {}", SentiloConstants.HMAC_HEADER, hmac);
        logger.debug("Add header {} with value {}", SentiloConstants.DATE_HEADER, currentDate);
    }

    /**
     * Sets the timeout until a connection is established. A value of 0 means <em>never</em> timeout.
     * 
     * @param timeout the timeout value in milliseconds
     * @see org.apache.http.params.HttpConnectionParams#setConnectionTimeout(org.apache.http.params.HttpParams,
     *      int)
     */
    public void setConnectionTimeout(final int timeout) {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout must be a non-negative value");
        }
        HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), timeout);
    }

    /**
     * Set the socket read timeout for the underlying HttpClient. A value of 0 means <em>never</em>
     * timeout.
     * 
     * @param timeout the timeout value in milliseconds
     * @see org.apache.http.params.HttpConnectionParams#setSoTimeout(org.apache.http.params.HttpParams,
     *      int)
     */
    public void setReadTimeout(final int timeout) {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout must be a non-negative value");
        }
        HttpConnectionParams.setSoTimeout(httpClient.getParams(), timeout);
    }

    public void setHost(final String host) {
        this.host = host;
    }

    public void setHttpClient(final HttpClient httpClient) {
        this.httpClient = httpClient;
    }

    public void setInterceptors(final HttpRequestInterceptor[] interceptors) {
        this.interceptors = interceptors;
    }

    public void setCredentials(final Credentials credentials) {
        this.credentials = credentials;
    }

    public void setSecretKey(final String secretKey) {
        this.secretKey = secretKey;
    }

    public void setSuccessHttpCode(final int successHttpCode) {
        this.successHttpCode = successHttpCode;
    }

}