com.urbancode.ud.client.UDRestClient.java Source code

Java tutorial

Introduction

Here is the source code for com.urbancode.ud.client.UDRestClient.java

Source

/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Deploy
* (c) Copyright IBM Corporation 2011, 2016. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*/
package com.urbancode.ud.client;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;

import org.apache.commons.codec.EncoderException;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder;
import com.urbancode.commons.util.Check;
import com.urbancode.commons.util.IO;
import com.urbancode.commons.web.util.PercentCodec;

@SuppressWarnings("deprecation") // Triggered by DefaultHttpClient
public class UDRestClient {

    //**********************************************************************************************
    // CLASS
    //**********************************************************************************************
    final static private Logger log = Logger.getLogger(UDRestClient.class);

    public static final int WAIT_RANGE_MIN_DEFAULT = 200; // in milliseconds (ms)
    public static final int WAIT_RANGE_MAX_DEFAULT = 1000; // in milliseconds (ms)
    public static final int RETRY_LIMIT_DEFAULT = 3;

    /**
     * Create an HTTP client configuring trustAllCerts using agent environment variables
     *
     * @param user The username to associate with the http client connection.
     * @param password The password of the username used to associate with the http client connection.
     * @see #createHttpClient(String,String,boolean)
     *
     * @return DefaultHttpClient
     */
    static public DefaultHttpClient createHttpClient(String user, String password) {
        String verifyServerIdentityString = System.getenv().get("UC_TLS_VERIFY_CERTS");
        Boolean verifiedCerts = Boolean.valueOf(verifyServerIdentityString);
        return createHttpClient(user, password, !verifiedCerts);
    }

    /**
     * Create an HTTP client configured with credentials and any proxy settings
     * from the environment.
     *
     * @param user The username to associate with the http client connection.
     * @param password The password of the username used to associate with the http client connection.
     * @param trustAllCerts Boolean to trust or deny all insecure certifications with the http client.
     *
     * @return DefaultHttpClient
     */
    static public DefaultHttpClient createHttpClient(String user, String password, boolean trustAllCerts) {
        HttpClientBuilder builder = new HttpClientBuilder();
        builder.setPreemptiveAuthentication(true);
        builder.setUsername(user);
        builder.setPassword(password);
        builder.setTrustAllCerts(trustAllCerts);

        if (!StringUtils.isEmpty(System.getenv("PROXY_HOST"))
                && StringUtils.isNumeric(System.getenv("PROXY_PORT"))) {
            log.debug("Configuring proxy settings.");
            builder.setProxyHost(System.getenv("PROXY_HOST"));
            builder.setProxyPort(Integer.valueOf(System.getenv("PROXY_PORT")));
        }

        if (!StringUtils.isEmpty(System.getenv("PROXY_USERNAME"))
                && !StringUtils.isEmpty(System.getenv("PROXY_PASSWORD"))) {
            log.debug("Configuring proxy settings.");
            builder.setProxyUsername(System.getenv("PROXY_USERNAME"));
            builder.setProxyPassword(System.getenv("PROXY_PASSWORD"));
        }

        return builder.buildClient();
    }

    //**********************************************************************************************
    // INSTANCE
    //**********************************************************************************************
    final protected URI url;
    final protected String clientUser;
    final protected String clientPassword;
    final protected DefaultHttpClient client;

    //----------------------------------------------------------------------------------------------
    /**
     * @param url The url of the UrbanCode Deploy server.
     * @param clientUser The username of the UrbanCode Deploy user.
     * @param clientPassword The password of the UrbanCode Deploy user.
     *
     */
    public UDRestClient(URI url, String clientUser, String clientPassword) {
        this.url = url;
        this.clientUser = clientUser;
        this.clientPassword = clientPassword;
        client = createHttpClient(clientUser, clientPassword);
    }

    //----------------------------------------------------------------------------------------------
    /**
     * @param url The url of the UrbanCode Deploy server.
     * @param clientUser The username of the UrbanCode Deploy user.
     * @param clientPassword The password of the UrbanCode Deploy user.
     * @param trustAllCerts Boolean to trust or deny all insecure certifications with the http client.
     *
     */
    public UDRestClient(URI url, String clientUser, String clientPassword, boolean trustAllCerts) {
        this.url = url;
        this.clientUser = clientUser;
        this.clientPassword = clientPassword;
        client = createHttpClient(clientUser, clientPassword, trustAllCerts);
    }

    //----------------------------------------------------------------------------------------------
    /**
     * Create a client with a supplied HTTP client. The client must be
     * configured with any proxy settings, credentials, etc required for correct
     * operation. Subclasses will not be able to access the username or
     * password if the instance is constructed with this method.
     *
     * @param url The url of the UrbanCode Deploy server.
     * @param client The HTTP client builder to make a REST call against the UrbanCode Deploy server.
     *
     */
    public UDRestClient(URI url, DefaultHttpClient client) {
        Check.nonNull(url);
        Check.nonNull(client);
        this.url = url;
        this.client = client;
        clientUser = null;
        clientPassword = null;
    }

    //----------------------------------------------------------------------------------------------
    /**
     * @param name The name of the new property being created.
     * @param value The value of the new property being created.
     * @param isSecure Boolean of whether or not the value should be secured.
     *
     * @return JSONObject
     *
     * @throws JSONException
     */
    protected JSONObject createNewPropertyJSON(String name, String value, boolean isSecure) throws JSONException {
        JSONObject result = new JSONObject();

        result.put("name", name).put("value", value).put("secure", isSecure);
        return result;
    }

    //----------------------------------------------------------------------------------------------
    /**
     * @param propArray A JSONArray object of properties each with a 'name' and 'value'.
     *
     * @return JSONObject
     *
     * @throws JSONException
     */
    protected JSONObject convertPropArrayToKeyValuePairs(JSONArray propArray) throws JSONException {
        JSONObject result = new JSONObject();
        for (int i = 0; i < propArray.length(); i++) {
            JSONObject prop = propArray.getJSONObject(i);
            JSONObject propDefJSON = null;
            try {
                propDefJSON = (JSONObject) prop.get("propValue");
            } catch (JSONException e) {
                //default property values have not been overwritten; fetching defaults
                propDefJSON = (JSONObject) prop.get("propDef");
            }

            String propKey = (String) propDefJSON.get("name");
            String propValue = (String) propDefJSON.get("value");
            result.put(propKey, propValue);
        }
        return result;
    }

    /**
     * @param object The object of which the StringEntity being retrieved.
     *
     * @return StringEntity
     */
    protected StringEntity getStringEntity(Object object) {
        return new StringEntity(object.toString(), "UTF-8");
    }

    //----------------------------------------------------------------------------------------------
    /**
     * @param response The response created by an HTTPClient call.
     *
     * @return String
     */
    protected String getBody(HttpResponse response) throws IOException {
        String result = null;
        StringBuilder builder = new StringBuilder();

        if (response.getStatusLine().getStatusCode() != 204) {
            InputStream body = response.getEntity().getContent();
            if (body != null) {
                Reader reader = IO.reader(body, IO.utf8());
                try {
                    IO.copy(reader, builder);
                } finally {
                    reader.close();
                }
            }
            result = builder.toString();
        }
        // Else if the response was "No Content" we should pass back null

        return result;
    }

    //----------------------------------------------------------------------------------------------
    /**
     * @param response The response created by an HTTPClient call.
     *
     * @throws IOException
     */
    protected void discardBody(HttpResponse response) throws IOException {
        EntityUtils.consumeQuietly(response.getEntity());
    }

    //----------------------------------------------------------------------------------------------
    /**
     * @param request The response created by an HTTPClient call.
     *
     * @return HttpResponse
     */
    protected HttpResponse invokeMethod(HttpRequestBase request) throws IOException, ClientProtocolException {
        HttpResponse response = client.execute(request);
        int status = response.getStatusLine().getStatusCode();
        if (status > 299) {
            throw new IOException(String.format("%d %s\n%s", response.getStatusLine().getStatusCode(),
                    response.getStatusLine().getReasonPhrase(), getBody(response)));
        }
        return response;
    }

    /**
     * Invokes the specified REST API method, with retry if the HTTP request
     * returned a status code of 409 (Conflict).
     * Uses default values for wait range and retry limit.
     *
     * @param request HTTP REST API method (for URI)
     * @param logMethodName [Optional, but recommended] if not null, logs message for each retry.
     *    and if the limit is reached. A value of null suppresses log messages.
     *
     * @return response from sending HTTP request
     *
     * @throws IOException
     */
    protected HttpResponse retryInvokeMethod(HttpRequestBase request, String logMethodName) throws IOException {

        // Call method with default values.
        return retryInvokeMethod(request, logMethodName, WAIT_RANGE_MIN_DEFAULT, WAIT_RANGE_MAX_DEFAULT,
                RETRY_LIMIT_DEFAULT);
    }

    /**
     * Invokes the specified REST API method, with retry if the HTTP request
     * returned a status code of 409 (Conflict).
     *
     * @param request HTTP REST API method (for URI)
     * @param logMethodName [Optional, but recommended] if not null, logs message for each retry.
     *    and if the limit is reached. A value of null suppresses log messages.
     * @param waitRangeMin minimum number of milliseconds to wait if the invoke needs to retry.
     * @param waitRangeMax maximum number of milliseconds to wait if the invoke needs to retry.
     * @param retryLimit maximum times to retry if invoke fails, does not retry if value is less than 1.
     *
     * @return response from sending HTTP request
     *
     * @throws IOException
     */
    protected HttpResponse retryInvokeMethod(HttpRequestBase request, String logMethodName, int waitRangeMin,
            int waitRangeMax, int retryLimit) throws IOException {

        // Ensure coded parameters are not invalid (use defaults if so).
        if (waitRangeMin < 0) {
            log.debug("Retry minimum wait [" + waitRangeMin + "] is invalid, using " + WAIT_RANGE_MIN_DEFAULT);
            waitRangeMin = WAIT_RANGE_MIN_DEFAULT;
        }
        if (waitRangeMax < 0) {
            log.debug("Retry maximum wait [" + waitRangeMax + "] is invalid, using " + WAIT_RANGE_MAX_DEFAULT);
            waitRangeMax = WAIT_RANGE_MAX_DEFAULT;
        }
        // Swap wait range if min/max are reversed
        if (waitRangeMax < waitRangeMin) {
            int tmpMax = waitRangeMax;
            waitRangeMax = waitRangeMin;
            waitRangeMin = tmpMax;
        }

        if (retryLimit < 0) {
            log.debug("Retry limit [" + retryLimit + "] is invalid, using " + RETRY_LIMIT_DEFAULT);
            retryLimit = RETRY_LIMIT_DEFAULT;
        }

        int waitRandomLimit = waitRangeMax - waitRangeMin + 1;
        int tryCount = 0;
        Random random = new Random();
        HttpResponse response = null;
        int status = 0;

        // Loop until we either break or throw an exception
        while (true) {
            response = client.execute(request); // send HTTP request
            status = response.getStatusLine().getStatusCode();

            // If status code is HTTP 409 Conflict error, retry if retry limit is not reached.
            if (status == 409) {
                ++tryCount;
                // Throw error if retry limit is reached.
                if (tryCount > retryLimit) {
                    // Log message if retry limit is not 0 and string parameter != null.
                    if (retryLimit > 0 && logMethodName != null) {
                        System.out.println("Reached retry limit");
                    }
                    // reached retry limit
                    throw new IOException(String.format("%d %s\n%s", response.getStatusLine().getStatusCode(),
                            response.getStatusLine().getReasonPhrase(), getBody(response)));
                }

                // Log retry if logMethodName was specified.
                if (logMethodName != null) {
                    System.out.println("Retrying " + logMethodName);
                }
                // wait random number of milliseconds within specified range
                long wait = random.nextInt(waitRandomLimit) + waitRangeMin;
                try {
                    Thread.sleep(wait);
                } catch (InterruptedException e1) {
                    System.out.println(e1);
                }
            } else if (status > 299) {
                throw new IOException(String.format("%d %s\n%s", response.getStatusLine().getStatusCode(),
                        response.getStatusLine().getReasonPhrase(), getBody(response)));
            } else {
                break; // return response from the HTTP request
            }
        }
        return response;
    }

    //----------------------------------------------------------------------------------------------
    /**
     * @param request
     */
    protected void releaseConnection(HttpRequestBase request) {
        request.releaseConnection();
    }

    /**
     * this method is misnamed; it encodes segments of user-inputted characters
     *
     * @param path String input to be encoded.
     *
     * @return String
     */
    protected String encodePath(String path) {
        String result;
        try {
            result = sanitizePathSegment(path);
        } catch (Exception e) {
            log.debug("the user input " + path + " could not be sanitized. defaulting to user input", e);
            result = path;
        }
        return result;
    }

    /**
     * @param path String input to be encoded.
     *
     * @return String
     *
     * @throws URISyntaxException
     * @throws EncoderException
     */
    protected String sanitizePathSegment(String path) throws URISyntaxException, EncoderException {
        PercentCodec encoder = new PercentCodec();
        return encoder.encode(path);
    }

    /**
     * Helper method to get a string/string map from a JSONObject so that JSON returned from REST
     * calls can be set as output properties.
     *
     * @param object JSONobject to be converted into a Property Map.
     *
     * @return Map
     */
    public Map<String, String> getJSONAsProperties(JSONObject object) {
        Map<String, String> result = new HashMap<String, String>();

        try {
            Iterator<?> iterator = object.keys();
            while (iterator.hasNext()) {
                String key = (String) iterator.next();
                Object value = object.get(key);
                if (!(value instanceof JSONObject) && !(value instanceof JSONArray)) {
                    result.put(key, String.valueOf(value));
                }
            }
        } catch (JSONException e) {
            throw new RuntimeException("Failed to convert from JSON to map", e);
        }

        return result;
    }
}