diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/Application.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/Application.java index bd0d1a4ca..c809f8462 100755 --- a/src/main/java/com/sap/oss/phosphor/fosstars/tool/Application.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/Application.java @@ -344,7 +344,8 @@ private static void checkOptionsIn(CommandLine commandLine) { } if (commandLine.hasOption("report-type") - && !asList("text", "markdown").contains(commandLine.getOptionValue("report-type"))) { + && !asList("text", "markdown", "json").contains( + commandLine.getOptionValue("report-type"))) { throw new IllegalArgumentException( format("Unknown report type: %s", commandLine.getOptionValue("report-type"))); diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/OssProjectSecurityRatingHandler.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/OssProjectSecurityRatingHandler.java index 307ed66f0..64b4dea89 100644 --- a/src/main/java/com/sap/oss/phosphor/fosstars/tool/OssProjectSecurityRatingHandler.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/OssProjectSecurityRatingHandler.java @@ -16,9 +16,11 @@ import com.sap.oss.phosphor.fosstars.model.rating.oss.OssSecurityRating; import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; import com.sap.oss.phosphor.fosstars.tool.format.Formatter; +import com.sap.oss.phosphor.fosstars.tool.format.JsonPrettyPrinter; import com.sap.oss.phosphor.fosstars.tool.format.OssSecurityRatingMarkdownFormatter; import com.sap.oss.phosphor.fosstars.tool.format.PrettyPrinter; import com.sap.oss.phosphor.fosstars.tool.report.MergedJsonReporter; +import com.sap.oss.phosphor.fosstars.tool.report.OssSecurityRatingJsonReporter; import com.sap.oss.phosphor.fosstars.tool.report.OssSecurityRatingMarkdownReporter; import com.sap.oss.phosphor.fosstars.tool.report.Reporter; import java.io.File; @@ -177,6 +179,8 @@ Formatter createFormatter(String type) { return PrettyPrinter.withVerboseOutput(OSS_SECURITY_GITHUB_ADVISOR); case "markdown": return new OssSecurityRatingMarkdownFormatter(OSS_SECURITY_GITHUB_ADVISOR); + case "json": + return new JsonPrettyPrinter(OSS_SECURITY_GITHUB_ADVISOR); default: throw new IllegalArgumentException(format("Unsupported report type: %s", type)); } @@ -192,6 +196,10 @@ Optional> reporterFrom(ReportConfig reportConfig) throws return Optional.of( new OssSecurityRatingMarkdownReporter(reportConfig.where, reportConfig.source, rating(), OSS_SECURITY_GITHUB_ADVISOR)); + case JSON_REPORT: + return Optional.of( + new OssSecurityRatingJsonReporter(reportConfig.where, reportConfig.source, + rating(), OSS_SECURITY_GITHUB_ADVISOR)); default: logger.warn("Oops! That's an unknown type of report: {}", reportConfig.type); return Optional.empty(); diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/ReportConfig.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/ReportConfig.java index 35a899934..ea951338f 100644 --- a/src/main/java/com/sap/oss/phosphor/fosstars/tool/ReportConfig.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/ReportConfig.java @@ -11,7 +11,7 @@ public class ReportConfig { * Types of reports. */ public enum ReportType { - MARKDOWN, JSON, ISSUES + MARKDOWN, JSON, ISSUES, JSON_REPORT } /** diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/AbstractMarkdownFormatter.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/AbstractMarkdownFormatter.java index 51a0ac212..74740c36f 100755 --- a/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/AbstractMarkdownFormatter.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/AbstractMarkdownFormatter.java @@ -3,6 +3,7 @@ import static com.sap.oss.phosphor.fosstars.tool.format.Markdown.DOUBLE_NEW_LINE; import static com.sap.oss.phosphor.fosstars.tool.format.Markdown.NEW_LINE; import static java.lang.String.format; +import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; import com.sap.oss.phosphor.fosstars.advice.Advice; @@ -29,12 +30,20 @@ import java.util.TreeSet; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * A base class for Markdown formatters. */ public abstract class AbstractMarkdownFormatter extends CommonFormatter { + /** + * A logger. + */ + private static final Logger LOGGER + = LogManager.getLogger(AbstractMarkdownFormatter.class); + /** * Create a new formatter. * @@ -67,7 +76,7 @@ protected MarkdownElement markdownAdviceFor(Subject subject) { throw new UncheckedIOException("Oops! Could not print advice!", e); } - if (adviceList.isEmpty()) { + if (adviceList == null || adviceList.isEmpty()) { return MarkdownString.EMPTY; } @@ -85,6 +94,21 @@ protected MarkdownElement markdownAdviceFor(Subject subject) { return Markdown.section().with(markdownAdviceHeader()).thatContains(advice); } + /** + * Extract advices for a subject. + * + * @param subject The subject. + * @return Advices collected form a subject. + */ + protected List adviceFor(Subject subject) { + try { + return advisor.adviceFor(subject); + } catch (IOException e) { + LOGGER.warn("Oops! Could not collect advices!", e); + return emptyList(); + } + } + /** * Convert links from advice to Markdown elements. * diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/JsonPrettyPrinter.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/JsonPrettyPrinter.java new file mode 100644 index 000000000..e056b622a --- /dev/null +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/JsonPrettyPrinter.java @@ -0,0 +1,216 @@ +package com.sap.oss.phosphor.fosstars.tool.format; + +import static java.util.Collections.emptyList; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sap.oss.phosphor.fosstars.advice.Advice; +import com.sap.oss.phosphor.fosstars.advice.AdviceContent; +import com.sap.oss.phosphor.fosstars.advice.Advisor; +import com.sap.oss.phosphor.fosstars.model.Subject; +import com.sap.oss.phosphor.fosstars.model.Value; +import com.sap.oss.phosphor.fosstars.model.value.RatingValue; +import com.sap.oss.phosphor.fosstars.model.value.ScoreValue; +import com.sap.oss.phosphor.fosstars.tool.format.model.Advices; +import com.sap.oss.phosphor.fosstars.tool.format.model.Feature; +import com.sap.oss.phosphor.fosstars.tool.format.model.Rating; +import com.sap.oss.phosphor.fosstars.tool.format.model.Score; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.text.DecimalFormat; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * The class prints a pretty rating value in JSON. + */ +public class JsonPrettyPrinter extends CommonFormatter { + + /** + * Object Mapper for Json. + */ + private static final ObjectMapper mapper = new ObjectMapper(); + + /** + * A logger. + */ + private static final Logger LOGGER + = LogManager.getLogger(JsonPrettyPrinter.class); + + /** + * A formatter for doubles. + */ + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.#"); + + static { + DECIMAL_FORMAT.setMinimumFractionDigits(1); + DECIMAL_FORMAT.setMaximumFractionDigits(2); + } + + /** + * Initializes a pretty printer. + * + * @param advisor to be added to the printed output. + */ + public JsonPrettyPrinter(Advisor advisor) { + super(advisor); + } + + @Override + public String print(Subject subject) { + if (!subject.ratingValue().isPresent()) { + return StringUtils.EMPTY; + } + RatingValue ratingValue = subject.ratingValue().get(); + Rating rating = from(ratingValue, subject); + rating.advices(adviceFor(subject)); + StringBuilder output = new StringBuilder(); + try { + output.append(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rating)); + } catch (JsonProcessingException e) { + throw new UncheckedIOException( + "Oops! Could not parse the rating value object to Json string!", e); + } + + return output.toString(); + } + + /** + * Extract advices for a subject. + * + * @param subject The subject. + * @return Advices collected form a subject. + */ + private List adviceFor(Subject subject) { + try { + return advisor.adviceFor(subject).stream().map(JsonPrettyPrinter::from) + .collect(Collectors.toList()); + } catch (IOException e) { + LOGGER.warn("Oops! Could not collect advices!", e); + return emptyList(); + } + } + + /** + * Map Advice to serializable class. + * + * @param advice The Advice. + * @return The serializable class. + */ + private static Advices from(Advice advice) { + AdviceContent content = advice.content(); + return new Advices(content.text(), content.feature().name(), content.links()); + } + + /** + * Format a rating value. + * + * @param ratingValue The rating value. + * @param subject The subject. + * @return A formatted rating value. + */ + private static Rating from(RatingValue ratingValue, Subject subject) { + ScoreValue scoreValue = ratingValue.scoreValue(); + Rating rating = new Rating() + .purl(subject.purl()) + .label(ratingValue.label().name()); + Score score = from(scoreValue); + rating.score(score); + return rating; + } + + /** + * Extract Score from Score Value. + * + * @param scoreValue The score value. + * @return the serializable Score. + */ + private static Score from(ScoreValue scoreValue) { + Score score = new Score() + .name(scoreValue.score().name()) + .value(tellMeActualValueOf(scoreValue)) + .confidence(printValue(scoreValue.confidence())) + .weight(printValue(scoreValue.weight())); + from(scoreValue, score); + return score; + } + + /** + * Map feature value to serializable class. + * + * @param featureValue The feature value. + * @return The serializable class from feature value. + */ + private static Feature from(Value featureValue) { + return new Feature() + .name(featureValue.feature().name()) + .value(tellMeActualValueOf(featureValue)); + } + + /** + * Extract Sub scores from the score value. + * + * @param scoreValue The score value to be printed. + * @param score Tells if the score is a top-level score in the rating. + */ + private static void from(ScoreValue scoreValue, Score score) { + for (Value usedValue : scoreValue.usedValues()) { + if (usedValue instanceof ScoreValue) { + score.subScore(from((ScoreValue) usedValue)); + } else { + score.feature(from(usedValue)); + } + } + } + + /** + * Prints an actual value of a score value. The method takes care about unknown and not-applicable + * score values. + * + * @param scoreValue The score value. + * @return A string that represents the score value. + */ + public static String tellMeActualValueOf(ScoreValue scoreValue) { + if (scoreValue.isNotApplicable()) { + return "N/A"; + } + + if (scoreValue.isUnknown()) { + return "unknown"; + } + + return printValue(scoreValue.get()); + } + + /** + * Prints an actual value. The method takes care about unknown and not-applicable values. + * + * @param value The value. + * @return A string that represents the score value. + */ + public static String tellMeActualValueOf(Value value) { + if (value.isNotApplicable()) { + return "N/A"; + } + + if (value.isUnknown()) { + return "unknown"; + } + + return String.format("%s", value.get()); + } + + /** + * Prints out a number with its max value. + * + * @param value The number. + * @return A formatted string with the number and max value. + */ + public static String printValue(double value) { + return String.format("%-4s", + DECIMAL_FORMAT.format(value)); + } +} \ No newline at end of file diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/OssRulesOfPlayRatingMarkdownFormatter.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/OssRulesOfPlayRatingMarkdownFormatter.java index fb181b890..9d6dfe4c6 100755 --- a/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/OssRulesOfPlayRatingMarkdownFormatter.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/OssRulesOfPlayRatingMarkdownFormatter.java @@ -7,7 +7,6 @@ import static com.sap.oss.phosphor.fosstars.tool.format.Markdown.SPACE; import static java.lang.String.format; import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.unmodifiableMap; import static java.util.Objects.requireNonNull; @@ -46,8 +45,6 @@ import java.util.Set; import java.util.function.BooleanSupplier; import javax.annotation.Nullable; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** * The class prints a rating value @@ -67,12 +64,6 @@ public class OssRulesOfPlayRatingMarkdownFormatter extends AbstractMarkdownForma private static final String DEFAULT_RATING_VALUE_TEMPLATE = loadFrom(RATING_VALUE_TEMPLATE_RESOURCE, OssRulesOfPlayRatingMarkdownFormatter.class); - /** - * A logger. - */ - private static final Logger LOGGER - = LogManager.getLogger(OssRulesOfPlayRatingMarkdownFormatter.class); - /** * Maps a rule to its identifier. */ @@ -192,21 +183,6 @@ String print(RatingValue ratingValue, List advice) { .replace("%ADVICE%", makeAdviceFrom(violations, warnings, passedRules, unclearRules)); } - /** - * Looks for advice for a subject. - * - * @param subject The subject. - * @return A list of advice. - */ - private List adviceFor(Subject subject) { - try { - return advisor.adviceFor(subject); - } catch (IOException e) { - LOGGER.warn("Oops! Could not print advice!", e); - return emptyList(); - } - } - /** * Convert a list of formatted rules to Markdown-formatted advice. * diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/model/Advices.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/model/Advices.java new file mode 100644 index 000000000..a2ec43046 --- /dev/null +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/model/Advices.java @@ -0,0 +1,115 @@ +package com.sap.oss.phosphor.fosstars.tool.format.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.sap.oss.phosphor.fosstars.advice.Link; +import java.util.ArrayList; +import java.util.List; + +/** + * The Advice class that encloses @link {@link com.sap.oss.phosphor.fosstars.advice.Advice} to be + * used in serialization. + */ +public class Advices { + + /** + * The advice text. + */ + private String text; + + /** + * The feature of the advice. + */ + private String feature; + + /** + * The references for the advice. + */ + private List links = new ArrayList<>(); + + /** + * Default constructor. + */ + public Advices() { + } + + /** + * Initializes a Feature instance. + * + * @param text The advice text. + * @param feature of the advice. + * @param links references of the advice. + */ + @JsonCreator + public Advices(@JsonProperty("text") String text, @JsonProperty("feature") String feature, + @JsonProperty("links") List links) { + this.text = text; + this.feature = feature; + this.links.addAll(links); + } + + /** + * Return the text of the advice. + * + * @return the text of the advice. + */ + @JsonGetter("text") + public String text() { + return text; + } + + /** + * Set the text of the advice. + * + * @return the Advice instance. + */ + public Advices text(String text) { + this.text = text; + return this; + } + + /** + * Return the feature of the advice. + * + * @return the feature of the advice. + */ + @JsonGetter("feature") + public String feature() { + return feature; + } + + /** + * Set the feature of the advice. + * + * @return the feature of the advice. + */ + public Advices feature(String feature) { + this.feature = feature; + return this; + } + + /** + * Return the references of the advice. + * + * @return the references of the advice. + */ + @JsonGetter("links") + public List links() { + return links; + } + + /** + * Set the references of the advice. + * + * @param links to be added as prt of the advice. + * @return the Advice. + */ + public Advices links(List links) { + if (this.links == null) { + this.links = new ArrayList<>(); + } + this.links.addAll(links); + return this; + } +} \ No newline at end of file diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/model/Feature.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/model/Feature.java new file mode 100644 index 000000000..e847e2bf1 --- /dev/null +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/model/Feature.java @@ -0,0 +1,80 @@ +package com.sap.oss.phosphor.fosstars.tool.format.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The Feature class that encloses @link {@link com.sap.oss.phosphor.fosstars.model.Feature} to be + * used in serialization. + */ +public class Feature { + + /** + * The name of the feature. + */ + private String name; + + /** + * The value of the feature. + */ + private String value; + + /** + * Default constructor. + */ + public Feature() { + } + + /** + * Initializes a Feature instance. + * + * @param name the name of the feature. + * @param value the value of the feature. + */ + @JsonCreator + public Feature(@JsonProperty("name") String name, @JsonProperty("value") String value) { + this.name = name; + this.value = value; + } + + /** + * Return the name of the feature. + * + * @return the name of the feature. + */ + @JsonGetter("name") + public String name() { + return name; + } + + /** + * Set the name of the feature. + * + * @return the Feature instance. + */ + public Feature name(String name) { + this.name = name; + return this; + } + + /** + * Return the value of the feature. + * + * @return the value of the feature. + */ + @JsonGetter("value") + public String value() { + return value; + } + + /** + * Set the value of the feature. + * + * @return the Feature instance. + */ + public Feature value(String value) { + this.value = value; + return this; + } +} \ No newline at end of file diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/model/Rating.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/model/Rating.java new file mode 100644 index 000000000..6b98c570a --- /dev/null +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/model/Rating.java @@ -0,0 +1,158 @@ +package com.sap.oss.phosphor.fosstars.tool.format.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.sap.oss.phosphor.fosstars.advice.Advice; +import com.sap.oss.phosphor.fosstars.model.value.RatingValue; +import java.util.ArrayList; +import java.util.List; + +/** + * The Rating class that encloses @link {@link RatingValue} to be used in serialization. + */ +public class Rating { + + /** + * The purl of the identifier. + */ + private String purl; + + /** + * The label of the rating. + */ + private String label; + + /** + * The {@link Score} of the rating. + */ + private Score score; + + /** + * The list of {@link Advice}s of the rating. + */ + private List advices = new ArrayList<>(); + + /** + * Initializes a Rating instance. + * + * @param purl the purl of the identifier. + * @param label of the rating. + * @param score of the rating. + */ + @JsonCreator + public Rating(@JsonProperty("purl") String purl, + @JsonProperty("label") String label, + @JsonProperty("score") Score score) { + this.purl = purl; + this.label = label; + this.score = score; + } + + /** + * Default constructor. + */ + public Rating() { + } + + /** + * Return the purl of the identifier. + * + * @return the purl of the identifier. + */ + @JsonGetter("purl") + public String purl() { + return purl; + } + + /** + * Set the purl of the identifier. + * + * @param purl the purl of the identifier. + * @return this. + */ + public Rating purl(String purl) { + this.purl = purl; + return this; + } + + /** + * Return the label of the rating. + * + * @return the label of the rating. + */ + @JsonGetter("label") + public String label() { + return label; + } + + /** + * Set the label of the rating. + * + * @param label the label of the rating. + * @return this. + */ + public Rating label(String label) { + this.label = label; + return this; + } + + /** + * Return the {@link Score} of the rating. + * + * @return the {@link Score} of the rating. + */ + @JsonGetter("score") + public Score score() { + return score; + } + + /** + * Set the {@link Score} of the rating. + * + * @param score the {@link Score} of the rating. + * @return this. + */ + public Rating score(Score score) { + this.score = score; + return this; + } + + /** + * Set the advices of the rating. + * + * @param advices to be added to the list of advices. + * @return the Rating. + */ + public Rating advice(Advices advices) { + if (this.advices == null) { + this.advices = new ArrayList<>(); + } + this.advices.add(advices); + return this; + } + + /** + * Return the list of {@link Advice}s of the rating. + * + * @return the list of {@link Advice}s of the rating. + */ + @JsonGetter("advices") + public List advices() { + return advices; + } + + /** + * Set the advices of the rating. + * + * @param advices to be added to the list of advices. + * @return the Rating. + */ + public Rating advices(List advices) { + if (this.advices == null) { + this.advices = new ArrayList<>(); + } + this.advices.addAll(advices); + return this; + } +} \ No newline at end of file diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/model/Score.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/model/Score.java new file mode 100644 index 000000000..e74b0a253 --- /dev/null +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/format/model/Score.java @@ -0,0 +1,199 @@ +package com.sap.oss.phosphor.fosstars.tool.format.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.sap.oss.phosphor.fosstars.model.value.ScoreValue; +import java.util.ArrayList; +import java.util.List; + +/** + * The Score class that encloses @link {@link ScoreValue} to be used in serialization. + */ +public class Score { + + /** + * The name of the score. + */ + private String name; + + /** + * The value of the score. + */ + private String value; + + /** + * The sub-scores summarized to calculate the score. + */ + private List subScores = new ArrayList<>(); + + /** + * The features used to calculate the score. + */ + private List features = new ArrayList<>(); + + /** + * The weight of the score. + */ + private String weight; + + /** + * The confidence of the score. + */ + private String confidence; + + /** + * Default constructor. + */ + public Score() { + } + + /** + * Initializes a Score instance. + * + * @param name the name of the score. + * @param value the value of the score. + * @param weight the weight of the score. + * @param confidence the confidence of the score. + */ + @JsonCreator + public Score(@JsonProperty("name") String name, @JsonProperty("value") String value, + @JsonProperty("weight") String weight, @JsonProperty("confidence") String confidence) { + this.name = name; + this.value = value; + this.weight = weight; + this.confidence = confidence; + } + + /** + * Return the name of the score. + * + * @return the name of the score. + */ + @JsonGetter("name") + public String name() { + return name; + } + + /** + * Set the name of the score. + * + * @param name the name of the score. + * @return the score. + */ + public Score name(String name) { + this.name = name; + return this; + } + + /** + * Return the value of the score. + * + * @return the value of the score. + */ + @JsonGetter("value") + public String value() { + return value; + } + + /** + * Set the value of the score. + * + * @param value the value of the score. + * @return the score. + */ + public Score value(String value) { + this.value = value; + return this; + } + + /** + * Set the sub-scores of the score. + * + * @param subScore to be added to the list of subScores. + * @return the score. + */ + public Score subScore(Score subScore) { + if (this.subScores == null) { + this.subScores = new ArrayList<>(); + } + this.subScores.add(subScore); + return this; + } + + /** + * Set the features of the score. + * + * @param feature to be added to the list of features. + * @return the score. + */ + public Score feature(Feature feature) { + if (this.features == null) { + this.features = new ArrayList<>(); + } + this.features.add(feature); + return this; + } + + /** + * Return the weight of the score. + * + * @return the weight of the score. + */ + @JsonGetter("weight") + public String weight() { + return weight; + } + + /** + * Set the weight of the score. + * + * @param weight the weight of the score. + * @return the score. + */ + public Score weight(String weight) { + this.weight = weight; + return this; + } + + /** + * Return the confidence of the score. + * + * @return the confidence of the score. + */ + @JsonGetter("confidence") + public String confidence() { + return confidence; + } + + /** + * Set the confidence of the score. + * + * @param confidence the confidence of the score. + * @return the score. + */ + public Score confidence(String confidence) { + this.confidence = confidence; + return this; + } + + /** + * Return the sub-scores of the score. + * + * @return the sub-scores of the score. + */ + @JsonGetter("subScores") + public List subscores() { + return subScores; + } + + /** + * Return the features of the score. + * + * @return the features of the score. + */ + @JsonGetter("features") + public List features() { + return features; + } +} \ No newline at end of file diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/report/OssSecurityRatingJsonReporter.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/report/OssSecurityRatingJsonReporter.java new file mode 100644 index 000000000..fefefad27 --- /dev/null +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/report/OssSecurityRatingJsonReporter.java @@ -0,0 +1,59 @@ +package com.sap.oss.phosphor.fosstars.tool.report; + +import com.sap.oss.phosphor.fosstars.advice.Advisor; +import com.sap.oss.phosphor.fosstars.model.rating.oss.OssSecurityRating; +import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; +import com.sap.oss.phosphor.fosstars.tool.format.JsonPrettyPrinter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * This reporter takes a number of projects and generates a json report. + */ +public class OssSecurityRatingJsonReporter extends OssSecurityRatingMarkdownReporter { + + /** + * Initializes a new reporter. + * + * @param outputDirectory An output directory. + * @param rating A rating. + * @param advisor An advisor for calculated ratings. + * @throws IOException If something went wrong (for example, the output directory doesn't exist, + * or the extra projects couldn't be loaded). + */ + public OssSecurityRatingJsonReporter( + String outputDirectory, OssSecurityRating rating, Advisor advisor) throws IOException { + this(outputDirectory, NO_EXTRA_SOURCE, rating, advisor); + } + + /** + * Initializes a new reporter. + * + * @param outputDirectory An output directory. + * @param extraSourceFileName A JSON file with serialized extra projects. + * @param rating A rating. + * @param advisor An advisor for calculated ratings. + * @throws IOException If something went wrong (for example, the output directory doesn't exist, + * or the extra projects couldn't be loaded). + */ + public OssSecurityRatingJsonReporter( + String outputDirectory, String extraSourceFileName, OssSecurityRating rating, Advisor advisor) + throws IOException { + super(outputDirectory, extraSourceFileName, rating, advisor, new JsonPrettyPrinter(advisor)); + } + + @Override + protected String writeReport(GitHubProject project, String projectPath, + Path organizationDirectory) + throws IOException { + String details = formatter().print(project); + + String projectReportFilename = String.format("%s.json", project.name()); + Files.write( + organizationDirectory.resolve(projectReportFilename), + details.getBytes()); + + return projectReportFilename; + } +} diff --git a/src/main/java/com/sap/oss/phosphor/fosstars/tool/report/OssSecurityRatingMarkdownReporter.java b/src/main/java/com/sap/oss/phosphor/fosstars/tool/report/OssSecurityRatingMarkdownReporter.java index 36997954e..4887cb6eb 100644 --- a/src/main/java/com/sap/oss/phosphor/fosstars/tool/report/OssSecurityRatingMarkdownReporter.java +++ b/src/main/java/com/sap/oss/phosphor/fosstars/tool/report/OssSecurityRatingMarkdownReporter.java @@ -37,7 +37,7 @@ public class OssSecurityRatingMarkdownReporter extends AbstractReporter projects) throws IOException { Files.createDirectories(organizationDirectory); } - String details = PROJECT_DETAILS_TEMPLATE - .replace("%PROJECT_URL%", project.scm().toString()) - .replace("%UPDATED_DATE%", - project.ratingValueDate().map(DATE_FORMAT::format).orElse(UNKNOWN)) - .replace("%PROJECT_NAME%", projectPath) - .replace("%DETAILS%", detailsOf(project)); - - String projectReportFilename = String.format("%s.md", project.name()); - Files.write( - organizationDirectory.resolve(projectReportFilename), - details.getBytes()); - String relativePathToDetails = String.format("%s/%s", - project.organization().name(), projectReportFilename); + project.organization().name(), writeReport(project, projectPath, organizationDirectory)); String labelLink = String.format(LINK_TEMPLATE, labelOf(project), relativePathToDetails); String nameLink = String.format(LINK_TEMPLATE, nameOf(project), relativePathToDetails); @@ -232,13 +240,47 @@ public void runFor(List projects) throws IOException { Files.write(path, buildReportWith(projectsTable.toString(), statistics).getBytes()); } + /** + * Write the report of the associated project. + * + * @param project The GitHub project to get the rating. + * @param projectPath The path to the project output. + * @param organizationDirectory The path of the organization folder to write the projects' + * reports. + * @return The file name of the report. + */ + protected String writeReport(GitHubProject project, String projectPath, + Path organizationDirectory) + throws IOException { + String details = PROJECT_DETAILS_TEMPLATE + .replace("%PROJECT_URL%", project.scm().toString()) + .replace("%UPDATED_DATE%", + project.ratingValueDate().map(DATE_FORMAT::format).orElse(UNKNOWN)) + .replace("%PROJECT_NAME%", projectPath) + .replace("%DETAILS%", detailsOf(project)); + + String projectReportFilename = String.format("%s.md", project.name()); + Files.write( + organizationDirectory.resolve(projectReportFilename), + details.getBytes()); + + return projectReportFilename; + } + + /** + * Return the formatter to be used to generate the project details. + */ + protected Formatter formatter() { + return formatter; + } + /** * Prints out a name of a project. * * @param project The project. * @return A name of the project. */ - private static String nameOf(GitHubProject project) { + protected static String nameOf(GitHubProject project) { return insert("
", NAME_LINE_LENGTH, project.scm().getPath().replaceFirst("/", "")); } @@ -251,7 +293,7 @@ private static String nameOf(GitHubProject project) { * @param content The original string. * @return The updated string. */ - static String insert(String string, int n, String content) { + protected static String insert(String string, int n, String content) { if (content.length() <= n) { return content; } @@ -276,7 +318,7 @@ static String insert(String string, int n, String content) { * @return The report. * @throws IOException If something went wrong. */ - private String buildReportWith(String table, Statistics statistics) throws IOException { + protected String buildReportWith(String table, Statistics statistics) throws IOException { try (InputStream is = OssSecurityRatingMarkdownReporter.class .getResourceAsStream("OssSecurityRatingMarkdownReporterMainTemplate.md")) { @@ -334,7 +376,7 @@ private static String format(double value) { * @param project The project. * @return The details of the rating calculation. */ - private String detailsOf(GitHubProject project) { + protected String detailsOf(GitHubProject project) { if (!project.ratingValue().isPresent()) { return UNKNOWN; } @@ -344,14 +386,14 @@ private String detailsOf(GitHubProject project) { /** * Formats a date when a rating was calculated for a project. */ - private static String lastUpdateOf(GitHubProject project) { + protected static String lastUpdateOf(GitHubProject project) { return project.ratingValueDate().map(DATE_FORMAT::format).orElse(UNKNOWN); } /** * Formats a confidence of a rating of a project. */ - private static String confidenceOf(GitHubProject project) { + protected static String confidenceOf(GitHubProject project) { Optional something = project.ratingValue(); if (!something.isPresent()) { return UNKNOWN; @@ -363,7 +405,7 @@ private static String confidenceOf(GitHubProject project) { /** * Formats a label of a rating of a project. */ - private static String labelOf(GitHubProject project) { + protected static String labelOf(GitHubProject project) { Optional something = project.ratingValue(); if (!something.isPresent()) { return UNKNOWN; @@ -375,7 +417,7 @@ private static String labelOf(GitHubProject project) { /** * Formats a score of a project. */ - private static String scoreOf(GitHubProject project) { + protected static String scoreOf(GitHubProject project) { Optional something = project.ratingValue(); if (!something.isPresent()) { return UNKNOWN; @@ -412,7 +454,7 @@ private static String actualValueOf(ScoreValue scoreValue) { * @return A number of stars or {@link #UNKNOWN_NUMBER_OF_STARS} * if the number of stars is unknown. */ - private static int starsOf(GitHubProject project) { + static int starsOf(GitHubProject project) { if (!project.ratingValue().isPresent()) { return UNKNOWN_NUMBER_OF_STARS; } @@ -456,7 +498,7 @@ private static int stars(ScoreValue scoreValue) { /** * This class holds statistics about projects. */ - private static class Statistics { + protected static class Statistics { /** * Total number of projects. diff --git a/src/test/java/com/sap/oss/phosphor/fosstars/tool/report/OssSecurityRatingJsonReporterTest.java b/src/test/java/com/sap/oss/phosphor/fosstars/tool/report/OssSecurityRatingJsonReporterTest.java new file mode 100644 index 000000000..8d2d86699 --- /dev/null +++ b/src/test/java/com/sap/oss/phosphor/fosstars/tool/report/OssSecurityRatingJsonReporterTest.java @@ -0,0 +1,132 @@ +package com.sap.oss.phosphor.fosstars.tool.report; + +import static com.sap.oss.phosphor.fosstars.model.rating.oss.OssSecurityRating.SecurityLabel.BAD; +import static com.sap.oss.phosphor.fosstars.model.rating.oss.OssSecurityRating.SecurityLabel.GOOD; +import static com.sap.oss.phosphor.fosstars.model.rating.oss.OssSecurityRating.SecurityLabel.MODERATE; +import static com.sap.oss.phosphor.fosstars.model.rating.oss.OssSecurityRating.SecurityLabel.UNCLEAR; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.sap.oss.phosphor.fosstars.advice.oss.github.OssSecurityGithubAdvisor; +import com.sap.oss.phosphor.fosstars.model.Label; +import com.sap.oss.phosphor.fosstars.model.RatingRepository; +import com.sap.oss.phosphor.fosstars.model.Score; +import com.sap.oss.phosphor.fosstars.model.rating.oss.OssSecurityRating; +import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject; +import com.sap.oss.phosphor.fosstars.model.value.RatingValue; +import com.sap.oss.phosphor.fosstars.model.value.ScoreValue; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.junit.Test; + +public class OssSecurityRatingJsonReporterTest { + + @Test + public void testReport() throws IOException { + Path outputDirectory = Files.createTempDirectory( + OssSecurityRatingJsonReporterTest.class.getName()); + try { + OssSecurityRating rating = RatingRepository.INSTANCE.rating(OssSecurityRating.class); + + GitHubProject goodProject = new GitHubProject("org", "good"); + goodProject.set( + new RatingValue( + new ScoreValue(rating.score()).set(Score.MAX).confidence(10.0), + GOOD)); + + GitHubProject moderateProject = new GitHubProject("org", "moderate"); + moderateProject.set( + new RatingValue( + new ScoreValue(rating.score()).set(5.0).confidence(9.0), + MODERATE)); + + GitHubProject badProject = new GitHubProject("org", "bad"); + badProject.set( + new RatingValue( + new ScoreValue(rating.score()).set(Score.MIN).confidence(8.0), + BAD)); + + GitHubProject projectWithLowConfidence = new GitHubProject("org", "unclear"); + projectWithLowConfidence.set( + new RatingValue( + new ScoreValue(rating.score()).set(Score.MIN).confidence(1.0), + UNCLEAR)); + + List projects = Arrays.asList( + goodProject, moderateProject, badProject, projectWithLowConfidence + ); + + OssSecurityRatingJsonReporter reporter = new OssSecurityRatingJsonReporter( + outputDirectory.toString(), null, rating, new OssSecurityGithubAdvisor()); + reporter.runFor(projects); + + Path reportFileName = outputDirectory.resolve( + OssSecurityRatingJsonReporter.REPORT_FILENAME); + assertTrue(Files.exists(reportFileName)); + + String report = new String(Files.readAllBytes(reportFileName)); + System.out.println(report); + + assertFalse(report.isEmpty()); + assertTrue(report.contains("Total")); + for (Label label : OssSecurityRating.SecurityLabel.values()) { + assertTrue(report.contains(label.name())); + } + assertTrue(report.contains(OssSecurityRatingJsonReporter.UNKNOWN)); + assertTrue(report.contains("org/good")); + assertTrue(report.contains("org/bad")); + assertTrue(report.contains("org/moderate")); + assertTrue(report.contains("10.0")); + assertTrue(report.contains("0.0")); + assertTrue(report.contains("5.0")); + assertEquals(1, linesWith("100%", report)); + assertEquals(4, linesWith("25.0%", report)); + assertEquals(1, linesWith("0.0%", report)); + } finally { + FileUtils.forceDeleteOnExit(outputDirectory.toFile()); + } + } + + @Test + public void testCreatingReportDirectory() throws IOException { + Path baseDirectory = Files.createTempDirectory( + OssSecurityRatingJsonReporterTest.class.getName()); + Path outputDirectory = baseDirectory.resolve("one").resolve("two"); + if (Files.exists(outputDirectory)) { + fail("Report directory already exists. Please fix the test or its environment"); + } + try { + OssSecurityRatingJsonReporter reporter = new OssSecurityRatingJsonReporter( + outputDirectory.toString(), + null, + RatingRepository.INSTANCE.rating(OssSecurityRating.class), + new OssSecurityGithubAdvisor()); + reporter.runFor(Collections.emptyList()); + } finally { + FileUtils.forceDeleteOnExit(baseDirectory.toFile()); + } + } + + private static int linesWith(String string, String content) throws IOException { + BufferedReader reader = new BufferedReader(new StringReader(content)); + + String line; + int n = 0; + while ((line = reader.readLine()) != null) { + if (line.contains(string)) { + n++; + } + } + + return n; + } +} \ No newline at end of file