com.blackducksoftware.integration.hub.rest.RestConnection.java Source code

Java tutorial

Introduction

Here is the source code for com.blackducksoftware.integration.hub.rest.RestConnection.java

Source

/*******************************************************************************
 * Copyright (C) 2016 Black Duck Software, Inc.
 * http://www.blackducksoftware.com/
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.blackducksoftware.integration.hub.rest;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.CookieHandler;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.AbstractMap;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.restlet.Client;
import org.restlet.Context;
import org.restlet.Message;
import org.restlet.Response;
import org.restlet.data.CharacterSet;
import org.restlet.data.Cookie;
import org.restlet.data.CookieSetting;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Protocol;
import org.restlet.data.Status;
import org.restlet.engine.header.HeaderConstants;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.ClientResource;
import org.restlet.resource.ResourceException;
import org.restlet.util.NamedValue;
import org.restlet.util.Series;

import com.blackducksoftware.integration.exception.EncryptionException;
import com.blackducksoftware.integration.hub.exception.BDRestException;
import com.blackducksoftware.integration.hub.exception.ResourceDoesNotExistException;
import com.blackducksoftware.integration.hub.global.HubCredentials;
import com.blackducksoftware.integration.hub.global.HubProxyInfo;
import com.blackducksoftware.integration.log.IntLogger;
import com.blackducksoftware.integration.log.LogLevel;
import com.blackducksoftware.integration.util.AuthenticatorUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

/**
 * Manages the low-level details of communicating with the server via REST.
 *
 * @author sbillings
 *
 */
public abstract class RestConnection {
    public static final String JSON_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSX";

    private String baseUrl;

    private Series<Cookie> cookies;

    private int timeout = 120;

    private IntLogger logger;

    private final Gson gson = new GsonBuilder().setDateFormat(JSON_DATE_FORMAT).create();

    private final JsonParser jsonParser = new JsonParser();

    private final Client client;

    public static Date parseDateString(final String dateString) throws ParseException {
        final SimpleDateFormat sdf = new SimpleDateFormat(JSON_DATE_FORMAT);
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        return sdf.parse(dateString);
    }

    public static String formatDate(final Date date) {
        final SimpleDateFormat sdf = new SimpleDateFormat(JSON_DATE_FORMAT);
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        return sdf.format(date);
    }

    public RestConnection() {
        this(null);
    }

    public RestConnection(final IntLogger logger) {
        if (logger != null) {
            setLogger(logger);
        }
        client = createClient();
        setTimeout(timeout); // just in case setTimeout() is never called
    }

    private Client createClient() {
        logMessage(LogLevel.DEBUG, "createClient()");
        final Context context = new Context();
        final List<Protocol> protocolList = new ArrayList<>();
        protocolList.add(Protocol.HTTP);
        protocolList.add(Protocol.HTTPS);
        final Client client = new Client(context, protocolList);
        // set the connection pool parameters for the httpClient. Since we are
        // connecting to one hub instance then the maxConnections per host can
        // be equal to the maxTotalConnections. If this rest connection object
        // connects to more than one hub instance then the maxConnectionsPerHost
        // would need to be divided by the number of hub instances.
        logMessage(LogLevel.DEBUG, "Setting maxConnectionsPerHost and maxTotalConnections on client context");
        client.getContext().getParameters().set("maxConnectionsPerHost", "100");
        client.getContext().getParameters().set("maxTotalConnections", "100");

        return client;
    }

    public IntLogger getLogger() {
        return logger;
    }

    public void setLogger(final IntLogger logger) {
        this.logger = logger;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(final int timeout) {
        if (timeout <= 0) {
            throw new IllegalArgumentException("Timeout must be greater than zero.");
        }
        this.timeout = timeout;
        // the User sets the timeout in seconds, so we translate to ms
        final String stringTimeout = String.valueOf(timeout * 1000);
        logMessage(LogLevel.DEBUG, "Setting socketTimeout, socketConnectTimeoutMs, and readTimeout to: "
                + stringTimeout + " on client context");
        client.getContext().getParameters().set("socketTimeout", stringTimeout);
        client.getContext().getParameters().set("socketConnectTimeoutMs", stringTimeout);
        client.getContext().getParameters().set("readTimeout", stringTimeout);
    }

    public String getBaseUrl() {
        return baseUrl;
    }

    public void setBaseUrl(final String baseUrl) {
        this.baseUrl = baseUrl;
    }

    /**
     * The proxy settings get set as System properties. I.E. https.proxyHost,
     * https.proxyPort, http.proxyHost, http.proxyPort, http.nonProxyHosts
     *
     */
    public void setProxyProperties(final HubProxyInfo proxyInfo) {
        cleanUpOldProxySettings();

        if (!StringUtils.isBlank(proxyInfo.getHost()) && proxyInfo.getPort() > 0) {
            if (logger != null) {
                logger.debug("Using Proxy : " + proxyInfo.getHost() + ", at Port : " + proxyInfo.getPort());
            }

            System.setProperty("http.proxyHost", proxyInfo.getHost());
            System.setProperty("http.proxyPort", Integer.toString(proxyInfo.getPort()));

            try {
                if (!StringUtils.isBlank(proxyInfo.getUsername())
                        && !StringUtils.isBlank(proxyInfo.getDecryptedPassword())) {

                    AuthenticatorUtil.setAuthenticator(proxyInfo.getUsername(), proxyInfo.getDecryptedPassword());
                }
            } catch (final Exception e) {
                if (logger != null) {
                    logger.error(e);
                }
            }
        }
        if (!StringUtils.isBlank(proxyInfo.getIgnoredProxyHosts())) {
            System.setProperty("http.nonProxyHosts", proxyInfo.getIgnoredProxyHosts().replaceAll(",", "|"));
        }
    }

    /**
     * The proxy settings get set as System properties. I.E. https.proxyHost,
     * https.proxyPort, http.proxyHost, http.proxyPort, http.nonProxyHosts
     *
     */
    public void setProxyProperties(final String proxyHost, final int proxyPort, final List<Pattern> noProxyHosts,
            final String proxyUsername, final String proxyPassword) {

        HubCredentials proxyCredentials = null;
        try {
            proxyCredentials = new HubCredentials(proxyUsername, proxyPassword);
        } catch (final IllegalArgumentException e) {
            if (logger != null) {
                logger.error(e);
            }
        } catch (final EncryptionException e) {
            if (logger != null) {
                logger.error(e);
            }
        }
        String noProxyHostsString = null;
        if (noProxyHosts != null && !noProxyHosts.isEmpty()) {
            for (final Pattern pattern : noProxyHosts) {
                if (noProxyHostsString == null) {
                    noProxyHostsString = pattern.toString();
                } else {
                    noProxyHostsString = noProxyHostsString + "|" + pattern.toString();
                }
            }
        }

        final HubProxyInfo proxyInfo = new HubProxyInfo(proxyHost, proxyPort, proxyCredentials, noProxyHostsString);
        setProxyProperties(proxyInfo);

    }

    /**
     * Gets the cookie for the Authorized connection to the Hub server. Returns
     * the response code from the connection.
     *
     */
    public int setCookies(final String hubUserName, final String hubPassword)
            throws URISyntaxException, BDRestException {
        final ClientResource resource = createClientResource();
        try {
            resource.addSegment("j_spring_security_check");
            resource.setMethod(Method.POST);

            String encodedHubUser = null;
            String encodedHubPassword = null;
            try {
                encodedHubUser = URLEncoder.encode(hubUserName, "UTF-8");
                encodedHubPassword = URLEncoder.encode(hubPassword, "UTF-8");
            } catch (final UnsupportedEncodingException e) {
                throw new BDRestException("Could not encode the HubUsername and Password", e, resource);
            }

            final StringRepresentation stringRep = new StringRepresentation(
                    "j_username=" + encodedHubUser + "&j_password=" + encodedHubPassword);
            stringRep.setCharacterSet(CharacterSet.UTF_8);
            stringRep.setMediaType(MediaType.APPLICATION_WWW_FORM);
            resource.getRequest().setEntity(stringRep);

            handleRequest(resource);

            final int statusCode = resource.getResponse().getStatus().getCode();
            if (isSuccess(statusCode)) {
                final Series<CookieSetting> cookieSettings = resource.getResponse().getCookieSettings();
                final Series<Cookie> requestCookies = resource.getRequest().getCookies();
                if (cookieSettings != null && !cookieSettings.isEmpty()) {
                    for (final CookieSetting ck : cookieSettings) {
                        if (ck == null) {
                            continue;
                        }
                        final Cookie cookie = new Cookie();
                        cookie.setName(ck.getName());
                        cookie.setDomain(ck.getDomain());
                        cookie.setPath(ck.getPath());
                        cookie.setValue(ck.getValue());
                        cookie.setVersion(ck.getVersion());
                        requestCookies.add(cookie);
                    }
                }

                if (requestCookies == null || requestCookies.size() == 0) {
                    throw new BDRestException(
                            "Could not establish connection to '" + getBaseUrl() + "' . Failed to retrieve cookies",
                            resource);
                }
                cookies = requestCookies;
            } else {
                logger.trace("Response entity : " + resource.getResponse().getEntityAsText());
                Status status = resource.getResponse().getStatus();
                logger.trace("Status : " + status.toString(), status.getThrowable());
                throw new BDRestException(resource.getResponse().getStatus().toString(), status.getThrowable(),
                        resource);
            }
            return statusCode;
        } finally {
            releaseResource(resource);
        }
    }

    public Series<Cookie> getCookies() {
        return cookies;
    }

    /**
     * Get a resource from via an absolute URL.
     *
     * @param modelClass
     *            The type of the returned object.
     * @param url
     *            The absolute URL for the resource.
     * @return The resource gotten from the Hub.
     * @throws ResourceDoesNotExistException
     * @throws URISyntaxException
     * @throws IOException
     * @throws BDRestException
     */
    public <T> T httpGetFromAbsoluteUrl(final Class<T> modelClass, final String url)
            throws ResourceDoesNotExistException, URISyntaxException, IOException, BDRestException {
        final ClientResource resource = createClientResource(url);
        try {
            resource.setMethod(Method.GET);
            handleRequest(resource);

            logMessage(LogLevel.DEBUG, "Resource: " + resource);
            final int responseCode = getResponseStatusCode(resource);
            if (isSuccess(responseCode)) {
                return parseResponse(modelClass, resource);
            } else {
                throw new ResourceDoesNotExistException(
                        "Error getting resource from " + url + ": " + responseCode + "; " + resource.toString(),
                        resource);
            }
        } finally {
            releaseResource(resource);
        }
    }

    /**
     * Get a resource via a relative URL.
     *
     * This method uses (and, if necessary, initializes) the re-usable
     * ClientResource object.
     *
     * @param modelClass
     *            The type of the returned object.
     * @param urlSegments
     *            URL segments to add to the base Hub URL.
     * @param queryParameters
     *            Query parameters to add to the URL.
     * @return The resource gotten from the Hub.
     * @throws IOException
     * @throws ResourceDoesNotExistException
     * @throws URISyntaxException
     * @throws BDRestException
     */
    public <T> T httpGetFromRelativeUrl(final Class<T> modelClass, final List<String> urlSegments,
            final Set<AbstractMap.SimpleEntry<String, String>> queryParameters)
            throws IOException, ResourceDoesNotExistException, URISyntaxException, BDRestException {

        final ClientResource resource = createClientResource(urlSegments, queryParameters);
        try {
            resource.setMethod(Method.GET);
            handleRequest(resource);

            logMessage(LogLevel.DEBUG, "Resource: " + resource);
            final int responseCode = getResponseStatusCode(resource);

            if (isSuccess(responseCode)) {
                return parseResponse(modelClass, resource);
            } else {
                throw new ResourceDoesNotExistException("Error getting resource from relative url segments "
                        + urlSegments + " and query parameters " + queryParameters + "; errorCode: " + responseCode
                        + "; " + resource.toString(), resource);
            }
        } finally {
            releaseResource(resource);
        }
    }

    private void releaseResource(final ClientResource resource) {
        if (resource.getResponse() != null) {
            resource.getResponse().release();
        }
        resource.release();
    }

    public ClientResource createClientResource() throws URISyntaxException {
        return createClientResource(getBaseUrl());
    }

    public ClientResource createClientResource(final String providedUrl) throws URISyntaxException {
        // Should throw timeout exception after the specified timeout, default
        // is 2 minutes
        final ClientResource resource = createClientResource(client.getContext(), providedUrl);
        resource.setNext(client);
        resource.getRequest().setCookies(getCookies());
        return resource;
    }

    public abstract ClientResource createClientResource(final Context context, final String providedUrl)
            throws URISyntaxException;

    public ClientResource createClientResource(final List<String> urlSegments,
            final Set<AbstractMap.SimpleEntry<String, String>> queryParameters) throws URISyntaxException {
        final ClientResource resource = createClientResource();

        for (final String urlSegment : urlSegments) {
            resource.addSegment(urlSegment);
        }
        for (final AbstractMap.SimpleEntry<String, String> queryParameter : queryParameters) {
            resource.addQueryParameter(queryParameter.getKey(), queryParameter.getValue());
        }
        return resource;
    }

    public int getResponseStatusCode(final ClientResource resource) {
        return resource.getResponse().getStatus().getCode();
    }

    public boolean isSuccess(final int responseCode) {
        return responseCode >= 200 && responseCode < 300;
    }

    public void handleRequest(final ClientResource resource) throws BDRestException {
        final boolean debugLogging = isDebugLogging();
        if (debugLogging) {
            logMessage(LogLevel.TRACE, "Resource : " + resource.toString());
            logRestletRequestOrResponse(resource.getRequest());
        }

        final CookieHandler originalCookieHandler = CookieHandler.getDefault();
        try {
            if (originalCookieHandler != null) {
                if (debugLogging) {
                    logMessage(LogLevel.TRACE, "Setting Cookie Handler to NULL");
                }
                CookieHandler.setDefault(null);
            }
            resource.handle();
        } catch (final ResourceException e) {
            throw new BDRestException("Problem connecting to the Hub server provided.", e, resource);
        } finally {
            if (originalCookieHandler != null) {
                if (debugLogging) {
                    logMessage(LogLevel.TRACE,
                            "Setting Original Cookie Handler : " + originalCookieHandler.toString());
                }
                CookieHandler.setDefault(originalCookieHandler);
            }
        }

        if (debugLogging) {
            logRestletRequestOrResponse(resource.getResponse());
            logMessage(LogLevel.TRACE, "Status Code : " + resource.getResponse().getStatus().getCode());
        }
    }

    private boolean isDebugLogging() {
        return logger != null && logger.getLogLevel() == LogLevel.TRACE;
    }

    public String readResponseAsString(final Response response) throws IOException {
        return response.getEntityAsText();
    }

    private void logRestletRequestOrResponse(final Message requestOrResponse) {
        if (isDebugLogging()) {
            final String requestOrResponseName = requestOrResponse.getClass().getSimpleName();
            logMessage(LogLevel.TRACE, requestOrResponseName + " : " + requestOrResponse.toString());

            if (!requestOrResponse.getAttributes().isEmpty()) {
                logMessage(LogLevel.TRACE, requestOrResponseName + " attributes : ");
                for (final Entry<String, Object> requestAtt : requestOrResponse.getAttributes().entrySet()) {
                    logMessage(LogLevel.TRACE, "Attribute key : " + requestAtt.getKey());
                    logMessage(LogLevel.TRACE, "Attribute value : " + requestAtt.getValue());
                    logMessage(LogLevel.TRACE, "");
                }
                @SuppressWarnings("unchecked")
                final Series<? extends NamedValue> responseheaders = (Series<? extends NamedValue>) requestOrResponse
                        .getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS);
                if (responseheaders != null) {
                    logMessage(LogLevel.TRACE, requestOrResponseName + " headers : ");
                    for (final NamedValue header : responseheaders) {
                        if (header == null) {
                            logMessage(LogLevel.TRACE, "received a null header");
                        } else {
                            logMessage(LogLevel.TRACE, "Header name : " + header.getName());
                            logMessage(LogLevel.TRACE, "Header value : " + header.getValue());
                            logMessage(LogLevel.TRACE, "");
                        }
                    }
                } else {
                    logMessage(LogLevel.TRACE, requestOrResponseName + " headers : NONE");
                }
            } else {
                logMessage(LogLevel.TRACE, requestOrResponseName + " does not have any attributes/headers.");
            }
        }
    }

    /**
     * Clears the previously set System properties I.E. https.proxyHost,
     * https.proxyPort, http.proxyHost, http.proxyPort, http.nonProxyHosts
     *
     */
    private void cleanUpOldProxySettings() {
        System.clearProperty("http.proxyHost");
        System.clearProperty("http.proxyPort");
        System.clearProperty("http.nonProxyHosts");

        AuthenticatorUtil.resetAuthenticator();
    }

    private <T> T parseResponse(final Class<T> modelClass, final ClientResource resource) throws IOException {
        final String response = readResponseAsString(resource.getResponse());
        final JsonParser parser = new JsonParser();
        final JsonObject json = parser.parse(response).getAsJsonObject();

        final T modelObject = gson.fromJson(json, modelClass);
        return modelObject;
    }

    private void logMessage(final LogLevel level, final String txt) {
        if (logger != null) {
            if (level == LogLevel.ERROR) {
                logger.error(txt);
            } else if (level == LogLevel.WARN) {
                logger.warn(txt);
            } else if (level == LogLevel.INFO) {
                logger.info(txt);
            } else if (level == LogLevel.DEBUG) {
                logger.debug(txt);
            } else if (level == LogLevel.TRACE) {
                logger.trace(txt);
            }
        }
    }

    @Override
    public String toString() {
        return "RestConnection [baseUrl=" + baseUrl + "]";
    }

    public String httpPostFromAbsoluteUrl(final String url, final Representation content)
            throws ResourceDoesNotExistException, URISyntaxException, IOException, BDRestException {

        final ClientResource resource = createClientResource(url);
        try {
            resource.setMethod(Method.POST);
            resource.getRequest().setEntity(content);
            return handleHttpPost(resource);
        } finally {
            releaseResource(resource);
        }
    }

    public String httpPostFromRelativeUrl(final List<String> urlSegments, final Representation content)
            throws IOException, ResourceDoesNotExistException, URISyntaxException, BDRestException {
        final Set<SimpleEntry<String, String>> queryParameters = new HashSet<>();
        return httpPostFromRelativeUrl(urlSegments, queryParameters, content);
    }

    public String httpPostFromRelativeUrl(final List<String> urlSegments,
            final Set<AbstractMap.SimpleEntry<String, String>> queryParameters, final Representation content)
            throws IOException, ResourceDoesNotExistException, URISyntaxException, BDRestException {

        final ClientResource resource = createClientResource(urlSegments, queryParameters);
        try {
            resource.setMethod(Method.POST);
            resource.getRequest().setEntity(content);
            return handleHttpPost(resource);
        } finally {
            releaseResource(resource);
        }
    }

    public String handleHttpPost(final ClientResource resource)
            throws IOException, ResourceDoesNotExistException, URISyntaxException, BDRestException {
        handleRequest(resource);

        logMessage(LogLevel.DEBUG, "Resource: " + resource);
        final int responseCode = getResponseStatusCode(resource);

        if (isSuccess(responseCode)) {
            if (resource.getResponse().getAttributes() == null
                    || resource.getResponse().getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS) == null) {
                throw new ResourceDoesNotExistException(
                        "Could not get the response headers after creating the resource.", resource);
            }

            if (responseCode != 201) {
                return "";
            } else {
                @SuppressWarnings("unchecked")
                final Series<? extends NamedValue> responseHeaders = (Series<? extends NamedValue>) resource
                        .getResponse().getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS);
                final NamedValue resourceUrl = responseHeaders.getFirst("location", true);
                if (resourceUrl == null) {
                    throw new ResourceDoesNotExistException(
                            "Could not get the resource URL from the response headers.", resource);
                }
                final String value = (String) resourceUrl.getValue();
                return value;
            }
        } else {
            throw new ResourceDoesNotExistException(
                    "There was a problem creating the resource. Error Code: " + responseCode, resource);
        }
    }

    public Gson getGson() {
        return gson;
    }

    public JsonParser getJsonParser() {
        return jsonParser;
    }

}