/* * The MIT License * * Copyright 2014 Stellar Science Ltd Co * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.sonyericsson.jenkins.plugins.bfa.tokens; import com.sonyericsson.jenkins.plugins.bfa.model.FailureCauseBuildAction; import com.sonyericsson.jenkins.plugins.bfa.model.FailureCauseDisplayData; import com.sonyericsson.jenkins.plugins.bfa.model.FailureCauseMatrixBuildAction; import com.sonyericsson.jenkins.plugins.bfa.model.FoundFailureCause; import com.sonyericsson.jenkins.plugins.bfa.model.indication.FoundIndication; import hudson.matrix.MatrixRun; import jenkins.model.Jenkins; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import java.io.IOException; import java.util.List; /** * Renderer for the Token mechanims. Provides options to render based on {@link FailureCauseBuildAction} or * {@link FailureCauseMatrixBuildAction}. */ public class Renderer { private static final int ITEM_INCREMENT = 0; private static final int LIST_INCREMENT = 1; private static final String LIST_BULLET = "* "; private static final String LIST_BULLET_SPACE = " "; private static final Logger logger = Logger.getLogger(Renderer.class.getName()); /** * When true, the indication numbers and links into the console log are included in the token replacement text. */ private boolean includeIndications = true; /** * When true, the replacement will be an HTML snippet. */ private boolean useHtmlFormat = false; /** * When true, the "Identified problems:" title will appear over the causes. */ private boolean includeTitle = true; /** * Wrap long lines at this width.If wrapWidth is 0,the text isn't wrapped. Only applies if useHtmlFormat == false. */ private int wrapWidth = 0; /** * Default text to include if no problem was found. It defaults to an empty string. */ private String noFailureText = ""; /** * @param includeIndications When true, the indication numbers and links into the console log are included * in the token replacement text. */ public void setIncludeIndications(boolean includeIndications) { this.includeIndications = includeIndications; } /** * @param useHtmlFormat When true, the replacement text will be an HTML snippet. */ public void setUseHtmlFormat(boolean useHtmlFormat) { this.useHtmlFormat = useHtmlFormat; } /** * @param includeTitle When true, the title will appear in the token replacement text. */ public void setIncludeTitle(boolean includeTitle) { this.includeTitle = includeTitle; } /** * @param wrapWidth Wrap long lines at this width. If wrapWidth is 0, the text isn't wrapped. Only applies if * useHtmlFormat == false. */ public void setWrapWidth(int wrapWidth) { this.wrapWidth = wrapWidth; } /** * @param noFailureText Text to return when no failure cause is present. */ public void setNoFailureText(String noFailureText) { this.noFailureText = noFailureText; } /** * Append the either the html or plain text given to the StringBuilder, depending on "useHtmlFormat" value. * @param stringBuilder The {@link StringBuilder} to append to. * @param htmlText Text to append in case of html, can be null. * @param plainText Text to append in case of plain text, can be null. */ protected void appendHtmlOrPlain(StringBuilder stringBuilder, String htmlText, String plainText) { if (useHtmlFormat && htmlText != null) { stringBuilder.append(htmlText); } else if (!useHtmlFormat && plainText != null) { stringBuilder.append(plainText); } } /** * Renders the Causes as provided by the action. * @param action The action containing the causes * @return The formatted causes. */ public String render(FailureCauseBuildAction action) { final FailureCauseDisplayData data = action.getFailureCauseDisplayData(); if (data.getFoundFailureCauses().isEmpty() && data.getDownstreamFailureCauses().isEmpty()) { logger.info("there were no causes"); return noFailureText; } final StringBuilder stringBuilder = new StringBuilder(); addTitle(stringBuilder); final int indentLevel = 0; addFailureCauseDisplayDataRepresentation(stringBuilder, data, indentLevel); return stringBuilder.toString(); } /** * @param indentLevel the indent level * @return a whitespace string with an appropriate with for the specified indent level */ static String indentForDepth(final int indentLevel) { return StringUtils.repeat(" ", indentLevel); } /** * Add the "Identified problems:" title to the output. * @param stringBuilder the string builder to which to add the title */ protected void addTitle(final StringBuilder stringBuilder) { if (includeTitle) { final String title = "Identified problems:"; appendHtmlOrPlain(stringBuilder, "<h2>", null); stringBuilder.append(title); appendHtmlOrPlain(stringBuilder, "</h2>", "\n"); } } /** * @param stringBuilder the string builder to which to add the failure cause data representation * @param data the failure cause display data * @param indentLevel the indent level */ protected void addFailureCauseDisplayDataRepresentation(final StringBuilder stringBuilder, final FailureCauseDisplayData data, final int indentLevel) { final IndicationUrlBuilder indicationUrlBuilder = new IndicationUrlBuilder(); indicationUrlBuilder.setBuildUrl(data.getLinks().getBuildUrl()); final List<FoundFailureCause> causes = data.getFoundFailureCauses(); final int nextIndentLevel = indentLevel + ITEM_INCREMENT; appendHtmlOrPlain(stringBuilder, "<ul>", null); for (final FoundFailureCause cause : causes) { indicationUrlBuilder.setCause(cause); addFailureCauseRepresentation(stringBuilder, indicationUrlBuilder, cause, nextIndentLevel); } appendHtmlOrPlain(stringBuilder, "</ul>", null); } /** * @param stringBuilder the string builder to which to add the failure cause representation * @param indicationUrlBuilder the indication URL builder * @param cause the found failure cause * @param indentLevel the indent level */ private void addFailureCauseRepresentation(final StringBuilder stringBuilder, final IndicationUrlBuilder indicationUrlBuilder, final FoundFailureCause cause, final int indentLevel) { final int nextIndentLevel = indentLevel + LIST_INCREMENT; if (useHtmlFormat) { stringBuilder.append("<li>"); try { stringBuilder.append(Jenkins.getInstance().getMarkupFormatter().translate(cause.getName())); } catch (final IOException exception) { stringBuilder.append("cause-name"); } stringBuilder.append(": "); try { stringBuilder.append(Jenkins.getInstance().getMarkupFormatter().translate(cause.getDescription())); } catch (final IOException exception) { stringBuilder.append("cause-description"); } if (includeIndications) { addIndicationsRepresentation(stringBuilder, indicationUrlBuilder, cause.getIndications(), nextIndentLevel); } stringBuilder.append("</li>"); } else { // e.g., // * cause-name: cause-description that // can wrap lines // |-------------------------------------| // AA // BB // CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC // // A = indentForDepth() // B = LIST_BULLET.length() // C = wrapWidth final List<String> lines = TokenUtils.wrap( cause.getName() + ": " + cause.getDescription(), // C - A - B wrapWidth - indentForDepth(indentLevel).length() - LIST_BULLET.length()); for (int lineIndex = 0, lineCount = lines.size(); lineIndex < lineCount; ++lineIndex) { if (lineIndex == 0) { stringBuilder.append(LIST_BULLET); } else { stringBuilder.append(LIST_BULLET_SPACE); } stringBuilder.append(lines.get(lineIndex)); stringBuilder.append("\n"); } if (includeIndications) { addIndicationsRepresentation(stringBuilder, indicationUrlBuilder, cause.getIndications(), nextIndentLevel); } } } /** * @param stringBuilder the string builder to which to add the indication list representation * @param indicationUrlBuilder the indication URL builder * @param indications the indication list * @param indentLevel the indent level */ private void addIndicationsRepresentation(final StringBuilder stringBuilder, final IndicationUrlBuilder indicationUrlBuilder, final List<FoundIndication> indications, final int indentLevel) { final int nextIndentLevel = indentLevel + ITEM_INCREMENT; appendHtmlOrPlain(stringBuilder, "<ul>", null); for (int i = 0, size = indications.size(); i < size; ++i) { final FoundIndication indication = indications.get(i); indicationUrlBuilder.setIndication(indication); final int indicationNumber = i + 1; addIndicationRepresentation(stringBuilder, indicationUrlBuilder, indication, indicationNumber, nextIndentLevel); } appendHtmlOrPlain(stringBuilder, "</ul>", null); } /** * @param stringBuilder the string builder to which to add the indication representation * @param indicationUrlBuilder the indication URL builder * @param indication the found indication * @param indicationNumber the found indication number (index+1) in the list of indications * @param indentLevel the indent level */ private void addIndicationRepresentation(final StringBuilder stringBuilder, final IndicationUrlBuilder indicationUrlBuilder, final FoundIndication indication, final int indicationNumber, final int indentLevel) { if (useHtmlFormat) { stringBuilder.append("<li><a href=\""); stringBuilder.append(indicationUrlBuilder.getUrlString()); stringBuilder.append("\">"); stringBuilder.append("Indication "); stringBuilder.append(indicationNumber); stringBuilder.append("</a></li>"); } else { stringBuilder.append(indentForDepth(indentLevel)); stringBuilder.append(LIST_BULLET); stringBuilder.append("Indication "); stringBuilder.append(indicationNumber); stringBuilder.append(":\n"); stringBuilder.append(indentForDepth(indentLevel)); stringBuilder.append(LIST_BULLET_SPACE); stringBuilder.append("<"); stringBuilder.append(indicationUrlBuilder.getUrlString()); stringBuilder.append(">\n"); } } /** * Renders the Causes as provided by the action. * @param matrixAction The action containing the causes * @return The formatted causes. */ public String render(final FailureCauseMatrixBuildAction matrixAction) { final StringBuilder stringBuilder = new StringBuilder(); addTitle(stringBuilder); final int indentLevel = 0; addFailureCauseMatrixRepresentation(stringBuilder, matrixAction, indentLevel); return stringBuilder.toString(); } /** * @param stringBuilder the string builder to which to add the matrix build representation * @param matrixAction the matrix action * @param indentLevel the indent level */ private void addFailureCauseMatrixRepresentation(final StringBuilder stringBuilder, final FailureCauseMatrixBuildAction matrixAction, final int indentLevel) { final List<MatrixRun> matrixRuns = matrixAction.getRunsWithAction(); if (useHtmlFormat) { stringBuilder.append("<ul>"); } for (final MatrixRun matrixRun : matrixRuns) { addMatrixRunRepresentation(stringBuilder, matrixRun, indentLevel + ITEM_INCREMENT); } if (useHtmlFormat) { stringBuilder.append("</ul>"); } } /** * @param stringBuilder the string builder to which to add the matrix run representation * @param matrixRun the matrix run * @param indentLevel the indent level */ private void addMatrixRunRepresentation(final StringBuilder stringBuilder, final MatrixRun matrixRun, final int indentLevel) { final FailureCauseDisplayData data = FailureCauseMatrixBuildAction.getFailureCauseDisplayData(matrixRun); if (data.getFoundFailureCauses().isEmpty() && data.getDownstreamFailureCauses().isEmpty()) { return; } final int nextIndentLevel = indentLevel + LIST_INCREMENT; if (useHtmlFormat) { stringBuilder.append("<li>"); try { stringBuilder.append(Jenkins.getInstance().getMarkupFormatter().translate( matrixRun.getFullDisplayName())); } catch (final IOException exception) { stringBuilder.append("matrix-full-display-name"); } addFailureCauseDisplayDataRepresentation(stringBuilder, data, nextIndentLevel); stringBuilder.append("</li>"); } else { stringBuilder.append(indentForDepth(indentLevel)); stringBuilder.append(LIST_BULLET); stringBuilder.append(matrixRun.getFullDisplayName()); stringBuilder.append("\n"); addFailureCauseDisplayDataRepresentation(stringBuilder, data, nextIndentLevel); } } /** * Helps build a URL into the build log for an indication. */ protected static class IndicationUrlBuilder { private String buildUrl = ""; private String causeId = ""; private String indicationHash = ""; /** * @param buildUrl the url for the indication's build */ void setBuildUrl(final String buildUrl) { this.buildUrl = buildUrl; } /** * @param cause the cause containing the indication */ void setCause(final FoundFailureCause cause) { this.causeId = cause.getId(); } /** * @param indication the indication */ void setIndication(final FoundIndication indication) { this.indicationHash = String.valueOf(indication.getMatchingHash()); } /** * @return the string representation of the URL into the build log for the indication */ String getUrlString() { final StringBuilder builder = new StringBuilder(); builder.append(Jenkins.getInstance().getRootUrl()); builder.append("/"); builder.append(buildUrl); builder.append("consoleFull#"); builder.append(indicationHash); builder.append(causeId); return builder.toString(); } @Override public String toString() { return getUrlString(); } } }