Java tutorial
/******************************************************************************* * 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); } }