com.produban.cloudfoundry.bosh.bosh_javaclient.BoshClientImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.produban.cloudfoundry.bosh.bosh_javaclient.BoshClientImpl.java

Source

/*
Copyright 2015 Produban
    
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.produban.cloudfoundry.bosh.bosh_javaclient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.codec.binary.Base64;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * This class implements a very basic BOSH client, that interrogates a BOSH
 * director to get some useful information.
 *
 * @author Pablo Alonso Rodriguez (Produban)
 *
 */
public class BoshClientImpl {

    /**
     * The IP or host name of the BOSH director
     */
    private String host;

    /**
     * The port where the director is listening at. It is 25555 by default.
     */
    private int port = 25555;

    /**
     * BOSH user name
     */
    private String username;

    /**
     * BOSH password
     */
    private String password;

    /**
     * Regular expression to look for the task number on the Location field of the 302 HTTP redirect.
     */
    private static final String REGEX_TASK_LOCATION = "^https://(?:[^/]+)/tasks/(?<taskNumber>[0-9]+)$";

    /**
     * This method sets up {@link HttpsURLConnection} so that no certificate or
     * hostname check is performed.
     */
    private void disableSSLChecks() {
        try {
            TrustManager trustAllCertificates = new X509TrustManager() {

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                    // I do nothing because the way to say "OK" is not to throw
                    // a CertificateException

                }

                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                    // I do nothing because the way to say "OK" is not to throw
                    // a CertificateException

                }
            };

            TrustManager[] trustAllCertificatesArray = { trustAllCertificates };
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCertificatesArray, new java.security.SecureRandom());

            HostnameVerifier allHostsValid = new HostnameVerifier() {

                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true; // I always say 'OK'
                }
            };

            HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
            HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);

        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            throw new IllegalStateException("Something strange happened here", e);
        }
    }

    /**
     * This method opens a {@link HttpsURLConnection} to the BOSH director using
     * the given path and sets up 'GET' method and authentication.
     *
     * @param path
     *            the path (starting with '/')
     * @return the {@link HttpsURLConnection} object
     * @throws MalformedURLException
     * @throws IOException
     * @throws ProtocolException
     * @throws UnsupportedEncodingException
     */
    private HttpsURLConnection setupHttpsUrlConnection(String path)
            throws MalformedURLException, IOException, ProtocolException, UnsupportedEncodingException {
        URL requestURL = new URL("https://" + this.host + ":" + this.port + path);
        HttpsURLConnection httpsURLConnection = (HttpsURLConnection) requestURL.openConnection();
        httpsURLConnection.setRequestMethod("GET");
        httpsURLConnection.setUseCaches(false);

        String credentials = this.username + ":" + this.password;
        String basicAuthHeaderValue = "Basic " + Base64.encodeBase64String(credentials.getBytes("UTF-8"));
        httpsURLConnection.setRequestProperty("Authorization", basicAuthHeaderValue);
        httpsURLConnection.setDoInput(true);
        return httpsURLConnection;
    }

    /**
     * This method reads the response body from a {@link HttpURLConnection}
     *
     * @param httpURLConnection the connection
     * @return the body
     * @throws IOException if there are I/O problems
     */
    private String getResponseBody(HttpURLConnection httpURLConnection) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()));
        String inputLine;
        StringBuffer responseBuffer = new StringBuffer();

        while ((inputLine = in.readLine()) != null) {
            responseBuffer.append(inputLine);
        }
        in.close();
        String responseBody = responseBuffer.toString();
        return responseBody;
    }

    /**
     * Constructor
     *
     * @param host the host of the BOSH director
     * @param port the port of the BOSH director (usually 25555)
     * @param username the BOSH user used to log in
     * @param password the BOSH password used to log in
     */
    public BoshClientImpl(String host, int port, String username, String password) {
        super();
        this.host = host;
        this.port = port;
        this.username = username;
        this.password = password;
        disableSSLChecks();
        HttpURLConnection.setFollowRedirects(false); // We do not want to follow redirects
    }

    /**
     * @return the host
     */
    public String getHost() {
        return host;
    }

    /**
     * @return the port
     */
    public int getPort() {
        return port;
    }

    /**
     * @return the username
     */
    public String getUsername() {
        return username;
    }

    /**
     * @return the password
     */
    public String getPassword() {
        return password;
    }

    /**
     * This method performs a simple HTTP request to the BOSH director and
     * returns the results
     *
     * @param path the path to query
     * @return a {@link BoshResponse} object with the status code, the headers and the body of the response
     * @throws BoshClientException if there are problems
     */
    public BoshResponse doSimpleRequest(String path) throws BoshClientException {
        try {
            String fixedPath;
            if (path.startsWith("/")) {
                fixedPath = path;
            } else {
                fixedPath = "/" + path;
            }
            HttpsURLConnection httpsURLConnection = setupHttpsUrlConnection(fixedPath);

            int statusCode = httpsURLConnection.getResponseCode();

            String responseBody = getResponseBody(httpsURLConnection);

            Map<String, List<String>> headers = httpsURLConnection.getHeaderFields();

            BoshResponse result = new BoshResponse(statusCode, headers, responseBody);

            return result;
        } catch (IOException e) {
            throw new BoshClientException("Error performing HTTP request (more info in the cause)", e);
        }
    }

    /**
     * This method launches a complex query, which requires a single BOSH task
     * to be launched and completed. This method performs the query, gathers the
     * task number, waits for its completion and returns its results.
     *
     * @param path path to query
     * @return the results of the launched tasks
     * @throws BoshClientException if there are problems
     */
    public String doComplexQuerySingleTask(String path) throws BoshClientException {
        BoshResponse initialRequestResults = doSimpleRequest(path);
        if (initialRequestResults.getStatusCode() != 302
                || !initialRequestResults.getHeaders().containsKey("Location")
                || initialRequestResults.getHeaders().get("Location").isEmpty()) {
            throw new BoshClientException(
                    "Unexpected response from BOSH director. Please check whether the path leads to a single task creation. Response results are: "
                            + initialRequestResults);
        }
        String locationValue = initialRequestResults.getHeaders().get("Location").get(0);
        Pattern taskNumberPattern = Pattern.compile(REGEX_TASK_LOCATION);
        Matcher taskNumberMatcher = taskNumberPattern.matcher(locationValue);
        boolean matches = taskNumberMatcher.matches();
        if (!matches) {
            throw new BoshClientException(
                    "Location header containing task number has an unexpected format: " + locationValue);
        }
        String taskNumberStr = taskNumberMatcher.group("taskNumber");
        boolean completed = false;
        while (!completed) {
            try {
                HttpsURLConnection taskRequestHttpsURLConnection = setupHttpsUrlConnection(
                        "/tasks/" + taskNumberStr);

                int taskRequestStatusCode = taskRequestHttpsURLConnection.getResponseCode();
                if (taskRequestStatusCode >= 300) {
                    throw new BoshClientException(
                            "Received code " + taskRequestStatusCode + " while requesting task status.");
                }
                String taskRequestResponse = getResponseBody(taskRequestHttpsURLConnection);

                try {
                    JSONObject taskJson = new JSONObject(taskRequestResponse);
                    String state = taskJson.optString("state");
                    if (state.equals("done")) {
                        completed = true;
                        break;
                    }
                } catch (JSONException e) {

                }

            } catch (IOException e) {
                throw new BoshClientException("Error while requesting task state or results", e);
            }

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new BoshClientException("Interrupted while sleeping", e);
            }
        }

        try {
            HttpsURLConnection taskResultHttpsURLConnection = setupHttpsUrlConnection(
                    "/tasks/" + taskNumberStr + "/output?type=result");
            int responseCode = taskResultHttpsURLConnection.getResponseCode();
            if (responseCode >= 300) {
                throw new BoshClientException(
                        "Unexpected " + responseCode + " response code while getting task results");
            }
            String result = getResponseBody(taskResultHttpsURLConnection);
            return result;

        } catch (IOException e) {
            throw new BoshClientException("Error while getting results", e);
        }

    }

    /**
     * It performs a request to get a list of deployments with some basic info
     * @return the response to the <code>/deployments</code> endpoint as-is. It includes the deployments and some basic info.
     * @throws BoshClientException
     */
    public String deploymentsInfo() throws BoshClientException {
        BoshResponse requestResult = doSimpleRequest("/deployments");
        int statusCode = requestResult.getStatusCode();
        if (statusCode >= 300) {
            throw new BoshClientException("Returned unexpected HTTP code: " + statusCode);
        }
        return requestResult.getBody();
    }

    /**
     * It performs a request to some basic information from the BOSH director
     * @return the response to the <code>/info</code> endpoint as-is.
     * @throws BoshClientException
     */
    public String info() throws BoshClientException {
        BoshResponse requestResult = doSimpleRequest("/info");
        int statusCode = requestResult.getStatusCode();
        if (statusCode >= 300) {
            throw new BoshClientException("Returned unexpected HTTP code: " + statusCode);
        }
        return requestResult.getBody();
    }

    /**
     * It performs a request to get info about VMs of a deployment
     *
     * @param Whether we get all the information (<code>?format=full</code>) or just some basic information.
     * @return the response as-is
     * @throws BoshClientException
     */
    public String vmsInfo(String deployment, boolean full) throws BoshClientException {
        if (!full) {
            BoshResponse requestResult = doSimpleRequest("/deployments/" + deployment + "/vms");
            int statusCode = requestResult.getStatusCode();
            if (statusCode >= 300) {
                throw new BoshClientException("Returned unexpected HTTP code: " + statusCode);
            }
            return requestResult.getBody();
        } else {
            return doComplexQuerySingleTask("/deployments/" + deployment + "/vms?format=full");
        }
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((host == null) ? 0 : host.hashCode());
        result = prime * result + ((password == null) ? 0 : password.hashCode());
        result = prime * result + port;
        result = prime * result + ((username == null) ? 0 : username.hashCode());
        return result;
    }

    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof BoshClientImpl)) {
            return false;
        }
        BoshClientImpl other = (BoshClientImpl) obj;
        if (host == null) {
            if (other.host != null) {
                return false;
            }
        } else if (!host.equals(other.host)) {
            return false;
        }
        if (password == null) {
            if (other.password != null) {
                return false;
            }
        } else if (!password.equals(other.password)) {
            return false;
        }
        if (port != other.port) {
            return false;
        }
        if (username == null) {
            if (other.username != null) {
                return false;
            }
        } else if (!username.equals(other.username)) {
            return false;
        }
        return true;
    }

}