com.evrythng.java.wrapper.core.api.ApiCommand.java Source code

Java tutorial

Introduction

Here is the source code for com.evrythng.java.wrapper.core.api.ApiCommand.java

Source

/*
 * (c) Copyright Reserved EVRYTHNG Limited 2016. All rights reserved.
 * Use of this material is subject to license.
 * Copying and unauthorised use of this material strictly prohibited.
 */
package com.evrythng.java.wrapper.core.api;

import com.evrythng.java.wrapper.core.http.HttpMethodBuilder;
import com.evrythng.java.wrapper.core.http.HttpMethodBuilder.Method;
import com.evrythng.java.wrapper.core.http.HttpMethodBuilder.MethodBuilder;
import com.evrythng.java.wrapper.core.http.Status;
import com.evrythng.java.wrapper.exception.EvrythngClientException;
import com.evrythng.java.wrapper.exception.EvrythngException;
import com.evrythng.java.wrapper.util.LogUtils;
import com.evrythng.java.wrapper.util.URIBuilder;
import com.evrythng.thng.commons.config.ApiConfiguration;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.collections.map.MultiValueMap;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.net.URI;
import java.security.KeyStore;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Generic definition for API commands.
 */
public class ApiCommand<T> {

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

    private static final int DEFAULT_CONNECTION_TIMEOUT = 5000;
    private static final int DEFAULT_SOCKET_TIMEOUT = 30000;

    private MultiValueMap queryParams = new MultiValueMap();
    private Map<String, String> headers = new LinkedHashMap<>();
    private HttpParams httpParams = new BasicHttpParams();
    {
        HttpConnectionParams.setConnectionTimeout(httpParams, DEFAULT_CONNECTION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(httpParams, DEFAULT_SOCKET_TIMEOUT);
    }
    private MethodBuilder<?> methodBuilder;
    private URI uri;
    private Status responseStatus;
    private TypeReference<T> responseType;

    /**
     * Creates a new instance of {@link ApiCommand}.
     *
     * @param methodBuilder  the {@link MethodBuilder} used for creating the
     *                       request
     * @param uri            the {@link URI} holding the absolute URL
     * @param responseStatus the expected {@link HttpResponse} status
     * @param responseType   the native type to which the {@link HttpResponse} will be
     *                       mapped to
     */
    public ApiCommand(final MethodBuilder<?> methodBuilder, final URI uri, final Status responseStatus,
            final TypeReference<T> responseType) {

        this.methodBuilder = methodBuilder;
        this.uri = uri;
        this.responseStatus = responseStatus;
        this.responseType = responseType;
    }

    /**
     * Gets the expected response status.
     *
     * @return {@link Status} instance
     */
    public Status getExpectedResponseStatus() {

        return responseStatus;
    }

    /**
     * Gets the response type.
     *
     * @return {@link TypeReference} instance
     */
    public TypeReference<T> getResponseType() {

        return responseType;
    }

    /**
     * Gets the HTTP method.
     *
     * @return {@link Method} type
     */
    public Method getMethod() {

        return methodBuilder.getMethod();
    }

    /**
     * Executes the current command and maps the {@link HttpResponse} entity to
     * {@code T} specified by {@link ApiCommand#responseType}.
     *
     * @return the {@link HttpResponse} entity mapped to {@code T}
     * @see #execute(TypeReference)
     */
    public T execute() throws EvrythngException {

        return execute(responseType);
    }

    /**
     * Executes the current command and returns the {@link HttpResponse} entity
     * content as {@link String}.
     *
     * @return the {@link HttpResponse} entity content as {@link String}
     * @see #execute(TypeReference)
     */
    public String content() throws EvrythngException {

        return execute(new TypeReference<String>() {

        });
    }

    /**
     * Executes the current command and returns the native {@link HttpResponse}.
     *
     * @return the {@link HttpResponse} implied by the request
     * @see #execute(TypeReference)
     */
    public HttpResponse request() throws EvrythngException {

        return execute(new TypeReference<HttpResponse>() {

        });
    }

    /**
     * Executes the current command and returns the {@link HttpResponse} entity
     * body as {@link InputStream}.
     *
     * @return the {@link HttpResponse} entity as {@link InputStream}
     * @see #execute(TypeReference)
     */
    public InputStream stream() throws EvrythngException {

        return execute(new TypeReference<InputStream>() {

        });
    }

    /**
     * Execute the current command and returns both {@link HttpResponse} and
     * the entity typed. Bundled in a {@link TypedResponseWithEntity} object
     *
     * @return {@link HttpResponse} bundled with entity
     */
    public TypedResponseWithEntity<T> bundle() throws EvrythngException {

        HttpClient client = new DefaultHttpClient(httpParams);
        client = wrapClient(client);
        try {
            HttpResponse response = performRequest(client, methodBuilder, responseStatus);
            T entity = Utils.convert(response, responseType);
            return new TypedResponseWithEntity<>(response, entity);
        } finally {
            shutdown(client);
        }
    }

    /**
     * Executes the current command using the HTTP {@code HEAD} method and
     * returns the value of the first {@link HttpResponse} {@link Header}
     * specified by {@code headerName}. This
     * method is usefull for obtaining
     * metainformation about the {@link HttpResponse} implied by the request
     * without transferring the entity-body.
     * <p>
     * FIXME: HEAD not supported for now, using GET instead
     *
     * @param headerName the {@link HttpResponse} header to be retrieved
     * @return the value of the first retrieved {@link HttpResponse} header or
     * null if no such header could be found.
     * @see HttpResponse#getFirstHeader(String)
     */
    public Header head(final String headerName) throws EvrythngException {

        HttpResponse response = execute(HttpMethodBuilder.httpGet(), new TypeReference<HttpResponse>() {

        });
        logger.debug("Retrieving first header: [name={}]", headerName);
        return response.getFirstHeader(headerName);
    }

    /**
     * Sets (adds or overwrittes) the specified request header.
     *
     * @param name  the request header name
     * @param value the request header value
     */
    public void setHeader(final String name, final String value) {

        logger.debug("Setting header: [name={}, value={}]", name,
                ApiConfiguration.HTTP_HEADER_AUTHORIZATION.equals(name) ? LogUtils.maskApiKey(value) : value);
        headers.put(name, value);
    }

    /**
     * Removes the specified request header.
     *
     * @param name the name of the request header to be removed
     */
    public void removeHeader(final String name) {

        logger.debug("Removing header: [name={}]", name);
        headers.remove(name);
    }

    /**
     * Sets (adds or overwrittes) the specified query parameter.
     *
     * @param name  the query parameter name
     * @param value the query parameter value
     */
    public void setQueryParam(final String name, final String value) {
        // Ensure unicity of parameter:
        queryParams.remove(name);

        logger.debug("Setting query parameter: [name={}, value={}]", name, value);
        queryParams.put(name, value);
    }

    public void setQueryParam(final QueryParamValue queryParam) {

        // Ensure uniqueness of parameter:
        queryParams.remove(queryParam.getKey());

        logger.debug("Setting query parameter: [name={}, value={}]", queryParam.getKey(), queryParam.getValue());
        queryParams.put(queryParam.getKey(), queryParam.getValue());
    }

    /**
     * Sets (adds or overwrittes) the multi-value of specified query parameter.
     *
     * @param name  the query parameter name
     * @param value the query parameter values list
     */
    public void setQueryParam(final String name, final List<String> value) {

        logger.debug("Setting query parameter: [name={}, value={}]", name, value);
        queryParams.putAll(name, value);
    }

    /**
     * Removes the specified query parameter.
     *
     * @param name the name of the query parameter to be removed
     */
    public void removeQueryParam(final String name) {

        logger.debug("Removing query parameter: [name={}]", name);
        queryParams.remove(name);
    }

    /**
     * Sets HTTP-specific params, {
     *
     * @param params {@link HttpParams} instance
     * @see HttpClient
     */
    public void setHttpParams(final HttpParams params) {

        logger.debug("Setting HttpParams: [{}]", params);
        this.httpParams = params;
    }

    private <K> K execute(final TypeReference<K> type) throws EvrythngException {
        // Delegate:
        return execute(methodBuilder, type);
    }

    private <K> K execute(final MethodBuilder<?> method, final TypeReference<K> type) throws EvrythngException {
        // Delegate:
        return execute(method, responseStatus, type);
    }

    private <K> K execute(final MethodBuilder<?> method, final Status expectedStatus, final TypeReference<K> type)
            throws EvrythngException {

        HttpClient client = new DefaultHttpClient(httpParams);
        client = wrapClient(client);
        try {
            HttpResponse response = performRequest(client, method, expectedStatus);
            return Utils.convert(response, type);
        } finally {
            shutdown(client);
        }
    }

    private HttpResponse performRequest(final HttpClient client, final MethodBuilder<?> method,
            final Status expectedStatus) throws EvrythngException {

        HttpResponse response;
        HttpUriRequest request = buildRequest(method);
        try {
            logger.debug(">> Executing request: [method={}, url={}]", request.getMethod(),
                    request.getURI().toString());
            response = client.execute(request);
            logger.debug("<< Response received: [statusLine={}]", response.getStatusLine().toString());
        } catch (Exception e) {
            // Convert to custom exception:
            throw new EvrythngClientException(String.format("Unable to execute request: [uri=%s, cause=%s]",
                    request.getURI(), e.getMessage()), e);
        }

        // Assert response status:
        Utils.assertStatus(response, expectedStatus);

        return response;
    }

    private static HttpClient wrapClient(final HttpClient base) {

        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);
            SSLSocketFactory ssf = new WrapperSSLSocketFactory(trustStore);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = base.getConnectionManager();
            SchemeRegistry sr = ccm.getSchemeRegistry();
            sr.register(new Scheme("https", ssf, 443));
            return new DefaultHttpClient(ccm, base.getParams());
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * Builds and prepares the {@link HttpUriRequest}.
     *
     * @param method the {@link MethodBuilder} used to build the request
     * @return the prepared {@link HttpUriRequest} for execution
     */
    private HttpUriRequest buildRequest(final MethodBuilder<?> method) throws EvrythngClientException {

        // Build request method:
        HttpUriRequest request = method.build(uri());

        // Define client headers:
        for (Entry<String, String> header : headers.entrySet()) {
            request.setHeader(header.getKey(), header.getValue());
        }

        return request;
    }

    /**
     * Builds the final {@link URI} using {@link ApiCommand#uri} as base URL and
     * all defined {@link ApiCommand#queryParams} as query parameters.
     *
     * @return the absolute URI
     */
    public final URI uri() throws EvrythngClientException {

        return URIBuilder.fromUri(uri.toString()).queryParams(queryParams).build();
    }

    /**
     * Shuts down the connection manager to ensure immediate deallocation of all
     * system resources.
     *
     * @param client the {@link HttpClient} to shut down
     */
    protected void shutdown(final HttpClient client) {

        client.getConnectionManager().shutdown();
    }
}