org.xframium.integrations.alm.ALMRESTConnection.java Source code

Java tutorial

Introduction

Here is the source code for org.xframium.integrations.alm.ALMRESTConnection.java

Source

/*******************************************************************************
 * xFramium
 *
 * Copyright 2017 by Moreland Labs LTD (http://www.morelandlabs.com)
 *
 * Some open source application is free software: you can redistribute 
 * it and/or modify it under the terms of the GNU General Public 
 * License as published by the Free Software Foundation, either 
 * version 3 of the License, or (at your option) any later version.
 *  
 * Some open source application is distributed in the hope that it will 
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *  
 * You should have received a copy of the GNU General Public License
 * along with xFramium.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @license GPL-3.0+ <http://spdx.org/licenses/GPL-3.0+>
 *******************************************************************************/
package org.xframium.integrations.alm;

import java.awt.HeadlessException;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.util.Base64;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xframium.integrations.alm.entity.ALMAttachment;
import org.xframium.integrations.alm.entity.ALMDefect;

// TODO: Auto-generated Javadoc
/**
 * Represents a connection to an ALM Server
 */
public class ALMRESTConnection {

    /** The log. */
    private static Log log = LogFactory.getLog(ALMRESTConnection.class);

    /** The field template. */
    private static String fieldTemplate = "--%1$s\r\n" + "Content-Disposition: form-data; name=\"%2$s\" \r\n\r\n"
            + "%3$s" + "\r\n";

    /** The file data prefix template. */
    private static String fileDataPrefixTemplate = "--%1$s\r\n"
            + "Content-Disposition: form-data; name=\"%2$s\"; filename=\"%3$s\"\r\n" + "Content-Type: %4$s\r\n\r\n";

    /** The boundary. */
    private static String boundary = "xALM-Boundary";

    /** The cookies. */
    protected Map<String, String> cookies = new HashMap<String, String>(20);
    /**
     * This is the URL to the ALM application. Make sure that there is no slash at the end.
     */
    protected String serverUrl;

    /** The domain. */
    protected String domain;

    /** The project. */
    protected String project;

    /**
     * Instantiates a new ALM REST connection.
     *
     * @param serverUrl The URL of the ALM Server instance
     * @param domain The domain 
     * @param project the project
     */
    public ALMRESTConnection(String serverUrl, String domain, String project) {
        this.serverUrl = serverUrl;
        this.domain = domain;
        this.project = project;
    }

    private String attachWithOctetStream(String entityUrl, byte[] fileData, String filename) throws Exception {

        Map<String, String> requestHeaders = new HashMap<String, String>();
        requestHeaders.put("Slug", filename);
        requestHeaders.put("Content-Type", "application/octet-stream");

        ALMResponse response = httpPost(entityUrl + "/attachments", fileData, requestHeaders);

        if (response.getStatusCode() != HttpURLConnection.HTTP_CREATED) {
            throw new Exception(response.toString());
        }

        return response.getResponseHeaders().get("Location").iterator().next();
    }

    private String attachWithMultipart(String entityUrl, byte[] fileData, String contentType, String filename,
            String description) throws Exception {

        // Note the order - extremely important:
        // Filename and description before file data.
        // Name of file in file part and filename part value MUST MATCH.
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        bytes.write(String.format(fieldTemplate, boundary, "filename", filename).getBytes());
        bytes.write(String.format(fieldTemplate, boundary, "description", description).getBytes());
        bytes.write(("\r\n--" + boundary + "--").getBytes());
        bytes.write(fileData);
        bytes.write(String.format(fileDataPrefixTemplate, boundary, "file", filename, contentType).getBytes());
        bytes.close();

        Map<String, String> requestHeaders = new HashMap<String, String>();

        requestHeaders.put("Content-Type", "multipart/form-data; boundary=" + boundary);

        ALMResponse response = httpPost(entityUrl + "/attachments", bytes.toByteArray(), requestHeaders);

        if (response.getStatusCode() != HttpURLConnection.HTTP_CREATED) {
            throw new Exception(response.toString());
        }

        return response.getResponseHeaders().get("Location").iterator().next();
    }

    /**
     * Builds the entity collection url.
     *
     * @param entityType the entity type
     * @return the string
     */
    public String buildEntityCollectionUrl(String entityType) {
        return buildUrl("rest/domains/" + domain + "/projects/" + project + "/" + entityType + "s");
    }

    /**
     * Builds the project url.
     *
     * @param path the path
     * @return the string
     */
    public String buildProjectUrl(String path) {
        return buildUrl("rest/domains/" + domain + "/projects/" + project + "/" + path);
    }

    /**
     * Builds the base server url.
     *
     * @param path            on the server to use
     * @return a url on the server for the path parameter
     */
    public String buildUrl(String path) {

        return String.format("%1$s/%2$s", serverUrl, path);
    }

    /**
     * Performs an HTTP Requests using PUT
     *
     * @param url the url
     * @param data the data
     * @param headers the headers
     * @return the ALM response
     * @throws Exception the exception
     */
    public ALMResponse httpPut(String url, byte[] data, Map<String, String> headers) throws Exception {

        return doHttp("PUT", url, null, data, headers, cookies);
    }

    /**
     * Performs an HTTP Requests using POST
     *
     * @param url the url
     * @param data the data
     * @param headers the headers
     * @return the ALM response
     * @throws Exception the exception
     */
    public ALMResponse httpPost(String url, byte[] data, Map<String, String> headers) throws Exception {

        return doHttp("POST", url, null, data, headers, cookies);
    }

    /**
     * Performs an HTTP Requests using DELETE
     *
     * @param url the url
     * @param headers the headers
     * @return the ALM response
     * @throws Exception the exception
     */
    public ALMResponse httpDelete(String url, Map<String, String> headers) throws Exception {

        return doHttp("DELETE", url, null, null, headers, cookies);
    }

    /**
     * Performs an HTTP Requests using GET
     *
     * @param url the url
     * @param queryString the query string
     * @param headers the headers
     * @return the ALM response
     * @throws Exception the exception
     */
    public ALMResponse httpGet(String url, String queryString, Map<String, String> headers) throws Exception {

        return doHttp("GET", url, queryString, null, headers, cookies);
    }

    private ALMResponse doHttp(String type, String url, String queryString, byte[] data,
            Map<String, String> headers, Map<String, String> cookies) throws Exception {
        log.warn("1.0.4");

        if ((queryString != null) && !queryString.isEmpty())
            url += "?" + queryString;

        if (log.isInfoEnabled())
            log.info("Executing " + type + ": to " + url);

        HttpURLConnection con = null;

        if (Boolean.parseBoolean(System.getProperty("alm.bypassProxy", "false")))
            con = (HttpURLConnection) new URL(url).openConnection(Proxy.NO_PROXY);
        else
            con = (HttpURLConnection) new URL(url).openConnection();

        con.setRequestMethod(type);
        String cookieString = getCookieString();

        if (log.isInfoEnabled())
            log.info("Cookies: " + cookieString);

        prepareHttpRequest(con, headers, data, cookieString);
        con.connect();
        ALMResponse ret = retrieveHtmlResponse(con);

        if (log.isInfoEnabled())
            log.info("Return Value: " + ret);

        updateCookies(ret);

        return ret;
    }

    /**
     * Prepare http request.
     *
     * @param con            connection to set the headers and bytes in
     * @param headers            to use in the request, such as content-type
     * @param bytes            the actual data to post in the connection.
     * @param cookieString            the cookies data from clientside, such as lwsso, qcsession,
     *            jsession etc.
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private void prepareHttpRequest(HttpURLConnection con, Map<String, String> headers, byte[] bytes,
            String cookieString) throws IOException {

        String contentType = null;

        // attach cookie information if such exists
        if ((cookieString != null) && !cookieString.isEmpty())
            con.setRequestProperty("Cookie", cookieString);

        // send data from headers
        if (headers != null) {

            contentType = headers.remove("Content-Type");

            Iterator<Entry<String, String>> headersIterator = headers.entrySet().iterator();
            while (headersIterator.hasNext()) {
                Entry<String, String> header = headersIterator.next();
                con.setRequestProperty(header.getKey(), header.getValue());
            }
        }

        if ((bytes != null) && (bytes.length > 0)) {

            con.setDoOutput(true);

            if (contentType != null) {
                con.setRequestProperty("Content-Type", contentType);
            }

            OutputStream out = con.getOutputStream();
            out.write(bytes);
            out.flush();
            out.close();
        }
    }

    private ALMResponse retrieveHtmlResponse(HttpURLConnection con) throws Exception {

        ALMResponse ret = new ALMResponse();

        ret.setStatusCode(con.getResponseCode());
        ret.setResponseHeaders(con.getHeaderFields());

        InputStream inputStream;

        try {
            inputStream = con.getInputStream();
        }

        catch (Exception e) {
            inputStream = con.getErrorStream();
            ret.setFailure(e);
        }

        ByteArrayOutputStream container = new ByteArrayOutputStream();

        byte[] buf = new byte[1024];
        int read;
        while ((read = inputStream.read(buf, 0, 1024)) > 0) {
            container.write(buf, 0, read);
        }

        ret.setResponseData(container.toByteArray());

        return ret;
    }

    /**
     * Update cookies.
     *
     * @param response the response
     */
    private void updateCookies(ALMResponse response) {

        Iterable<String> newCookies = response.getResponseHeaders().get("Set-Cookie");
        if (newCookies != null) {

            for (String cookie : newCookies) {
                if (cookie == null || cookie.isEmpty())
                    continue;

                int equalIndex = cookie.indexOf('=');
                int semicolonIndex = cookie.indexOf(';');

                if (equalIndex < 0 || semicolonIndex < 0)
                    continue;

                String cookieKey = cookie.substring(0, equalIndex);
                String cookieValue = cookie.substring(equalIndex + 1, semicolonIndex);

                cookies.put(cookieKey, cookieValue);
            }
        }
    }

    private String getCookieString() {

        StringBuilder sb = new StringBuilder();

        if (!cookies.isEmpty()) {

            Set<Entry<String, String>> cookieEntries = cookies.entrySet();
            for (Entry<String, String> entry : cookieEntries) {
                sb.append(entry.getKey()).append("=").append(entry.getValue()).append(";");
            }
        }

        String ret = sb.toString();

        return ret;
    }

    /**
     * Allows the user to login to ALM by first determining the authentication point
     *
     * @param username the username
     * @param password the password
     * @return true if authenticated at the end of this method.
     * @throws Exception             convenience method used by other examples to do their login
     */
    public boolean login(String username, String password) throws Exception {

        String authenticationPoint = this.isAuthenticated();
        if (authenticationPoint != null) {
            return this.login(authenticationPoint, username, password);
        }
        return true;
    }

    /**
     * Allows the user to login to ALM using the specified authentication point
     *
     * @param loginUrl            to authenticate at
     * @param username the username
     * @param password the password
     * @return true on operation success, false otherwise
     * @throws Exception             Logging in to our system is standard http login (basic
     *             authentication), where one must store the returned cookies
     *             for further use.
     */
    private boolean login(String loginUrl, String username, String password) throws Exception {

        // create a string that lookes like:
        // "Basic ((username:password)<as bytes>)<64encoded>"
        byte[] credBytes = (username + ":" + password).getBytes();
        String credEncodedString = "Basic " + new String(Base64.getEncoder().encode(credBytes));

        Map<String, String> map = new HashMap<String, String>();
        map.put("Authorization", credEncodedString);

        log.info(map);

        ALMResponse response = httpGet(loginUrl, null, map);

        boolean ret = response.getStatusCode() == HttpURLConnection.HTTP_OK;

        if (ret) {
            Map<String, String> requestHeaders = new HashMap<String, String>();

            // As can be seen in the implementation below, creating an entity
            // is simply posting its xml into the correct collection.
            response = httpPost(buildUrl("rest/site-session"), null, requestHeaders);
        }

        return ret;
    }

    /**
     * Adds a defect to ALM
     *
     * @param almDefect the alm defect
     * @return the string
     * @throws Exception the exception
     */
    public String addDefect(ALMDefect almDefect) throws Exception {
        String defectUrl = createEntity(buildEntityCollectionUrl(almDefect.getEntityType()), almDefect.toXML());

        if (almDefect.getAttachments() != null) {
            for (ALMAttachment a : almDefect.getAttachments()) {
                byte[] attachmentData = a.getFileData() != null ? a.getFileData() : readFile(a.getFileName());
                if (attachmentData != null)
                    attachWithOctetStream(defectUrl, attachmentData, a.getFileName().getName());
            }
        }

        return defectUrl;
    }

    /**
     * Read file.
     *
     * @param currentFile the current file
     * @return the byte[]
     */
    private byte[] readFile(File currentFile) {
        byte[] buffer = new byte[512];
        int bytesRead = 0;

        InputStream inputStream = null;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        try {
            inputStream = new BufferedInputStream(new FileInputStream(currentFile));
            while ((bytesRead = inputStream.read(buffer)) > 0) {
                outputStream.write(buffer, 0, bytesRead);
            }

            return outputStream.toByteArray();
        } catch (Exception e) {
            log.warn("Could not read " + currentFile.getAbsolutePath() + " - " + e.getMessage());
            return null;
        } finally {
            try {
                inputStream.close();
            } catch (Exception e) {
            }
        }
    }

    /**
     * Given an entity, list all of the possible fields.
     *
     * @param entityType the entity type
     * @return the string
     * @throws Exception the exception
     */
    public String entityFields(String entityType) throws Exception {

        Map<String, String> requestHeaders = new HashMap<String, String>();
        requestHeaders.put("Content-Type", "application/xml");
        requestHeaders.put("Accept", "application/xml");

        ALMResponse response = httpGet(buildProjectUrl("customization/entities/" + entityType + "/fields"), null,
                requestHeaders);

        Exception failure = response.getFailure();
        if (failure != null) {
            throw failure;
        }

        return null;
    }

    private String createEntity(String collectionUrl, String postedEntityXml) throws Exception {

        Map<String, String> requestHeaders = new HashMap<String, String>();
        requestHeaders.put("Content-Type", "application/xml");
        requestHeaders.put("Accept", "application/xml");

        ALMResponse response = httpPost(collectionUrl, postedEntityXml.getBytes(), requestHeaders);

        Exception failure = response.getFailure();
        if (failure != null) {
            throw failure;
        }

        String entityUrl = response.getResponseHeaders().get("Location").iterator().next();

        return entityUrl;
    }

    /**
     * Logout of ALM
     *
     * @return true if logout successful
     * @throws Exception             close session on server and clean session cookies on client
     */
    public boolean logout() throws Exception {
        ALMResponse response = httpGet(buildUrl("authentication-point/logout"), null, null);
        cookies.clear();

        return (response.getStatusCode() == HttpURLConnection.HTTP_OK);

    }

    /**
     * Checks if is authenticated.
     *
     * @return null if authenticated.<br>
     *         a url to authenticate against if not authenticated.
     * @throws Exception the exception
     */
    public String isAuthenticated() throws Exception {

        String isAuthenticateUrl = buildUrl("rest/is-authenticated");
        String ret;

        ALMResponse response = httpGet(isAuthenticateUrl, null, null);
        int responseCode = response.getStatusCode();

        // if already authenticated
        if (responseCode == HttpURLConnection.HTTP_OK) {

            ret = null;
        }

        // if not authenticated - get the address where to authenticate
        // via WWW-Authenticate
        else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {

            Iterable<String> authenticationHeader = response.getResponseHeaders().get("WWW-Authenticate");

            String newUrl = authenticationHeader.iterator().next().split("=")[1];
            newUrl = newUrl.replace("\"", "");
            newUrl += "/authenticate";
            ret = newUrl;
        }

        // Not ok, not unauthorized. An error, such as 404, or 500
        else {

            throw response.getFailure();
        }

        return ret;
    }

}