au.edu.unsw.cse.soc.federatedcloud.deployers.github.repository.GitHubClient.java Source code

Java tutorial

Introduction

Here is the source code for au.edu.unsw.cse.soc.federatedcloud.deployers.github.repository.GitHubClient.java

Source

package au.edu.unsw.cse.soc.federatedcloud.deployers.github.repository;
/*
 * Copyright (c) 2014, Denis Weerasiri 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.
 */

/*******************************************************************************
 *  Copyright (c) 2011 GitHub Inc.
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  which accompanies this distribution, and is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  Contributors:
 *    Kevin Sawicki (GitHub Inc.) - initial API and implementation
 *    Christian Trutz             - HttpClient 4.1
 *******************************************************************************/

import static com.google.gson.stream.JsonToken.BEGIN_ARRAY;
import static java.net.HttpURLConnection.HTTP_ACCEPTED;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_CONFLICT;
import static java.net.HttpURLConnection.HTTP_CREATED;
import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
import static java.net.HttpURLConnection.HTTP_GONE;
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
import static java.net.HttpURLConnection.HTTP_OK;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import static org.eclipse.egit.github.core.client.IGitHubConstants.AUTH_TOKEN;
import static org.eclipse.egit.github.core.client.IGitHubConstants.CHARSET_UTF8;
import static org.eclipse.egit.github.core.client.IGitHubConstants.CONTENT_TYPE_JSON;
import static org.eclipse.egit.github.core.client.IGitHubConstants.HOST_API;
import static org.eclipse.egit.github.core.client.IGitHubConstants.HOST_DEFAULT;
import static org.eclipse.egit.github.core.client.IGitHubConstants.HOST_GISTS;
import static org.eclipse.egit.github.core.client.IGitHubConstants.PROTOCOL_HTTPS;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_V3_API;

import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.stream.JsonReader;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;

import org.eclipse.egit.github.core.RequestError;
import org.eclipse.egit.github.core.util.EncodingUtils;

/**
 * Client class for interacting with GitHub HTTP/JSON API.
 */
public class GitHubClient {

    /**
     * Create API v3 client from URL.
     * <p>
     * This creates an HTTPS-based client with a host that contains the host
     * value of the given URL prefixed with 'api' if the given URL is github.com
     * or gist.github.com
     *
     * @param url
     * @return client
     */
    public static GitHubClient createClient(String url) {
        try {
            String host = new URL(url).getHost();
            if (HOST_DEFAULT.equals(host) || HOST_GISTS.equals(host))
                host = HOST_API;
            return new GitHubClient(host);
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * Content-Type header
     */
    protected static final String HEADER_CONTENT_TYPE = "Content-Type"; //$NON-NLS-1$

    /**
     * Accept header
     */
    protected static final String HEADER_ACCEPT = "Accept"; //$NON-NLS-1$

    /**
     * Authorization header
     */
    protected static final String HEADER_AUTHORIZATION = "Authorization"; //$NON-NLS-1$

    /**
     * User-Agent header
     */
    protected static final String HEADER_USER_AGENT = "User-Agent"; //$NON-NLS-1$

    /**
     * METHOD_GET
     */
    protected static final String METHOD_GET = "GET"; //$NON-NLS-1$

    /**
     * METHOD_PUT
     */
    protected static final String METHOD_PUT = "PUT"; //$NON-NLS-1$

    /**
     * METHOD_POST
     */
    protected static final String METHOD_POST = "POST"; //$NON-NLS-1$

    /**
     * METHOD_DELETE
     */
    protected static final String METHOD_DELETE = "DELETE"; //$NON-NLS-1$

    /**
     * Default user agent request header value
     */
    protected static final String USER_AGENT = "GitHubJava/2.1.0"; //$NON-NLS-1$

    /**
     * 422 status code for unprocessable entity
     */
    protected static final int HTTP_UNPROCESSABLE_ENTITY = 422;

    /**
     * Base URI
     */
    protected final String baseUri;

    /**
     * Prefix to apply to base URI
     */
    protected final String prefix;

    /**
     * {@link Gson} instance
     */
    protected Gson gson = GsonUtils.getGson();

    private String user;

    private String credentials;

    private String userAgent = USER_AGENT;

    private int bufferSize = 8192;

    private int requestLimit = -1;

    private int remainingRequests = -1;

    /**
     * Create default client
     */
    public GitHubClient() {
        this(HOST_API);
    }

    /**
     * Create client for host name
     *
     * @param hostname
     */
    public GitHubClient(String hostname) {
        this(hostname, -1, PROTOCOL_HTTPS);
    }

    /**
     * Create client for host, port, and scheme
     *
     * @param hostname
     * @param port
     * @param scheme
     */
    public GitHubClient(final String hostname, final int port, final String scheme) {
        final StringBuilder uri = new StringBuilder(scheme);
        uri.append("://"); //$NON-NLS-1$
        uri.append(hostname);
        if (port > 0)
            uri.append(':').append(port);
        baseUri = uri.toString();

        // Use URI prefix on non-standard host names
        if (HOST_API.equals(hostname))
            prefix = null;
        else
            prefix = SEGMENT_V3_API;
    }

    /**
     * Set whether or not serialized data should include fields that are null.
     *
     * @param serializeNulls
     * @return this client
     */
    public GitHubClient setSerializeNulls(boolean serializeNulls) {
        gson = GsonUtils.getGson(serializeNulls);
        return this;
    }

    /**
     * Set the value to set as the user agent header on every request created.
     * Specifying a null or empty agent parameter will reset this client to use
     * the default user agent header value.
     *
     * @param agent
     * @return this client
     */
    public GitHubClient setUserAgent(final String agent) {
        if (agent != null && agent.length() > 0)
            userAgent = agent;
        else
            userAgent = USER_AGENT;
        return this;
    }

    /**
     * Configure request with standard headers
     *
     * @param request
     * @return configured request
     */
    protected HttpURLConnection configureRequest(final HttpURLConnection request) {
        if (credentials != null)
            request.setRequestProperty(HEADER_AUTHORIZATION, credentials);
        request.setRequestProperty(HEADER_USER_AGENT, userAgent);
        request.setRequestProperty(HEADER_ACCEPT, "application/vnd.github.beta+json"); //$NON-NLS-1$
        return request;
    }

    /**
     * Configure request URI
     *
     * @param uri
     * @return configured URI
     */
    protected String configureUri(final String uri) {
        if (prefix == null || uri.startsWith(prefix))
            return uri;
        else
            return prefix + uri;
    }

    /**
     * Create connection to URI
     *
     * @param uri
     * @return connection
     * @throws IOException
     */
    protected HttpURLConnection createConnection(String uri) throws IOException {
        URL url = new URL(createUri(uri));
        return (HttpURLConnection) url.openConnection();
    }

    /**
     * Create connection to URI
     *
     * @param uri
     * @param method
     * @return connection
     * @throws IOException
     */
    protected HttpURLConnection createConnection(String uri, String method) throws IOException {
        HttpURLConnection connection = createConnection(uri);
        connection.setRequestMethod(method);
        return configureRequest(connection);
    }

    /**
     * Create a GET request connection to the URI
     *
     * @param uri
     * @return connection
     * @throws IOException
     */
    protected HttpURLConnection createGet(String uri) throws IOException {
        return createConnection(uri, METHOD_GET);
    }

    /**
     * Create a POST request connection to the URI
     *
     * @param uri
     * @return connection
     * @throws IOException
     */
    protected HttpURLConnection createPost(String uri) throws IOException {
        return createConnection(uri, METHOD_POST);
    }

    /**
     * Create a PUT request connection to the URI
     *
     * @param uri
     * @return connection
     * @throws IOException
     */
    protected HttpURLConnection createPut(String uri) throws IOException {
        return createConnection(uri, METHOD_PUT);
    }

    /**
     * Create a DELETE request connection to the URI
     *
     * @param uri
     * @return connection
     * @throws IOException
     */
    protected HttpURLConnection createDelete(String uri) throws IOException {
        return createConnection(uri, METHOD_DELETE);
    }

    /**
     * Set credentials
     *
     * @param user
     * @param password
     * @return this client
     */
    public GitHubClient setCredentials(final String user, final String password) {
        this.user = user;
        if (user != null && user.length() > 0 && password != null && password.length() > 0)
            credentials = "Basic " //$NON-NLS-1$
                    + EncodingUtils.toBase64(user + ':' + password);
        else
            credentials = null;
        return this;
    }

    /**
     * Set OAuth2 token
     *
     * @param token
     * @return this client
     */
    public GitHubClient setOAuth2Token(String token) {
        if (token != null && token.length() > 0)
            credentials = AUTH_TOKEN + ' ' + token;
        else
            credentials = null;
        return this;
    }

    /**
     * Set buffer size used to send the request and read the response
     *
     * @param bufferSize
     * @return this client
     */
    public GitHubClient setBufferSize(int bufferSize) {
        if (bufferSize < 1)
            throw new IllegalArgumentException("Buffer size must be greater than zero"); //$NON-NLS-1$

        this.bufferSize = bufferSize;
        return this;
    }

    /**
     * Get the user that this client is currently authenticating as
     *
     * @return user or null if not authentication
     */
    public String getUser() {
        return user;
    }

    /**
     * Convert object to a JSON string
     *
     * @param object
     * @return JSON string
     * @throws IOException
     */
    protected String toJson(Object object) throws IOException {
        try {
            return gson.toJson(object);
        } catch (JsonParseException jpe) {
            IOException ioe = new IOException("Parse exception converting object to JSON"); //$NON-NLS-1$
            ioe.initCause(jpe);
            throw ioe;
        }
    }

    /**
     * Parse JSON to specified type
     *
     * @param <V>
     * @param stream
     * @param type
     * @return parsed type
     * @throws IOException
     */
    protected <V> V parseJson(InputStream stream, Type type) throws IOException {
        return parseJson(stream, type, null);
    }

    /**
     * Parse JSON to specified type
     *
     * @param <V>
     * @param stream
     * @param type
     * @param listType
     * @return parsed type
     * @throws IOException
     */
    protected <V> V parseJson(InputStream stream, Type type, Type listType) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream, CHARSET_UTF8), bufferSize);
        if (listType == null)
            try {
                return gson.fromJson(reader, type);
            } catch (JsonParseException jpe) {
                IOException ioe = new IOException("Parse exception converting JSON to object"); //$NON-NLS-1$
                ioe.initCause(jpe);
                throw ioe;
            } finally {
                try {
                    reader.close();
                } catch (IOException ignored) {
                    // Ignored
                }
            }
        else {
            JsonReader jsonReader = new JsonReader(reader);
            try {
                if (jsonReader.peek() == BEGIN_ARRAY)
                    return gson.fromJson(jsonReader, listType);
                else
                    return gson.fromJson(jsonReader, type);
            } catch (JsonParseException jpe) {
                IOException ioe = new IOException("Parse exception converting JSON to object"); //$NON-NLS-1$
                ioe.initCause(jpe);
                throw ioe;
            } finally {
                try {
                    jsonReader.close();
                } catch (IOException ignored) {
                    // Ignored
                }
            }
        }
    }

    /**
     * Does status code denote an error
     *
     * @param code
     * @return true if error, false otherwise
     */
    protected boolean isError(final int code) {
        switch (code) {
        case HTTP_BAD_REQUEST:
        case HTTP_UNAUTHORIZED:
        case HTTP_FORBIDDEN:
        case HTTP_NOT_FOUND:
        case HTTP_CONFLICT:
        case HTTP_GONE:
        case HTTP_UNPROCESSABLE_ENTITY:
        case HTTP_INTERNAL_ERROR:
            return true;
        default:
            return false;
        }
    }

    /**
     * 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 HTTP_OK:
        case HTTP_CREATED:
        case HTTP_ACCEPTED:
            return true;
        default:
            return false;
        }
    }

    /**
     * Is the response empty?
     *
     * @param code
     * @return true if empty, false otherwise
     */
    protected boolean isEmpty(final int code) {
        return HTTP_NO_CONTENT == code;
    }

    /**
     * Parse error from response
     *
     * @param response
     * @return request error
     * @throws IOException
     */
    protected RequestError parseError(InputStream response) throws IOException {
        return parseJson(response, RequestError.class);
    }

    /**
     * Get body from response inputs stream
     *
     * @param request
     * @param stream
     * @return parsed body
     * @throws IOException
     */
    protected Object getBody(GitHubRequest request, InputStream stream) throws IOException {
        Type type = request.getType();
        if (type != null)
            return parseJson(stream, type, request.getArrayType());
        else
            return null;
    }

    /**
     * Create error exception from response and throw it
     *
     * @param response
     * @param code
     * @param status
     * @return non-null newly created {@link IOException}
     */
    protected IOException createException(InputStream response, int code, String status) {
        if (isError(code)) {
            final RequestError error;
            try {
                error = parseError(response);
            } catch (IOException e) {
                return e;
            }
            if (error != null)
                return new RequestException(error, code);
        } else
            try {
                response.close();
            } catch (IOException ignored) {
                // Ignored
            }
        String message;
        if (status != null && status.length() > 0)
            message = status + " (" + code + ')'; //$NON-NLS-1$
        else
            message = "Unknown error occurred (" + code + ')'; //$NON-NLS-1$
        return new IOException(message);
    }

    /**
     * Post to URI
     *
     * @param uri
     * @throws IOException
     */
    public void post(String uri) throws IOException {
        post(uri, null, null);
    }

    /**
     * Put to URI
     *
     * @param uri
     * @throws IOException
     */
    public void put(String uri) throws IOException {
        put(uri, null, null);
    }

    /**
     * Delete resource at URI. This method will throw an {@link IOException}
     * when the response status is not a 204 (No Content).
     *
     * @param uri
     * @throws IOException
     */
    public void delete(String uri) throws IOException {
        delete(uri, null);
    }

    /**
     * Send parameters to output stream of request
     *
     * @param request
     * @param params
     * @throws IOException
     */
    protected void sendParams(HttpURLConnection request, Object params) throws IOException {
        request.setDoOutput(true);
        if (params != null) {
            request.setRequestProperty(HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON + "; charset=" + CHARSET_UTF8); //$NON-NLS-1$
            byte[] data = toJson(params).getBytes(CHARSET_UTF8);
            request.setFixedLengthStreamingMode(data.length);
            BufferedOutputStream output = new BufferedOutputStream(request.getOutputStream(), bufferSize);
            try {
                output.write(data);
                output.flush();
            } finally {
                try {
                    output.close();
                } catch (IOException ignored) {
                    // Ignored
                }
            }
        } else {
            request.setFixedLengthStreamingMode(0);
            request.setRequestProperty("Content-Length", "0");
        }
    }

    private <V> V sendJson(final HttpURLConnection request, final Object params, final Type type)
            throws IOException {
        sendParams(request, params);
        final int code = request.getResponseCode();
        updateRateLimits(request);
        if (isOk(code))
            if (type != null)
                return parseJson(getStream(request), type);
            else
                return null;
        if (isEmpty(code))
            return null;
        throw createException(getStream(request), code, request.getResponseMessage());
    }

    /**
     * Create full URI from path
     *
     * @param path
     * @return uri
     */
    protected String createUri(final String path) {
        return baseUri + configureUri(path);
    }

    /**
     * Get response stream from GET to URI. It is the responsibility of the
     * calling method to close the returned stream.
     *
     * @param request
     * @return stream
     * @throws IOException
     */
    public InputStream getStream(final GitHubRequest request) throws IOException {
        return getResponseStream(createGet(request.generateUri()));
    }

    /**
     * Get response stream from POST to URI. It is the responsibility of the
     * calling method to close the returned stream.
     *
     * @param uri
     * @param params
     * @return stream
     * @throws IOException
     */
    public InputStream postStream(final String uri, final Object params) throws IOException {
        HttpURLConnection connection = createPost(uri);
        sendParams(connection, params);
        return getResponseStream(connection);
    }

    /**
     * Get response stream for request
     *
     * @param request
     * @return stream
     * @throws IOException
     */
    protected InputStream getResponseStream(final HttpURLConnection request) throws IOException {
        InputStream stream = getStream(request);
        int code = request.getResponseCode();
        updateRateLimits(request);
        if (isOk(code))
            return stream;
        else
            throw createException(stream, code, request.getResponseMessage());
    }

    /**
     * Get stream from request
     *
     * @param request
     * @return stream
     * @throws IOException
     */
    protected InputStream getStream(HttpURLConnection request) throws IOException {
        if (request.getResponseCode() < HTTP_BAD_REQUEST)
            return request.getInputStream();
        else {
            InputStream stream = request.getErrorStream();
            return stream != null ? stream : request.getInputStream();
        }
    }

    /**
     * Get response from URI and bind to specified type
     *
     * @param request
     * @return response
     * @throws IOException
     */
    public GitHubResponse get(GitHubRequest request) throws IOException {
        HttpURLConnection httpRequest = createGet(request.generateUri());
        String accept = request.getResponseContentType();
        if (accept != null)
            httpRequest.setRequestProperty(HEADER_ACCEPT, accept);
        final int code = httpRequest.getResponseCode();
        updateRateLimits(httpRequest);
        if (isOk(code))
            return new GitHubResponse(httpRequest, getBody(request, getStream(httpRequest)));
        if (isEmpty(code))
            return new GitHubResponse(httpRequest, null);
        throw createException(getStream(httpRequest), code, httpRequest.getResponseMessage());
    }

    /**
     * Post data to URI
     *
     * @param <V>
     * @param uri
     * @param params
     * @param type
     * @return response
     * @throws IOException
     */
    public <V> V post(final String uri, final Object params, final Type type) throws IOException {
        HttpURLConnection request = createPost(uri);
        return sendJson(request, params, type);
    }

    /**
     * Put data to URI
     *
     * @param <V>
     * @param uri
     * @param params
     * @param type
     * @return response
     * @throws IOException
     */
    public <V> V put(final String uri, final Object params, final Type type) throws IOException {
        HttpURLConnection request = createPut(uri);
        return sendJson(request, params, type);
    }

    /**
     * Delete resource at URI. This method will throw an {@link IOException}
     * when the response status is not a 204 (No Content).
     *
     * @param uri
     * @param params
     * @throws IOException
     */
    public void delete(final String uri, final Object params) throws IOException {
        HttpURLConnection request = createDelete(uri);
        if (params != null)
            sendParams(request, params);
        final int code = request.getResponseCode();
        updateRateLimits(request);
        if (!isEmpty(code))
            throw new RequestException(parseError(getStream(request)), code);
    }

    /**
     * Update rate limits present in response headers
     *
     * @param request
     * @return this client
     */
    protected GitHubClient updateRateLimits(HttpURLConnection request) {
        String limit = request.getHeaderField("X-RateLimit-Limit");
        if (limit != null && limit.length() > 0)
            try {
                requestLimit = Integer.parseInt(limit);
            } catch (NumberFormatException nfe) {
                requestLimit = -1;
            }
        else
            requestLimit = -1;

        String remaining = request.getHeaderField("X-RateLimit-Remaining");
        if (remaining != null && remaining.length() > 0)
            try {
                remainingRequests = Integer.parseInt(remaining);
            } catch (NumberFormatException nfe) {
                remainingRequests = -1;
            }
        else
            remainingRequests = -1;

        return this;
    }

    /**
     * Get number of requests remaining before rate limiting occurs
     * <p>
     * This will be the value of the 'X-RateLimit-Remaining' header from the
     * last request made
     *
     * @return remainingRequests or -1 if not present in the response
     */
    public int getRemainingRequests() {
        return remainingRequests;
    }

    /**
     * Get number of requests that {@link #getRemainingRequests()} counts down
     * from as each request is made
     * <p>
     * This will be the value of the 'X-RateLimit-Limit' header from the last
     * request made
     *
     * @return requestLimit or -1 if not present in the response
     */
    public int getRequestLimit() {
        return requestLimit;
    }
}