Java tutorial
/* * Copyright (c) 2018, Neotys * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Neotys nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL NEOTYS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jenkinsci.plugins.neoload.integration.supporting; import com.google.common.base.Charsets; import hudson.EnvVars; import hudson.Util; import hudson.model.*; import hudson.tasks.Builder; import hudson.util.FormValidation; import hudson.util.FormValidation.FileValidator; import hudson.util.ListBoxModel; import hudson.util.RunList; import jenkins.model.Jenkins; import org.apache.commons.beanutils.Converter; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.EncoderException; import org.apache.commons.codec.net.BCodec; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.neoload.integration.NeoBuildAction; import org.jenkinsci.plugins.neoload.integration.NeoGlobalConfig; import org.jenkinsci.plugins.neoload.integration.NeoResultsAction; import org.kohsuke.stapler.Stapler; import org.w3c.dom.Document; import org.w3c.dom.Node; import javax.xml.xpath.XPathExpressionException; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.net.URI; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; /** * In this class numerous function has been duplicated with a different prototype to work with pipelines * The older methods had been kept for compatibility purpose with the old plugin */ public final class PluginUtils implements Serializable, Converter { /** * The constant GRAPH_LOCK. */ public static final LockManager GRAPH_LOCK = new LockManager(); /** * Encode passwords so that they're not plain text on the disk. */ private static final BCodec BCODEC = new BCodec(); /** * Log various messages. */ private static final Logger LOGGER = Logger.getLogger(PluginUtils.class.getName()); /** * Generated. */ private static final long serialVersionUID = -3063042074729452263L; public static final String EMPTY_SERVER_MESSAGE = "Please configure Jenkins System Settings for NeoLoad to add a server."; static { Stapler.CONVERT_UTILS.register(new PluginUtils(), ServerInfo.class); Stapler.CONVERT_UTILS.register(new PluginUtils(), CollabServerInfo.class); Stapler.CONVERT_UTILS.register(new PluginUtils(), NTSServerInfo.class); } /** * Utility classes are not intended to be instantiated, but the plugin doesn't work if we throw an exception. */ private PluginUtils() { } /** * Encode string. * * @param text the text * @return the string * @throws EncoderException the encoder exception */ public static String encode(final String text) throws EncoderException { return BCODEC.encode(text, Charsets.UTF_8.name()); } /** * Decode string. * * @param text the text * @return the string * @throws DecoderException the decoder exception */ public static String decode(final String text) throws DecoderException { return BCODEC.decode(text); } /** * Get the configured instance for the plugin. * * @param project the project * @return plugin options */ public static NeoLoadPluginOptions getPluginOptions(final AbstractProject<?, ?> project) { final Project<?, ?> proj; NeoBuildAction nba = null; if (!(project instanceof Project)) { return null; } proj = (Project<?, ?>) project; final List<Builder> builders = proj.getBuilders(); for (final Builder b : builders) { if (b instanceof NeoBuildAction) { nba = (NeoBuildAction) b; break; } } return (NeoLoadPluginOptions) nba; } /** * Gets plugin options. * * @param project the project * @return the plugin options */ public static NeoLoadPluginOptions getPluginOptions(final Job<?, ?> project) { return project.getProperty(SimpleBuildOption.class); } /** * Get the configured instance for the plugin. * * @param project the project * @return neo build action */ public static NeoBuildAction getNeoBuildAction(final AbstractProject<?, ?> project) { if (!(project instanceof Project)) { return null; } for (final Builder b : ((Project<?, ?>) project).getBuilders()) { if (b instanceof NeoBuildAction) { return (NeoBuildAction) b; } } return null; } /** * Gets neo result action. * * @param build the build * @return the neo result action */ public static NeoResultsAction getNeoResultAction(final AbstractBuild<?, ?> build) { return build.getAction(NeoResultsAction.class); } /** * Gets neo result action. * * @param build the build * @return the neo result action */ public static NeoResultsAction getNeoResultAction(final Run<?, ?> build) { return build.getAction(NeoResultsAction.class); } /** * Validate warn if empty form validation. * * @param fieldValue the field value * @param displayName the display name * @return the form validation */ public static FormValidation validateWarnIfEmpty(final String fieldValue, final String displayName) { if (StringUtils.trimToNull(fieldValue) == null) { return FormValidation.warning("Don't forget to include the " + displayName + "."); } return FormValidation.ok(); } /** * Form validation error to warning form validation. * * @param formValidation the form validation * @return the same message but an error becomes a warning. "Ok" remains "Ok" */ public static FormValidation formValidationErrorToWarning(final FormValidation formValidation) { if (FormValidation.Kind.ERROR.equals(formValidation.kind)) { return FormValidation.warning(StringEscapeUtils.unescapeHtml(formValidation.getMessage())); } return formValidation; } /** * Validate url form validation. * * @param url the url * @return the form validation */ public static FormValidation validateURL(final String url) { if (StringUtils.trimToNull(url) == null) { return FormValidation.warning("Don't forget to include the URL."); } try { final URI uri = new URI(url); if (uri.getScheme() == null || uri.getHost() == null) { return FormValidation.error("Invalid URL: " + url); } return FormValidation.ok(); } catch (final Exception e) { return FormValidation.error("URL could not be parsed."); } } /** * removes empty strings from a list. * * @param originalStrings the original strings * @return list list */ public static List<String> removeAllEmpties(final String... originalStrings) { final List<String> cleanedStrings = new ArrayList<String>(Arrays.asList(originalStrings)); cleanedStrings.removeAll(Arrays.asList(null, "", Collections.singleton(null))); final Iterator<String> it = cleanedStrings.iterator(); while (it.hasNext()) { final String s2 = it.next(); if (StringUtils.trimToEmpty(s2).length() == 0) { it.remove(); } } return cleanedStrings; } /** * Check if the given string points to a file on local machine. * If it's not the case, just display an info message, not a warning because * it might be executed on a remote host. * * @param file the file * @param extension the extension * @param checkExtension the check extension * @param checkInPath the check in path * @return the form validation */ public static FormValidation validateFileExists(String file, final String extension, final boolean checkExtension, final boolean checkInPath) { // If file is null or empty, return an error final FormValidation emptyOrNullValidation = FormValidation.validateRequired(file); if (!FormValidation.Kind.OK.equals(emptyOrNullValidation.kind)) { return emptyOrNullValidation; } if (checkExtension && !file.toLowerCase().endsWith(extension)) { return FormValidation.error("Please specify a file with " + extension + " extension"); } // insufficient permission to perform validation? if (!Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER)) { return FormValidation.ok("Insufficient permission to perform legend validation."); } if (file.indexOf(File.separatorChar) >= 0) { // this is full legend File f = new File(file); if (f.exists()) return FileValidator.NOOP.validate(f); File fexe = new File(file + extension); if (fexe.exists()) return FileValidator.NOOP.validate(fexe); } if (Files.exists(Paths.get(file))) { return FormValidation.ok(); } if (checkInPath) { String path = EnvVars.masterEnvVars.get("PATH"); String delimiter = null; if (path != null) { for (String _dir : Util.tokenize(path.replace("\\", "\\\\"), File.pathSeparator)) { if (delimiter == null) { delimiter = ", "; } File dir = new File(_dir); File f = new File(dir, file); if (f.exists()) return FileValidator.NOOP.validate(f); File fexe = new File(dir, file + ".exe"); if (fexe.exists()) return FileValidator.NOOP.validate(fexe); } } } return FormValidation.ok( "There is no such file on local host. You can ignore this message if the job is executed on a remote slave."); } /** * Gets html report paths. * * @param build the build * @param firstPath the first path * @return the html report paths */ public static List<String> getHTMLReportPaths(final AbstractBuild<?, ?> build, final String firstPath) { List<String> paths = new ArrayList<>(); if (firstPath != null) { paths.add(firstPath); } final NeoBuildAction neoBuildAction = getNeoBuildAction(build.getProject()); final String htmlReportPath = neoBuildAction.getHTMLReportArtifactPath(); if (htmlReportPath != null) { paths.add(htmlReportPath); } paths.add("neoload-report/report.html"); return paths; } /** * Gets html report paths. * * @param build the build * @param firstPath the first path * @return the html report paths */ public static List<String> getHTMLReportPaths(final Run<?, ?> build, final String firstPath) { List<String> paths = new ArrayList<>(); if (firstPath != null) { paths.add(firstPath); } paths.add("neoload-report/report.html"); return paths; } /** * Gets xml report paths. * * @param build the build * @return the xml report paths */ public static List<String> getXMLReportPaths(final AbstractBuild<?, ?> build) { List<String> paths = new ArrayList<>(); final NeoResultsAction neoResultAction = getNeoResultAction(build); if (neoResultAction != null && neoResultAction.getStoredXmlReportPath() != null) { paths.add(neoResultAction.getStoredXmlReportPath()); } final NeoBuildAction neoBuildAction = getNeoBuildAction(build.getProject()); final String xmlReportPath = neoBuildAction.getXMLReportArtifactPath(); if (xmlReportPath != null) { paths.add(xmlReportPath); } paths.add("neoload-report/report.xml"); return paths; } /** * Gets xml report paths. * * @param build the build * @return the xml report paths */ public static List<String> getXMLReportPaths(final Run<?, ?> build) { List<String> paths = new ArrayList<>(); final NeoResultsAction neoResultAction = getNeoResultAction(build); if (neoResultAction != null && neoResultAction.getStoredXmlReportPath() != null) { paths.add(neoResultAction.getStoredXmlReportPath()); } paths.add("neoload-report/report.xml"); return paths; } /** * Remove workspace or relative point string. * * @param report the report * @return the string */ public static String removeWorkspaceOrRelativePoint(final String report) { if (report == null) { return null; } if (report.startsWith(".") && !report.startsWith("..")) { return report.substring(1); } return report.replaceAll("%WORKSPACE%/|\\$\\{WORKSPACE}/", ""); } /** * Find artifact run . artifact. * * @param paths the paths * @param build the build * @return the run . artifact */ public static Run.Artifact findArtifact(final List<String> paths, final AbstractBuild<?, ?> build) { if (build == null) { // This can happen when the plugin is reinstalled or simply after time passes. When the plugin is // initialized you need to get the "project" instance, iterate over every build, and reinitialize every // instance of this action (NeoResultsAction) so that the build instance won't be null. LOGGER.log(Level.SEVERE, "NeoResultsAction.findHtmlReportArtifact() build is null."); return null; } return findArtifacts(paths, build.getArtifacts(), build.getNumber()); } /** * Find artifact run . artifact. * * @param paths the paths * @param build the build * @return the run . artifact */ public static Run.Artifact findArtifact(final List<String> paths, final Run<?, ?> build) { if (build == null) { // This can happen when the plugin is reinstalled or simply after time passes. When the plugin is // initialized you need to get the "project" instance, iterate over every build, and reinitialize every // instance of this action (NeoResultsAction) so that the build instance won't be null. LOGGER.log(Level.SEVERE, "NeoResultsAction.findHtmlReportArtifact() build is null."); return null; } return findArtifacts(paths, build.getArtifacts(), build.getNumber()); } public static Run.Artifact findArtifacts(final List<String> paths, final List artifacts, final int buildnumber) { //To be compatible with older we try different paths. for (String path : paths) { path = path.replaceAll("\\$\\{BUILD_NUMBER\\}", Integer.toString(buildnumber)); //Variable issues ${workspace/toto become /toto and it not found. while (path.startsWith(File.pathSeparator)) { path = path.substring(1); } for (Object object : artifacts) { Run.Artifact artifact = (Run.Artifact) object;//Because the run and action is little different. if (artifact.relativePath.endsWith(path)) { return artifact; } } } return null; } /** * Gets custom. * * @param path the path * @param doc the doc * @return the custom * @throws XPathExpressionException the x path expression exception */ public static Float getCustom(final String path, final Document doc) throws XPathExpressionException { if (path == null) return null; final Node node = XMLUtilities.findFirstByExpression(path, doc); if (node != null) { return extractNeoLoadNumber(node.getNodeValue()); } return null; } /** * @param valArg * @return */ private static Float extractNeoLoadNumber(final String valArg) { String val = StringUtils.trimToEmpty(valArg); // remove spaces etc val = val.replaceAll(",", ".").replaceAll(" ", ""); // special case for percentages val = val.replaceAll(Pattern.quote("%"), ""); // special case for percentages val = val.replaceAll(Pattern.quote("+"), ""); // special case for less than 0.01% if ("<0.01".equals(val)) { return 0f; } try { return Float.valueOf(val); } catch (final Exception e) { // we couldn't convert the result to an actual number so the value will not be included. // this could be +INF, -INF, " - ", NaN, etc. See com.neotys.nl.util.FormatUtils.java, getTextNumber(). } return null; } /** * Gets pictures folder. * * @param project the project * @return the pictures folder */ public static File getPicturesFolder(AbstractProject<?, ?> project) { return new File(project.getRootDir(), "neoload-trend"); } /** * Gets pictures folder. * * @param project the project * @return the pictures folder */ public static File getPicturesFolder(Job<?, ?> project) { return new File(project.getRootDir(), "neoload-trend"); } /** * Build graph. * * @param picturesFolder the pictures folder * @param npo the npo * @param project the project */ public static void buildGraph(final File picturesFolder, final NeoLoadPluginOptions npo, final AbstractProject<?, ?> project) { if (GRAPH_LOCK.tryLock(project)) { try { picturesFolder.mkdirs(); if (picturesFolder.isDirectory()) { //Clean for (File file : picturesFolder.listFiles()) { file.delete(); } NeoloadGraphsStatistics neoloadGraphsStatistics = new NeoloadGraphsStatistics(npo); for (final AbstractBuild<?, ?> build : getLimitedBuilds(npo, project)) { neoloadGraphsStatistics.addBuild(build); } try { neoloadGraphsStatistics.writePng(picturesFolder); } catch (IOException e) { LOGGER.log(Level.WARNING, "Exception occurs during the picture writing ", e); } } } finally { GRAPH_LOCK.unlock(project); } } } /** * Build graph. * * @param picturesFolder the pictures folder * @param npo the npo * @param project the project */ public static void buildGraph(final File picturesFolder, final NeoLoadPluginOptions npo, final Job<?, ?> project) { if (GRAPH_LOCK.tryLock(project)) { try { picturesFolder.mkdirs(); if (picturesFolder.isDirectory()) { //Clean for (File file : picturesFolder.listFiles()) { file.delete(); } NeoloadGraphsStatistics neoloadGraphsStatistics = new NeoloadGraphsStatistics(npo); for (final Run<?, ?> build : getLimitedBuilds(npo, project)) { neoloadGraphsStatistics.addBuild(build); } try { neoloadGraphsStatistics.writePng(picturesFolder); } catch (IOException e) { LOGGER.log(Level.WARNING, "Exception occurs during the picture writing ", e); } } } finally { GRAPH_LOCK.unlock(project); } } } private static List<AbstractBuild> getLimitedBuilds(NeoLoadPluginOptions npo, final AbstractProject project) { final int maxTrends = npo.getMaxTrends(); final RunList<?> builds = project.getBuilds(); if (maxTrends > 0 && builds.size() > maxTrends) { return (List<AbstractBuild>) builds.subList(0, maxTrends); } else { return (List<AbstractBuild>) builds; } } private static List<Run> getLimitedBuilds(NeoLoadPluginOptions npo, final Job project) { final int maxTrends = npo.getMaxTrends(); final RunList<?> builds = project.getBuilds(); if (maxTrends > 0 && builds.size() > maxTrends) { return (List<Run>) builds.subList(0, maxTrends); } else { return (List<Run>) builds; } } /** * Build graph. * * @param project the project */ public static void buildGraph(AbstractProject project) { try { final NeoLoadPluginOptions npo = PluginUtils.getPluginOptions(project); final File picturesFolder = PluginUtils.getPicturesFolder(project); PluginUtils.buildGraph(picturesFolder, npo, project); } catch (Throwable th) { LOGGER.log(Level.WARNING, "Exception occurs during the trend building", th); } } /** * Build graph. * * @param project the project */ public static void buildGraph(Job project) { try { final NeoLoadPluginOptions npo = PluginUtils.getPluginOptions(project); final File picturesFolder = PluginUtils.getPicturesFolder(project); PluginUtils.buildGraph(picturesFolder, npo, project); } catch (Throwable th) { LOGGER.log(Level.WARNING, "Exception occurs during the trend building", th); } } public Object convert(@SuppressWarnings("rawtypes") final Class type, final Object value) { // get the main config. final NeoGlobalConfig.DescriptorImpl globalConfigDescriptor = getNeoGlobalConfig(); if (globalConfigDescriptor == null) { LOGGER.log(Level.FINEST, "No NeoLoad server settings found. Please add servers before configuring jobs. (getLicenseServerOptions)"); return null; } // find the serverInfo based on the unique ID. @SuppressWarnings("unchecked") final Collection<ServerInfo> allServerInfo = CollectionUtils.union(globalConfigDescriptor.getNtsInfo(), globalConfigDescriptor.getCollabInfo()); for (final ServerInfo si : allServerInfo) { if (si.getUniqueID().equals(value)) { return si; } } return null; } public static NeoGlobalConfig.DescriptorImpl getNeoGlobalConfig() { return (NeoGlobalConfig.DescriptorImpl) Jenkins.getInstance().getDescriptor(NeoGlobalConfig.class); } public static List<ServerInfo> getServerInfos(boolean collab) { final NeoGlobalConfig.DescriptorImpl globalConfigDescriptor = getNeoGlobalConfig(); List<ServerInfo> serverInfoList = new ArrayList<>(); if (globalConfigDescriptor != null) { serverInfoList.addAll(globalConfigDescriptor.getNtsInfo()); if (collab) { serverInfoList.addAll(globalConfigDescriptor.getCollabInfo()); } } return serverInfoList; } public static ListBoxModel getServerInfosListBox(boolean collab) { final List<ServerInfo> serverInfos = getServerInfos(collab); final ListBoxModel listBoxModel = new ListBoxModel(); if (serverInfos.isEmpty()) { listBoxModel.add(new ListBoxModel.Option(EMPTY_SERVER_MESSAGE, null)); } else { for (final ServerInfo server : serverInfos) { listBoxModel.add(new ListBoxModel.Option(server.getNonEmptyLabel(collab), server.getUniqueID())); } } return listBoxModel; } public static String forgeArtifactoryPath(final NeoBuildAction neoBuildAction) { List<String> paths = new ArrayList<>(); final String htmlReport = neoBuildAction.getHtmlReport(); if (StringUtils.isNotEmpty(htmlReport)) { paths.add(htmlReport); final File file = new File(htmlReport); paths.add(file.getParent() + "/" + FilenameUtils.removeExtension(file.getName()) + "_files/**"); } final String xmlReport = neoBuildAction.getXmlReport(); if (StringUtils.isNotEmpty(xmlReport)) { paths.add(xmlReport); paths.add(xmlReport.replace(".xml", ".dtd")); } addIfNotEmpty(paths, neoBuildAction.getPdfReport()); addIfNotEmpty(paths, neoBuildAction.getJunitReport()); return StringUtils.join(paths, ","); } private static void addIfNotEmpty(final List<String> paths, final String str) { if (StringUtils.isNotEmpty(str)) { paths.add(str); } } public static boolean isSAP(final String licenseVUSAPCount) { return StringUtils.isNotEmpty(licenseVUSAPCount) && !licenseVUSAPCount.equals("0"); } }