com.ibm.team.build.internal.hjplugin.util.Helper.java Source code

Java tutorial

Introduction

Here is the source code for com.ibm.team.build.internal.hjplugin.util.Helper.java

Source

/*******************************************************************************
 * Copyright (c) 2014, 2016 IBM Corporation and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.ibm.team.build.internal.hjplugin.util;

import hudson.Util;
import hudson.model.ParameterValue;
import hudson.model.TaskListener;
import hudson.model.AbstractBuild;
import hudson.model.Job;
import hudson.model.ParameterDefinition;
import hudson.model.ParametersAction;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.Run;
import hudson.model.StringParameterDefinition;
import hudson.model.StringParameterValue;
import hudson.util.FormValidation;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import jenkins.model.Jenkins;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.apache.commons.io.IOUtils;

import com.ibm.team.build.internal.hjplugin.Messages;
import com.ibm.team.build.internal.hjplugin.RTCBuildResultAction;
import com.ibm.team.build.internal.hjplugin.RTCFacadeFactory;
import com.ibm.team.build.internal.hjplugin.RTCFacadeFactory.RTCFacadeWrapper;
import com.ibm.team.build.internal.hjplugin.RTCLoginInfo;

public class Helper {
    private static final Logger LOGGER = Logger.getLogger(Helper.class.getName());

    public static final String COMPONENTS_TO_EXCLUDE = "componentsToExclude"; //$NON-NLS-1$
    public static final String LOAD_RULES = "loadRules"; //$NON-NLS-1$
    public static final String COMPONENT_ID = "componentId"; //$NON-NLS-1$
    public static final String COMPONENT_NAME = "componentName"; //$NON-NLS-1$
    public static final String FILE_ITEM_ID = "fileItemId"; //$NON-NLS-1$
    public static final String FILE_PATH = "filePath"; //$NON-NLS-1$
    private static final String previousSnapshotOwner = "team_scm_snapshotOwner"; //$NON-NLS-1$

    /** 
     * merge two results, if both are errors only one stack trace can be included
     * @param firstCheck The first validation done
     * @param secondCheck The second validaiton done
     * @return The merge of the 2 validations with a concatenated message and the highest severity
     */
    public static FormValidation mergeValidationResults(FormValidation firstCheck, FormValidation secondCheck) {
        // we do not want to merge validation results of different kinds
        // so, return only the validation result that is more inclined towards validation failures
        // i.e. if you have combination of error, warning and ok return the validation result which is an error, if
        // no error then return the warning
        // error warning - return firstCheck //
        // error OK - return firstCheck //
        // warning OK - return firstCheck //
        // warning error - return secondCheck//
        // OK error - return secondCheck
        // OK warning - return secondCheck //
        if (!firstCheck.kind.equals(secondCheck.kind)) {
            if (firstCheck.kind.equals(FormValidation.Kind.ERROR)
                    || (firstCheck.kind.equals(FormValidation.Kind.WARNING)
                            && secondCheck.kind.equals(FormValidation.Kind.OK))) {
                return firstCheck;
            } else {
                return secondCheck;
            }
        }
        Throwable errorCause = secondCheck.getCause();
        if (errorCause == null) {
            errorCause = firstCheck.getCause();
        }
        String message;
        String firstMessage = firstCheck.getMessage();
        String secondMessage = secondCheck.getMessage();
        if (firstCheck.kind.equals(FormValidation.Kind.OK) && (firstMessage == null || firstMessage.isEmpty())) {
            message = secondCheck.renderHtml();
        } else if (secondCheck.kind.equals(FormValidation.Kind.OK)
                && (secondMessage == null || secondMessage.isEmpty())) {
            message = firstCheck.renderHtml();
        } else {
            message = firstCheck.renderHtml() + "<br/>" + secondCheck.renderHtml(); //$NON-NLS-1$
        }
        FormValidation.Kind kind;
        if (firstCheck.kind.equals(secondCheck.kind)) {
            kind = firstCheck.kind;
        } else if (firstCheck.kind.equals(FormValidation.Kind.OK)) {
            kind = secondCheck.kind;
        } else if (firstCheck.kind.equals(FormValidation.Kind.ERROR)
                || secondCheck.kind.equals(FormValidation.Kind.ERROR)) {
            kind = FormValidation.Kind.ERROR;
        } else {
            kind = FormValidation.Kind.WARNING;
        }

        return FormValidation.respond(kind, message);
    }

    public static String getStringBuildParameter(Run<?, ?> build, String property, TaskListener listener)
            throws IOException, InterruptedException {
        LOGGER.finest("Helper.getStringBuildProperty : Begin"); //$NON-NLS-1$
        if (LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.finest("Helper.getStringBuildProperty: Finding value for property '" + property //$NON-NLS-1$
                    + "' in the build environment variables."); //$NON-NLS-1$
        }
        String value = Util.fixEmptyAndTrim(build.getEnvironment(listener).get(property));
        if (value == null) {
            if (LOGGER.isLoggable(Level.FINEST)) {
                LOGGER.finest("Helper.getStringBuildProperty: Cannot find value for property '" + property //$NON-NLS-1$
                        + "' in the build environment variables. Looking in the build parameters."); //$NON-NLS-1$
            }
            // check if parameter is available from ParametersAction
            value = getValueFromParametersAction(build, property);
            if (value == null) {
                if (LOGGER.isLoggable(Level.FINEST)) {
                    LOGGER.finest("Helper.getStringBuildProperty: Cannot find value for property '" + property //$NON-NLS-1$
                            + "' in the build parameters."); //$NON-NLS-1$
                }
            }
        }
        if (LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.finest(
                    "Helper.getStringBuildProperty: Value for property '" + property + "' is '" + value + "'."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }
        return value;
    }

    /**
     * Reads the json text, specifying the components to exclude during load, from the given path on disk and validates
     * that required fields are provided.
     * 
     * @param componentsToExcludefilePath path to a file on disk
     * @return json text specifying the components to exclude during load
     * @throws Exception
     */
    public static String validateAndGetComponentsToExcludeJson(String componentsToExcludefilePath)
            throws Exception {
        String componentsToExcludeJsonStr = readJsonFromFile(componentsToExcludefilePath);
        if (componentsToExcludeJsonStr != null) {
            LOGGER.finer(
                    "Helper.validateAndGetComponentsToExcludeJson: stepping through and validating components to exclude json"); //$NON-NLS-1$
            JSONObject json = JSONObject.fromObject(componentsToExcludeJsonStr);
            if (!json.has(COMPONENTS_TO_EXCLUDE)) {
                throw new IllegalArgumentException(Messages.Helper_components_to_exclude_required());
            }
            JSONArray componentsArray = json.getJSONArray(COMPONENTS_TO_EXCLUDE);
            if (componentsArray.isEmpty()) {
                throw new IllegalArgumentException(Messages.Helper_components_to_exclude_required());
            }
            for (int i = 0; i < componentsArray.size(); i++) {
                JSONObject inner = (JSONObject) componentsArray.get(i);
                if (!inner.has(COMPONENT_ID) && !inner.has(COMPONENT_NAME)) {
                    throw new IllegalArgumentException(Messages.Helper_component_id_or_name_required());
                }
                if (!inner.has(COMPONENT_ID) && inner.has(COMPONENT_NAME)
                        && inner.getString(COMPONENT_NAME).trim().length() == 0) {
                    throw new IllegalArgumentException(
                            Messages.Helper_component_name_empty_components_to_exclude());
                }
            }
        }
        return componentsToExcludeJsonStr;
    }

    /**
     * Reads the json text, specifying the component-to-load-rule-mapping to be enforced during load, from the given
     * path on disk and validates that the required fields are provided.
     * 
     * 
     * @param loadRulesFilePath path to a file on disk
     * @return json text specifying the component-to-load-rule-mapping to be enforced during load
     * @throws Exception
     */
    public static String validateAndGetLoadRulesJson(String loadRulesFilePath) throws Exception {
        String loadRulesJsonStr = readJsonFromFile(loadRulesFilePath);
        if (loadRulesJsonStr != null) {
            LOGGER.finer("Helper.validateAndGetLoadRulesJson: stepping through and validating load rules json"); //$NON-NLS-1$
            JSONObject json = JSONObject.fromObject(loadRulesJsonStr);
            if (!json.has(LOAD_RULES)) {
                throw new IllegalArgumentException(Messages.Helper_load_rules_required());
            }
            JSONArray loadRulesArray = json.getJSONArray(LOAD_RULES);
            if (loadRulesArray.isEmpty()) {
                throw new IllegalArgumentException(Messages.Helper_load_rules_required());
            }
            for (int i = 0; i < loadRulesArray.size(); i++) {
                JSONObject inner = (JSONObject) loadRulesArray.get(i);
                if (!inner.has(COMPONENT_ID) && !inner.has(COMPONENT_NAME)) {
                    throw new IllegalArgumentException(Messages.Helper_component_id_or_name_required());
                }
                if (!inner.has(COMPONENT_ID) && inner.has(COMPONENT_NAME)
                        && inner.getString(COMPONENT_NAME).trim().length() == 0) {
                    throw new IllegalArgumentException(Messages.Helper_component_name_empty_load_rules());
                }
                if (!inner.has(FILE_ITEM_ID) && !inner.has(FILE_PATH)) {
                    throw new IllegalArgumentException(Messages.Helper_file_item_id_or_name_required());
                }
            }
        }
        return loadRulesJsonStr;
    }

    private static String getValueFromParametersAction(Run<?, ?> build, String key) {
        LOGGER.finest("Helper.getValueFromParametersAction : Begin"); //$NON-NLS-1$
        String value = null;
        for (ParametersAction paction : build.getActions(ParametersAction.class)) {
            List<ParameterValue> pValues = paction.getParameters();
            if (pValues == null) {
                continue;
            }
            for (ParameterValue pv : pValues) {
                if (pv instanceof StringParameterValue && pv.getName().equals(key)) {
                    value = Util.fixEmptyAndTrim((String) pv.getValue());
                    if (value != null) {
                        break;
                    }
                }
            }
            if (value != null) {
                break;
            }
        }
        if (LOGGER.isLoggable(Level.FINEST)) {
            if (value == null) {
                LOGGER.finest("Helper.getValueFromParametersAction : Unable to find a value for key : " + key); //$NON-NLS-1$
            } else {
                LOGGER.finest("Helper.getValueFromParametersAction : Found value : " + value + " for key : " + key); //$NON-NLS-1$//$NON-NLS-2$
            }
        }
        return value;
    }

    /**
     * Given a value that may be a parameter, resolve the parameter to its default value while trimming for whitespace.
     * If the resolved value of the parameter is <code>null</null>, then the value is returned as such, 
     * after trimming for whitespace.
     * If the given value is not a parameter, then trim whitespace and return it.
     * @param job
     * @param value - the value which could be a parameter. May be <code>null</code>
     * @param listener
     * @return a {@link String} which is the value to the parameter resolves to or the value itself, after trimming for whitespace
     */
    public static String parseConfigurationValue(Job<?, ?> job, String value, TaskListener listener) {
        LOGGER.finest("Helper.parseConfigurationValue for Job: Enter");
        // First nullify the value if it is empty
        value = Util.fixEmptyAndTrim(value);
        if (value == null) {
            return null;
        }
        String paramValue = value;
        // Check whether value itself is a job parameter
        if (Helper.isAParameter(value)) {
            paramValue = Helper.resolveJobParameter(job, value, listener);
            if (paramValue != null) {
                if (LOGGER.isLoggable(Level.FINEST)) {
                    LOGGER.finest("Found value for job parameter '" + value + "' : " + paramValue);
                }
                return paramValue;
            } else {
                paramValue = value;
            }
        }
        return paramValue;
    }

    /**
     * This method first checks whether the given <param>jobParameter</param> exists and is not null
     * If yes, then its value is returned. Otherwise, it checks whether <param>value</param> is actually
     * a job property. If yes and <param>resolveValue</param> is true, it is resolved and returned. 
     * Otherwise, the <param>value</param> is returned
     * @param build
     * @param jobParameter the job parameter to look for the value first. may be <code>null</code>
     * @param value
     * @param listener
     * @return the value of the parameter 
     * @throws IOException
     * @throws InterruptedException
     */
    public static String parseConfigurationValue(Run<?, ?> build, String jobParameter, String value,
            TaskListener listener) throws IOException, InterruptedException {
        // If a Job parameter exists and not null, then return it
        if (jobParameter != null) {
            String jobParameterValue = getStringBuildParameter(build, jobParameter, listener);
            if (jobParameterValue != null) {
                return jobParameterValue;
            }
        }

        // First trim the value
        value = Util.fixEmptyAndTrim(value);
        if (value == null) {
            return null;
        }

        // If jobParameterValue is null, check whether value itself is a job parameter
        // if it is and paramValue is not null, then return paramValue
        // otherwise just return the value provided in the job configuration as is.
        String paramValue = value;
        if (Helper.isAParameter(value)) {
            paramValue = Helper.resolveBuildParameter(build, value, listener);
            if (paramValue != null) {
                if (LOGGER.isLoggable(Level.FINEST)) {
                    LOGGER.finest("Found value for job parameter '" + value + "' : " + paramValue);
                }
            } else {
                paramValue = value;
            }
        }
        // Our value is not a parameter, return it trimmed (see above where we have trimmed 'value')
        return paramValue;
    }

    /**
     * Checks whether a given string is a property like ${a} 
     * @param s The string to check. May be <code>null</code>
     * @return <code>true</code> if s is a job property 
     */
    public static boolean isAParameter(String s) {
        if (s == null) {
            return false;
        }
        if (s.startsWith("${") && s.endsWith("}")) {
            return true;
        }
        return false;
    }

    /**
     * Resolve a given build parameter to its value
     * @param build - the Jenkins build. Never <code>null</code>
     * @param parameter - the job parameter. Never <code>null</code>
     * @param listener - task listener. Never <code>null</code>
     * @return the value of the job parameter or <code>property</code> if the job parameter is not defined or 
     * if the given property is not a job parameter 
     * @throws InterruptedException 
     * @throws IOException 
     */
    public static String resolveBuildParameter(Run<?, ?> build, String parameter, TaskListener listener)
            throws IOException, InterruptedException {
        if (parameter == null) {
            return null;
        }
        String parameterStripped = extractParameterFromValue(parameter);
        return getStringBuildParameter(build, parameterStripped, listener);
    }

    /**
     * 
     * @param job
     * @param jobParameter
     * @param listener
     * @return
     */
    public static String resolveJobParameter(Job<?, ?> job, String jobParameter, TaskListener listener) {
        if (jobParameter == null) {
            return null;
        }
        String parameterStripped = extractParameterFromValue(jobParameter);
        return getStringBuildParameter(job, parameterStripped, listener);
    }

    /**
     * Returns the snapshot UUID from the previous build, sometimes only a successful build
     * @param build
     * @param facade
     * @param loginInfo
     * @param streamName
     * @param clientLocale
     * @return
     * @throws Exception
     */
    public static Tuple<Run<?, ?>, String> getSnapshotUUIDFromPreviousBuild(final Run<?, ?> build, String toolkit,
            RTCLoginInfo loginInfo, String processArea, String buildStream, final boolean onlyGoodBuild,
            Locale clientLocale) throws Exception {
        Tuple<Run<?, ?>, String> snapshotDetails = new Tuple<Run<?, ?>, String>(null, null);
        if (buildStream == null) {
            return snapshotDetails;
        }
        snapshotDetails = getValueForBuildStream(new IJenkinsBuildIterator() {

            @Override
            public Run<?, ?> nextBuild(Run<?, ?> build) {
                if (onlyGoodBuild) {
                    return build.getPreviousSuccessfulBuild();
                } else {
                    return build.getPreviousBuild();
                }
            }

            @Override
            public Run<?, ?> firstBuild() {
                return build;
            }
        }, toolkit, loginInfo, processArea, buildStream, onlyGoodBuild, "team_scm_snapshotUUID", clientLocale);
        if (LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.finest("Helper.getSnapshotUUIDFromPreviousBuild : "
                    + ((snapshotDetails.getSecond() == null) ? "No snapshotUUID found from a previous build"
                            : snapshotDetails.getSecond()));
        }
        return snapshotDetails;
    }

    /**
     * Returns the stream change data from the previous build irrespective of its status
     * @param job
     * @param toolkit
     * @param loginInfo
     * @param processArea
     * @param buildStream
     * @param clientLocale
     * @return
     * @throws Exception
     */
    public static Tuple<Run<?, ?>, String> getStreamChangesDataFromLastBuild(final Job<?, ?> job, String toolkit,
            RTCLoginInfo loginInfo, String processArea, String buildStream, Locale clientLocale) throws Exception {
        Tuple<Run<?, ?>, String> streamChangesData = new Tuple<Run<?, ?>, String>(null, null);
        if (buildStream == null) {
            return streamChangesData;
        }
        streamChangesData = getValueForBuildStream(new IJenkinsBuildIterator() {
            @Override
            public Run<?, ?> nextBuild(Run<?, ?> build) {
                return build.getPreviousBuild();
            }

            @Override
            public Run<?, ?> firstBuild() {
                return job.getLastBuild();
            }
        }, toolkit, loginInfo, processArea, buildStream, false, "team_scm_streamChangesData", clientLocale);
        if (LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.finest("Helper.getStreamChangesDataFromLastBuild : " + ((streamChangesData.getSecond() == null)
                    ? "No stream changes data found from a previous build"
                    : streamChangesData.getSecond()));
        }
        return streamChangesData;
    }

    /**
     * 
     * Returns the comment string for the temporary workspace to be created.
     * 
     * @param build The Jenkins {@link Run} from which some details are obtained. Never <code>null</code>
     * @return A {@link String} that is the comment for the temporary workspace
     */
    public static String getTemporaryWorkspaceComment(Run<?, ?> build) {
        return Messages.RTCScm_temporary_workspace_comment(Integer.toString(build.getNumber()),
                build.getParent().getName(), Jenkins.getInstance().getRootUrl());
    }

    /**
     * This method resolves any references to the environment variables and build parameters in the custom snapshot name
     * configured in the job
     * 
     * @param build The Jenkins {@link Run} from which some details are obtained. Never <code>null</code>
     * @param customSnapshotName Custom snapshot name configured in the job
     * @param listener Task listener. Never <code>null</code>
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    public static String resolveCustomSnapshotName(Run<?, ?> build, String customSnapshotName,
            TaskListener listener) throws IOException, InterruptedException {
        if (customSnapshotName == null) {
            return null;
        }
        // Util.replaceMacro() replaces $$ with a single $
        // this replace is required to retain consecutive $, like $$, in the template string.       
        customSnapshotName = customSnapshotName.replaceAll("\\$\\$", "\\$\\$\\$\\$");
        // lookup and resolve environment variables
        String s = build.getEnvironment(listener).expand(customSnapshotName);

        if (build instanceof AbstractBuild) {
            // Util.replaceMacro() replaces $$ with a single $
            // this replace is required to retain consecutive $, like $$, in the template string    
            s = s.replaceAll("\\$\\$", "\\$\\$\\$\\$");
            // lookup and resolve build variables, Build variables include the parameters passed in the Build
            s = Util.replaceMacro(s, ((AbstractBuild<?, ?>) build).getBuildVariableResolver());
        }
        return s;
    }

    private static Tuple<Run<?, ?>, String> getValueForBuildStream(IJenkinsBuildIterator iterator, String toolkit,
            RTCLoginInfo loginInfo, String processArea, String buildStream, boolean onlyGoodBuild, String key,
            Locale clientLocale) throws Exception {
        if (buildStream == null) {
            return null;
        }
        Run<?, ?> build = iterator.firstBuild();
        String value = null;
        Run<?, ?> previousBuild = null;
        RTCFacadeWrapper facade = RTCFacadeFactory.getFacade(toolkit, null);
        String streamUUID =
                // Resolve the stream and get stream UUID
                (String) facade.invoke("getStreamUUID", new Class[] { //$NON-NLS-1$
                        String.class, // serverURI,
                        String.class, // userId,
                        String.class, // password,
                        int.class, // timeout,
                        String.class, // processArea
                        String.class, // buildStream,
                        Locale.class // clientLocale
                }, loginInfo.getServerUri(), loginInfo.getUserId(), loginInfo.getPassword(), loginInfo.getTimeout(),
                        processArea, buildStream, clientLocale);
        while (build != null && value == null) {
            List<RTCBuildResultAction> rtcBuildResultActions = build.getActions(RTCBuildResultAction.class);
            if (rtcBuildResultActions.size() == 1) { // the usual case for freestyle builds (without multiple SCM) and workflow build with only one invocation of RTCScm
                RTCBuildResultAction rtcBuildResultAction = rtcBuildResultActions.get(0);
                if ((rtcBuildResultAction != null) && (rtcBuildResultAction.getBuildProperties() != null)) {
                    Map<String, String> buildProperties = rtcBuildResultAction.getBuildProperties();
                    String owningStreamUUID = Util.fixEmptyAndTrim(buildProperties.get(previousSnapshotOwner));
                    if (owningStreamUUID != null && owningStreamUUID.equals(streamUUID)) {
                        value = buildProperties.get(key);
                        previousBuild = build;
                    }
                }
            } else if (rtcBuildResultActions.size() > 1) {
                for (RTCBuildResultAction rtcBuildResultAction : rtcBuildResultActions) {
                    if ((rtcBuildResultAction != null) && (rtcBuildResultAction.getBuildProperties() != null)) {
                        Map<String, String> buildProperties = rtcBuildResultAction.getBuildProperties();
                        String owningStreamUUID = Util.fixEmptyAndTrim(buildProperties.get(previousSnapshotOwner));
                        if (owningStreamUUID != null && owningStreamUUID.equals(streamUUID)) {
                            value = buildProperties.get(key);
                            previousBuild = build;
                            // We don't we break here to maintain the following behavior for pipeline jobs
                            // if we load the same stream multiple times, we get the the value from the last
                            // RTCBuildResultAction
                        }
                    }
                }
            }
            build = iterator.nextBuild(build);
        }
        return new Tuple<Run<?, ?>, String>(previousBuild, value);
    }

    /**
     * 
     * @param parameter
     * @return
     */
    private static String extractParameterFromValue(String parameter) {
        parameter = Util.fixEmptyAndTrim(parameter);
        if (parameter == null) {
            return null;
        }
        String parameterStripped = parameter.substring(2);
        parameterStripped = parameterStripped.substring(0, parameterStripped.length() - 1);

        return parameterStripped;
    }

    /**
     * 
     **/
    private static String getStringBuildParameter(Job<?, ?> job, String parameter, TaskListener listener) {

        ParametersDefinitionProperty prop = job.getProperty(ParametersDefinitionProperty.class);
        String paramValue = null;
        if (prop != null) {
            List<ParameterDefinition> definitions = prop.getParameterDefinitions();
            for (ParameterDefinition definition : definitions) {
                if (definition == null) {
                    continue;
                }
                if (definition.getName().equals(parameter)
                        && definition.getClass().equals(StringParameterDefinition.class)) {
                    ParameterValue defaultParamValue = definition.getDefaultParameterValue();
                    if (defaultParamValue != null) {
                        paramValue = Util.fixEmptyAndTrim((String) defaultParamValue.getValue());
                    }
                }
            }
        }
        return paramValue;
    }

    /**
     * Reads and returns the json text from the specified file
     * 
     * @param filePath path to the file on disk containing the json text
     * @return json text specified in the file
     * @throws Exception
     */
    private static String readJsonFromFile(String filePath) throws Exception {
        if (Util.fixEmptyAndTrim(filePath) != null) {
            File fileHandle = new File(filePath);
            if (!fileHandle.exists()) {
                throw new IllegalArgumentException(Messages.Helper_file_not_found(filePath));
            }
            if (!fileHandle.isFile()) {
                throw new IllegalArgumentException(Messages.Helper_not_a_file(filePath));
            }
            InputStream is = new FileInputStream(fileHandle);
            String jsonTxt = IOUtils.toString(is);
            is.close();
            return jsonTxt;
        }
        return null;
    }

    private interface IJenkinsBuildIterator {
        Run<?, ?> firstBuild();

        Run<?, ?> nextBuild(Run<?, ?> build);
    }
}