com.monarchapis.client.rest.BaseClient.java Source code

Java tutorial

Introduction

Here is the source code for com.monarchapis.client.rest.BaseClient.java

Source

/*
 * Copyright (C) 2015 CapTech Ventures, Inc.
 * (http://www.captechconsulting.com) All Rights Reserved.
 *
 * 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.monarchapis.client.rest;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
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.FileEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.joda.time.DateTime;

import com.google.common.base.Function;
import com.google.common.collect.Collections2;

public abstract class BaseClient<T extends BaseClient<T>> {
    private static final Function<Object, String> TO_STRINGS = new Function<Object, String>() {
        @Override
        public String apply(Object value) {
            return String.valueOf(value);
        }
    };

    protected static final String CHARSET = "UTF-8";

    /** The HTTP method. */
    private final String method;

    /** URL that request will be sent to. */
    private final String url;

    /** Http headers to send. */
    private final Map<String, String> paths;

    /** Http headers to send. */
    private final Map<String, List<String>> headers;

    /** Http general parameters to send. */
    private final Map<String, List<String>> parameters;

    /** Http query parameters to send. */
    private final Map<String, List<String>> query;

    /** Http form parameters to send. */
    private final Map<String, List<String>> form;

    protected HttpEntity body;
    private String bodyString;

    /**
     * Internal method used to build an APIResponse using the specified
     * HttpResponse object.
     * 
     * @param response
     *            response wrapped inside an APIResponse object
     * @return api response
     */
    RestResponse buildResponse(HttpResponse response) throws RestException {
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            String rb = "";

            if (response.getEntity() != null) {
                rb = EntityUtils.toString(response.getEntity());
            }

            HttpHeader[] headers = buildHeaders(response);

            return new RestResponse(statusCode, rb, headers);
        } catch (IOException ioe) {
            throw new RestException(ioe);
        }
    }

    /**
     * Given an HttpResponse object, this method generates an array of HTTP
     * headers.
     * 
     * @param httpResponse
     *            used for building HTTP headers
     * @return array of http headers
     */
    private static HttpHeader[] buildHeaders(final HttpResponse httpResponse) {
        final Header[] headers = httpResponse.getAllHeaders();

        HttpHeader[] httpHeaders = new HttpHeader[headers.length];
        for (int i = 0; i < headers.length; ++i) {
            final Header header = headers[i];
            final String name = header.getName();
            final String value = header.getValue();
            final HttpHeader httpHeader = new HttpHeader(name, value);
            httpHeaders[i] = httpHeader;
        }

        return httpHeaders;
    }

    /**
     * Sets headers to the http message.
     * 
     * @param request
     *            http message to set headers for
     */
    protected void addInternalHeaders(HttpRequestBase request) {
        if (headers.isEmpty()) {
            return;
        }

        final Set<String> keySet = headers.keySet();

        for (final String key : keySet) {
            final List<String> values = headers.get(key);

            for (final String value : values) {
                request.addHeader(key, value);
            }
        }
    }

    public String getMethod() {
        return method;
    }

    public String getPath() {
        String url = this.url;

        for (Entry<String, String> entry : this.paths.entrySet()) {
            url = StringUtils.replace(url, "{" + entry.getKey() + "}", entry.getValue());
        }

        return url;
    }

    /**
     * Builds the query part of a URL using the UTF-8 encoding.
     * 
     * @return query
     */
    public String getQuery() {
        StringBuilder sb = new StringBuilder();

        try {
            if ("POST".equals(method) || "PUT".equals(method)) {
                appendParameters(sb, this.parameters);
            }

            appendParameters(sb, this.query);
        } catch (UnsupportedEncodingException e) {
            // UTF-8 is a Java supported encoding.
            // This should not occur unless the Java VM is not functioning
            // properly.
            throw new IllegalStateException();
        }

        return sb.toString();
    }

    public String getUrl() {
        String query = "";
        String builtQuery = getQuery();

        if (StringUtils.isNotEmpty(builtQuery)) {
            query = "?" + builtQuery;
        }

        return getPath() + query;
    }

    /**
     * Builds the form part of a URL using the UTF-8 encoding.
     * 
     * @return query
     */
    protected String buildForm() {
        StringBuilder sb = new StringBuilder();

        try {
            appendParameters(sb, this.parameters);
            appendParameters(sb, this.form);
        } catch (UnsupportedEncodingException e) {
            // UTF-8 is a Java supported encoding.
            // This should not occur unless the Java VM is not functioning
            // properly.
            throw new IllegalStateException();
        }

        return sb.toString();
    }

    private static void appendParameters(StringBuilder sb, Map<String, List<String>> parameters)
            throws UnsupportedEncodingException {
        for (Entry<String, List<String>> entry : parameters.entrySet()) {
            if (sb.length() > 0) {
                sb.append("&");
            }

            final String name = entry.getKey();
            final List<String> values = entry.getValue();

            for (final String value : values) {
                sb.append(URLEncoder.encode(name, CHARSET));
                sb.append("=");
                sb.append(URLEncoder.encode(value, CHARSET));
            }
        }
    }

    /**
     * Creates a BaseClient with the BaseClient object.
     * 
     * @param url
     *            The URL to send request to
     */
    public BaseClient(String method, String url) {
        this.paths = new HashMap<String, String>();
        this.headers = new HashMap<String, List<String>>();
        this.parameters = new HashMap<String, List<String>>();
        this.query = new HashMap<String, List<String>>();
        this.form = new HashMap<String, List<String>>();

        this.method = method;
        this.url = url;
    }

    public T setPath(String variable, String value) {
        this.paths.put(variable, value);

        return me();
    }

    public T setPath(String name, DateTime dateTime) {
        return setPath(name, (String) (dateTime != null ? dateTime.toString() : null));
    }

    public T setPath(String name, Object value) {
        return setPath(name, (String) (value != null ? String.valueOf(value) : null));
    }

    /**
     * Adds general parameter to be sent during http request.
     * 
     * <p>
     * Does not remove any parameters with the same name, thus allowing
     * duplicates.
     * </p>
     * 
     * @param name
     *            name of parameter
     * @param value
     *            value of parameter
     * @return a reference to 'this', which can be used for method chaining
     */
    public T addParameter(String name, String value) {
        if (value == null) {
            return me();
        }

        if (!parameters.containsKey(name)) {
            parameters.put(name, new ArrayList<String>());
        }

        List<String> values = parameters.get(name);
        values.add(value);

        return me();
    }

    public T addParameter(String name, DateTime dateTime) {
        return addParameter(name, (String) (dateTime != null ? dateTime.toString() : null));
    }

    public T addParameter(String name, Object value) {
        return addParameter(name, (String) (value != null ? String.valueOf(value) : null));
    }

    /**
     * Sets general parameter to be sent during http request.
     * 
     * <p>
     * Removes any parameters with the same name, thus disallowing duplicates.
     * </p>
     * 
     * @param name
     *            name of parameter
     * @param value
     *            value of parameter
     * @return a reference to 'this', which can be used for method chaining
     */
    public T setParameter(String name, String value) {
        if (parameters.containsKey(name)) {
            parameters.get(name).clear();
        }

        return addParameter(name, value);
    }

    public T setParameter(String name, DateTime dateTime) {
        return setParameter(name, (String) (dateTime != null ? dateTime.toString() : null));
    }

    public T setParameter(String name, Object value) {
        return setParameter(name, (String) (value != null ? String.valueOf(value) : null));
    }

    /**
     * Adds query parameter to be sent during http request.
     * 
     * <p>
     * Does not remove any parameters with the same name, thus allowing
     * duplicates.
     * </p>
     * 
     * @param name
     *            name of parameter
     * @param value
     *            value of parameter
     * @return a reference to 'this', which can be used for method chaining
     */
    public T addQuery(String name, String value) {
        if (value == null) {
            return me();
        }

        if (!query.containsKey(name)) {
            query.put(name, new ArrayList<String>());
        }

        List<String> values = query.get(name);
        values.add(value);

        return me();
    }

    public T addQuery(String name, DateTime dateTime) {
        return addQuery(name, (String) (dateTime != null ? dateTime.toString() : null));
    }

    public T addQuery(String name, Object value) {
        return addQuery(name, (String) (value != null ? String.valueOf(value) : null));
    }

    /**
     * Sets query parameter to be sent during http request.
     * 
     * <p>
     * Removes any parameters with the same name, thus disallowing duplicates.
     * </p>
     * 
     * @param name
     *            name of parameter
     * @param value
     *            value of parameter
     * @return a reference to 'this', which can be used for method chaining
     */
    public T setQuery(String name, String value) {
        if (query.containsKey(name)) {
            query.get(name).clear();
        }

        return addQuery(name, value);
    }

    public T setQuery(String name, DateTime dateTime) {
        return setQuery(name, (String) (dateTime != null ? dateTime.toString() : null));
    }

    public T setQuery(String name, Object value) {
        return setQuery(name, (String) (value != null ? String.valueOf(value) : null));
    }

    public T addQueryCollection(String name, Collection<?> values, CollectionFormat format) {
        if (values != null && !values.isEmpty()) {
            char delimiter = format.getDelimiter();

            if (delimiter == (char) 0) {
                for (Object value : values) {
                    addQuery(name, value);
                }
            } else {
                String value = StringUtils.join(Collections2.transform(values, TO_STRINGS), delimiter);
                addQuery(name, value);
            }
        }

        return me();
    }

    /**
     * Adds form parameter to be sent during http request.
     * 
     * <p>
     * Does not remove any parameters with the same name, thus allowing
     * duplicates.
     * </p>
     * 
     * @param name
     *            name of parameter
     * @param value
     *            value of parameter
     * @return a reference to 'this', which can be used for method chaining
     */
    public T addForm(String name, String value) {
        if (value == null) {
            return me();
        }

        if (!form.containsKey(name)) {
            form.put(name, new ArrayList<String>());
        }

        List<String> values = form.get(name);
        values.add(value);

        return me();
    }

    public T addForm(String name, DateTime dateTime) {
        return addForm(name, (String) (dateTime != null ? dateTime.toString() : null));
    }

    public T addForm(String name, Object value) {
        return addForm(name, (String) (value != null ? String.valueOf(value) : null));
    }

    public T addForm(String name, InputStream value) {
        throw new UnsupportedOperationException("adding files is not supported");
    }

    /**
     * Sets query parameter to be sent during http request.
     * 
     * <p>
     * Removes any parameters with the same name, thus disallowing duplicates.
     * </p>
     * 
     * @param name
     *            name of parameter
     * @param value
     *            value of parameter
     * @return a reference to 'this', which can be used for method chaining
     */
    public T setForm(String name, String value) {
        if (form.containsKey(name)) {
            form.get(name).clear();
        }

        return addForm(name, value);
    }

    public T setForm(String name, DateTime dateTime) {
        return setForm(name, (String) (dateTime != null ? dateTime.toString() : null));
    }

    public T setForm(String name, Object value) {
        return setForm(name, (String) (value != null ? String.valueOf(value) : null));
    }

    public T setForm(String name, InputStream value) {
        throw new UnsupportedOperationException("setting files is not supported");
    }

    /**
     * Adds http header to be sent during http request.
     * 
     * <p>
     * Does not remove any headers with the same name, thus allowing duplicates.
     * </p>
     * 
     * @param name
     *            name of header
     * @param value
     *            value of header
     * @return a reference to 'this', which can be used for method chaining
     */
    public T addHeader(String name, String value) {
        if (value == null) {
            return me();
        }

        if (!headers.containsKey(name)) {
            headers.put(name, new ArrayList<String>());
        }

        List<String> values = headers.get(name);
        values.add(value);

        return me();
    }

    public T addHeader(String name, DateTime dateTime) {
        return addHeader(name, (String) (dateTime != null ? dateTime.toString() : null));
    }

    public T addHeader(String name, Object value) {
        return addHeader(name, (String) (value != null ? String.valueOf(value) : null));
    }

    /**
     * Sets http header to be sent during http request.
     * 
     * <p>
     * Does not remove any headers with the same name, thus allowing duplicates.
     * </p>
     * 
     * @param name
     *            name of header
     * @param value
     *            value of header
     * @return a reference to 'this', which can be used for method chaining
     */
    public T setHeader(String name, String value) {
        if (headers.containsKey(name)) {
            headers.get(name).clear();
        }

        return addHeader(name, value);
    }

    public T setHeader(String name, DateTime dateTime) {
        return addHeader(name, (String) (dateTime != null ? dateTime.toString() : null));
    }

    public T setHeader(String name, Object value) {
        return addHeader(name, (String) (value != null ? String.valueOf(value) : null));
    }

    public String getHeader(String name) {
        List<String> list = headers.get(name);

        return list != null ? list.get(0) : null;
    }

    public T setBody(String body) {
        try {
            if (StringUtils.isNotEmpty(body)) {
                this.body = new StringEntity(body, CHARSET);
                bodyString = body;
            } else {
                this.body = null;
                bodyString = null;
            }
        } catch (Exception uee) {
            throw new IllegalStateException();
        }

        return me();
    }

    public String getBody() throws IOException {
        if (bodyString != null) {
            return bodyString;
        } else {
            return buildForm();
        }
    }

    @SuppressWarnings("deprecation")
    public T setBody(File file, String contentType) throws RestException {
        this.body = new FileEntity(file, contentType);

        return me();
    }

    public T accepts(String mimeType) {
        setHeader("Accept", mimeType);

        return me();
    }

    public T contentType(String mimeType) {
        setHeader("Content-Type", mimeType);

        return me();
    }

    public String getContentType() {
        String contentType = getHeader("Content-Type");

        return contentType;
    }

    public T authorization(String authorization) {
        setHeader("Authorization", authorization);

        return me();
    }

    @SuppressWarnings("unchecked")
    private T me() {
        return (T) this;
    }

    protected HttpRequestBase prepareRequest() {
        String method = getMethod();
        String url = getUrl();

        HttpRequestBase request = null;

        if ("GET".equals(method)) {
            request = new HttpGet(url);
        } else if ("POST".equals(method)) {
            HttpPost post = new HttpPost(url);
            setEntity(post);
            request = post;
        } else if ("PUT".equals(method)) {
            HttpPut put = new HttpPut(url);
            setEntity(put);
            request = put;
        } else if ("DELETE".equals(method)) {
            request = new HttpDelete(url);
        }

        addInternalHeaders(request);

        return request;
    }

    protected void setEntity(HttpEntityEnclosingRequestBase request) {
        if (body != null) {
            request.setEntity(body);
        } else {
            String form = buildForm();
            bodyString = form;

            if (StringUtils.isNotBlank(form)) {
                request.setEntity(new StringEntity(form, ContentType.APPLICATION_FORM_URLENCODED));
            } else {
                throw new RestException("No body was specified.");
            }
        }
    }

    public enum CollectionFormat {
        CSV(','), SSV(' '), TSV('\t'), PIPES('|'), MULTI((char) 0);

        private char delimiter;

        CollectionFormat(char delimiter) {
            this.delimiter = delimiter;
        }

        char getDelimiter() {
            return delimiter;
        }
    }
}