com.puppetlabs.puppetdb.javaclient.impl.HttpComponentsConnector.java Source code

Java tutorial

Introduction

Here is the source code for com.puppetlabs.puppetdb.javaclient.impl.HttpComponentsConnector.java

Source

/**
 * Copyright (c) 2013 Puppet Labs, Inc. and other contributors, as listed below.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Apache License, Version 2.0
 * which accompanies this distribution, and is available at
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Contributors:
 *   Puppet Labs
 */
package com.puppetlabs.puppetdb.javaclient.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
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.client.utils.URLEncodedUtils;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.inject.Inject;
import com.puppetlabs.puppetdb.javaclient.APIException;
import com.puppetlabs.puppetdb.javaclient.APIPreferences;
import com.puppetlabs.puppetdb.javaclient.HttpConnector;
import com.puppetlabs.puppetdb.javaclient.query.Paging;

/**
 * Class responsible for all HTTP request and response processing. Based on the
 * Apache {@link HttpClient}.
 */
public class HttpComponentsConnector implements HttpConnector {

    static InputStream getStream(HttpEntity entity) throws IOException {
        if (entity == null)
            return null;

        return entity.getContent();
    }

    protected static <T> T parseJson(Gson gson, InputStream stream, Type type) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream, HttpConnector.UTF_8), 2048);
        StringBuilder bld = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            bld.append(line);
            bld.append('\n');
        }
        try {
            return gson.fromJson(bld.toString(), type);
        } catch (JsonSyntaxException jpe) {
            throw new APIException("Parse exception converting JSON to object", jpe); //$NON-NLS-1$
        } finally {
            try {
                reader.close();
            } catch (IOException ignored) {
                // Ignored
            }
        }
    }

    private final Gson gson;

    private final HttpClient httpClient;

    private final APIPreferences preferences;

    private HttpRequestBase currentRequest;

    /**
     * <p>
     * Creates a new HttpCommonsConnector.
     * </p>
     * <p>
     * <b>For Guice injection only.</b> Don't use this constructor from code
     * </p>
     * 
     * @param gson
     *            The instance used when parsing or serializing JSON
     * @param httpClient
     *            The client to use for the connection
     * @param preferences
     *            API connection preferences
     */
    @Inject
    public HttpComponentsConnector(Gson gson, HttpClient httpClient, APIPreferences preferences) {
        this.gson = gson;
        this.preferences = preferences;
        this.httpClient = httpClient;
    }

    @Override
    public synchronized void abortCurrentRequest() {
        if (currentRequest != null) {
            currentRequest.abort();
            currentRequest = null;
        }
    }

    protected void assignContent(HttpEntityEnclosingRequestBase request, Map<String, String> params) {
        if (params != null && !params.isEmpty()) {
            List<NameValuePair> pairs = new ArrayList<NameValuePair>(params.size());
            for (Map.Entry<String, String> param : params.entrySet())
                pairs.add(new BasicNameValuePair(param.getKey(), param.getValue()));
            try {
                StringEntity entity = new StringEntity(URLEncodedUtils.format(pairs, UTF_8.name()), UTF_8.name());
                entity.setContentType(CONTENT_TYPE_WWW_FORM_URLENCODED);
                request.setEntity(entity);
            } catch (UnsupportedEncodingException e) {
                throw new IllegalArgumentException(e);
            }
        }
    }

    protected void configureRequest(final HttpRequestBase request) {
        request.addHeader(HttpHeaders.ACCEPT, CONTENT_TYPE_JSON);
        request.addHeader(HttpHeaders.USER_AGENT, USER_AGENT);
    }

    /**
     * Create exception from response
     * 
     * @param response
     * @param code
     * @param status
     * @return non-null newly {@link IOException}
     */
    protected HttpResponseException createException(InputStream response, int code, String status) {
        String message;
        if (status != null && status.length() > 0)
            message = status;
        else
            message = "Unknown error occurred";
        return new HttpResponseException(code, message);
    }

    private HttpGet createGetRequest(String urlStr, Map<String, String> params) {
        StringBuilder bld = new StringBuilder(createURI(urlStr));
        if (params != null && !params.isEmpty()) {
            List<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>();
            for (Map.Entry<String, String> param : params.entrySet())
                pairs.add(new BasicNameValuePair(param.getKey(), param.getValue()));
            bld.append('?');
            bld.append(URLEncodedUtils.format(pairs, UTF_8.name()));
        }
        return new HttpGet(URI.create(bld.toString()));
    }

    /**
     * Create full URI from path
     * 
     * @param path
     * @return uri
     */
    protected String createURI(String path) {
        StringBuilder bld = new StringBuilder();
        if (preferences.getCertPEM() == null)
            bld.append("http://");
        else
            bld.append("https://");

        bld.append(preferences.getServiceHostname());
        bld.append(':');
        bld.append(preferences.getServicePort());
        bld.append('/');
        if (path.startsWith("../"))
            // Skip the 'v3' part (this is probably ../experimental/<something>
            bld.append(path, 3, path.length());
        else {
            bld.append("v3");
            bld.append(path);
        }
        return bld.toString();
    }

    @Override
    public void delete(final String uri) throws IOException {
        HttpDelete request = new HttpDelete(createURI(uri));
        configureRequest(request);
        executeRequest(request, null, null);
    }

    @Override
    public void download(String urlStr, Map<String, String> params, final OutputStream output) throws IOException {
        HttpGet request = createGetRequest(urlStr, params);
        configureRequest(request);
        httpClient.execute(request, new ResponseHandler<Void>() {
            @Override
            public Void handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
                StatusLine statusLine = response.getStatusLine();
                int code = statusLine.getStatusCode();
                if (code != HttpStatus.SC_OK)
                    throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());

                HttpEntity entity = response.getEntity();
                entity.writeTo(output);
                return null;
            }
        });
    }

    private synchronized void endRequest() {
        currentRequest = null;
    }

    protected <V> V executeRequest(final HttpRequestBase request, final Type type, int[] totalCount)
            throws IOException {
        startRequest(request);
        try {
            HttpResponse response = httpClient.execute(request);
            StatusLine statusLine = response.getStatusLine();
            int code = statusLine.getStatusCode();
            if (code >= 300) {
                String msg;
                try {
                    msg = EntityUtils.toString(response.getEntity());
                    if (msg == null)
                        msg = statusLine.getReasonPhrase();
                    else {
                        msg = statusLine.getReasonPhrase() + ": " + msg;
                    }
                } catch (Exception e) {
                    // Just skip
                    msg = statusLine.getReasonPhrase();
                }
                throw new HttpResponseException(statusLine.getStatusCode(), msg);
            }

            HttpEntity entity = response.getEntity();
            if (isOk(code)) {
                if (type == null)
                    return null;

                if (totalCount != null) {
                    Header xrecs = response.getFirstHeader("X-Records");
                    if (xrecs != null)
                        try {
                            totalCount[0] = Integer.parseInt(xrecs.getValue());
                        } catch (NumberFormatException e) {
                        }
                }
                return parseJson(gson, getStream(entity), type);
            }
            throw createException(getStream(entity), code, statusLine.getReasonPhrase());
        } finally {
            endRequest();
        }
    }

    @Override
    public <V> V get(String urlStr, Map<String, String> params, Type type) throws IOException {
        HttpGet request = createGetRequest(urlStr, params);
        configureRequest(request);
        return executeRequest(request, type, null);
    }

    @Override
    public <V, Q> V get(String urlStr, Paging<Q> params, Type type) throws IOException {
        Map<String, String> queryParams = null;
        int[] totalCount = null;
        if (params != null) {
            queryParams = new HashMap<String, String>();
            params.appendTo(queryParams);
            totalCount = new int[] { -1 };
        }
        HttpGet request = createGetRequest(urlStr, queryParams);
        configureRequest(request);
        V result = executeRequest(request, type, totalCount);
        if (params != null)
            params.setTotalCount(totalCount[0]);
        return result;
    }

    /**
     * Does status code denote a non-error response?
     * 
     * @param code
     * @return true if okay, false otherwise
     */
    protected boolean isOk(final int code) {
        switch (code) {
        case HttpStatus.SC_OK:
        case HttpStatus.SC_CREATED:
        case HttpStatus.SC_ACCEPTED:
        case HttpStatus.SC_NO_CONTENT: // weird, but returned by DELETE calls
            return true;
        default:
            return false;
        }
    }

    @Override
    public <V> V patch(final String uri, final Map<String, String> params, final Class<V> type) throws IOException {
        // HttpPatch is introduced in 4.2. This code is compatible with 4.1 in order to
        // play nice with Eclipse Juno and Kepler
        HttpPost request = new HttpPost(createURI(uri)) {
            @Override
            public String getMethod() {
                return "PATCH";
            }
        };

        configureRequest(request);
        assignContent(request, params);
        return executeRequest(request, type, null);
    }

    @Override
    public <V> V post(final String uri, final Map<String, String> params, final Class<V> type) throws IOException {
        HttpPost request = new HttpPost(createURI(uri));
        configureRequest(request);
        assignContent(request, params);
        return executeRequest(request, type, null);
    }

    @Override
    public <V> V postUpload(String uri, Map<String, String> stringParts, InputStream in, String mimeType,
            String fileName, final long fileSize, Class<V> type) throws IOException {
        HttpPost request = new HttpPost(createURI(uri));
        configureRequest(request);

        MultipartEntity entity = new MultipartEntity();
        for (Map.Entry<String, String> entry : stringParts.entrySet())
            entity.addPart(entry.getKey(), StringBody.create(entry.getValue(), "text/plain", UTF_8));

        entity.addPart("file", new InputStreamBody(in, mimeType, fileName) {
            @Override
            public long getContentLength() {
                return fileSize;
            }
        });
        request.setEntity(entity);
        return executeRequest(request, type, null);
    }

    @Override
    public <V> V put(final String uri, final Map<String, String> params, final Class<V> type) throws IOException {
        HttpPut request = new HttpPut(createURI(uri));
        configureRequest(request);
        assignContent(request, params);
        return executeRequest(request, type, null);
    }

    private synchronized void startRequest(HttpRequestBase request) {
        if (currentRequest != null)
            currentRequest.abort();
        currentRequest = request;
    }

    @Override
    public String toJSON(Object object) {
        return gson.toJson(object);
    }
}