org.whitesource.agent.report.PolicyCheckReport.java Source code

Java tutorial

Introduction

Here is the source code for org.whitesource.agent.report.PolicyCheckReport.java

Source

/**
 * Copyright (C) 2012 White Source Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.whitesource.agent.report;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.whitesource.agent.api.dispatch.BaseCheckPoliciesResult;
import org.whitesource.agent.api.model.PolicyCheckResourceNode;
import org.whitesource.agent.api.model.RequestPolicyInfo;
import org.whitesource.agent.api.model.ResourceInfo;
import org.whitesource.agent.report.summary.PolicyRejectionReport;
import org.whitesource.agent.report.summary.RejectingPolicy;
import org.whitesource.agent.report.summary.RejectedLibrary;
import org.whitesource.agent.report.model.LicenseHistogramDataPoint;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.ZipOutputStream;

/**
 * A report generator for policy check results.
 *
 * @author Edo.Shor
 */
public class PolicyCheckReport {

    /* --- Static members --- */

    private static final String TEMPLATE_FOLDER = "templates/";
    private static final String TEMPLATE_FILE = "policy-check.vm";
    private static final String CSS_FILE = "wss.css";

    private static final float MAX_BAR_HEIGHT = 50;
    private static final int LICENSE_LIMIT = 6;
    private static final String OTHER_LICENSE = "Other types";

    public static final String REJECT = "Reject";
    public static final String CHECK_POLICIES_JSON_FILE = "checkPolicies-json.txt";
    public static final String POLICY_REJECTION_SUMMARY_FILE = "policyRejectionSummary.json";

    public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

    /* --- Members --- */

    private BaseCheckPoliciesResult result;
    private String buildName;
    private String buildNumber;

    /* --- Constructors --- */

    /**
     * Constructor
     *
     * @param result
     */
    public PolicyCheckReport(BaseCheckPoliciesResult result) {
        this.result = result;
    }

    /**
     * Constructor
     *
     * @param result report
     * @param buildName of the report
     * @param buildNumber of the report
     */
    public PolicyCheckReport(BaseCheckPoliciesResult result, String buildName, String buildNumber) {
        this(result);
        this.buildName = buildName;
        this.buildNumber = buildNumber;
    }

    /* --- Public methods --- */

    /**
     * The method generate the policy check report
     *
     * @param outputDir Directory where report files will be created.
     * @param pack      <code>True</code> to create a zip file from the resulting directory.
     * @return  File reference to the resulting report.
     * @throws IOException In case of errors during report generation process.
     */
    public File generate(File outputDir, boolean pack) throws IOException {
        return generate(outputDir, pack, null);
    }

    /**
     * The method generate the policy check report
     *
     * @param outputDir Directory where report files will be created.
     * @param pack      <code>True</code> to create a zip file from the resulting directory.
     * @param properties Properties for template engine.
     *
     * @return  File reference to the resulting report.
     *
     * @throws IOException In case of errors during report generation process.
     */
    public File generate(File outputDir, boolean pack, Properties properties) throws IOException {
        if (result == null) {
            throw new IllegalStateException("Check policies result is null");
        }

        // prepare working directory
        File workDir = new File(outputDir, "whitesource");
        File reportFile = workDir;
        if (!workDir.exists() && !workDir.mkdir()) {
            throw new IOException("Unable to make output directory: " + workDir);
        }

        // create actual report
        VelocityEngine engine = createTemplateEngine(properties);
        VelocityContext context = createTemplateContext();
        FileWriter fw = new FileWriter(new File(workDir, "index.html"));
        try {
            engine.mergeTemplate(TEMPLATE_FOLDER + TEMPLATE_FILE, "UTF-8", context, fw);
            fw.flush();
        } finally {
            FileUtils.close(fw);
        }

        // copy resources
        copyReportResources(workDir);

        // package report into a zip archive
        if (pack) {
            reportFile = new File(outputDir, "whitesource.zip");
            ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(reportFile));
            FileUtils.packDirectory(workDir, zos);
            FileUtils.deleteRecursive(workDir);
        }

        return reportFile;
    }

    public File generateJson(File outputDir) throws IOException {
        if (result == null) {
            throw new IllegalStateException("Check policies result is null");
        }

        // prepare working directory
        File workDir = new File(outputDir, "whitesource");
        if (!workDir.exists() && !workDir.mkdir()) {
            throw new IOException("Unable to make output directory: " + workDir);
        }

        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        writeToFile(new File(workDir, CHECK_POLICIES_JSON_FILE), gson.toJson(result));

        // summarized policy rejection report
        Map<RequestPolicyInfo, RejectingPolicy> policyToSummaryMap = new HashMap<RequestPolicyInfo, RejectingPolicy>();
        for (Map.Entry<String, PolicyCheckResourceNode> entry : result.getExistingProjects().entrySet()) {
            updatePolicyRejectionSummary(entry.getValue(), entry.getKey(), policyToSummaryMap);
        }
        for (Map.Entry<String, PolicyCheckResourceNode> entry : result.getNewProjects().entrySet()) {
            updatePolicyRejectionSummary(entry.getValue(), entry.getKey(), policyToSummaryMap);
        }

        PolicyRejectionReport report = new PolicyRejectionReport();
        for (RejectingPolicy rejectingPolicy : policyToSummaryMap.values()) {
            report.getRejectingPolicies().add(rejectingPolicy);
            report.getSummary().setTotalRejectedLibraries(report.getSummary().getTotalRejectedLibraries()
                    + rejectingPolicy.getRejectedLibraries().size());
        }
        writeToFile(new File(workDir, POLICY_REJECTION_SUMMARY_FILE), gson.toJson(report));

        return workDir;
    }

    /* --- Protected methods --- */

    /**
     * Create and initialize a template engine to use for generating reports.
     *
     * @param properties properties to use for engine initialization. May be null.
     *
     * @return A new instance of th template engine to use.
     */
    protected VelocityEngine createTemplateEngine(Properties properties) {
        Properties actualProperties = properties;
        if (actualProperties == null) {
            actualProperties = new Properties();
        }

        String resourceLoader = actualProperties.getProperty(VelocityEngine.RESOURCE_LOADER);
        if (StringUtils.isBlank(resourceLoader)) {
            actualProperties.setProperty(VelocityEngine.RESOURCE_LOADER, "classpath");
            actualProperties.setProperty("classpath.resource.loader.class",
                    ClasspathResourceLoader.class.getName());
        }

        String loggerClass = actualProperties.getProperty(VelocityEngine.RUNTIME_LOG_LOGSYSTEM_CLASS);
        if (StringUtils.isBlank(loggerClass)) {
            actualProperties.setProperty(VelocityEngine.RUNTIME_LOG_LOGSYSTEM_CLASS,
                    "org.apache.velocity.runtime.log.NullLogChute");
        }

        VelocityEngine velocity = new VelocityEngine(actualProperties);
        velocity.init();

        return velocity;
    }

    /**
     * Create the context holding all the information of the report.
     * @return the velocity context
     */
    protected VelocityContext createTemplateContext() {
        VelocityContext context = new VelocityContext();
        context.put("result", result);
        context.put("hasRejections", result.hasRejections());
        context.put("licenses", createLicenseHistogram(result));
        context.put("creationTime", new SimpleDateFormat(DATE_FORMAT).format(new Date()));

        if (StringUtils.isNotBlank(buildName)) {
            context.put("buildName", buildName);
        }
        if (StringUtils.isNotBlank(buildNumber)) {
            context.put("buildNumber", buildNumber);
        }
        return context;
    }

    /**
     * Copy required resources for the report.
     *
     * @param workDir Report work directory.
     *
     * @throws IOException exception5
     */
    protected void copyReportResources(File workDir) throws IOException {
        FileUtils.copyResource(TEMPLATE_FOLDER + CSS_FILE, new File(workDir, CSS_FILE));
    }

    protected void writeToFile(File file, String json) throws IOException {
        FileWriter fw = new FileWriter(file);
        try {
            fw.write(StringEscapeUtils.unescapeJava(json));
            fw.flush();
        } finally {
            FileUtils.close(fw);
        }
    }

    /* --- Private methods --- */

    private Collection<LicenseHistogramDataPoint> createLicenseHistogram(BaseCheckPoliciesResult result) {
        Collection<LicenseHistogramDataPoint> dataPoints = new ArrayList<LicenseHistogramDataPoint>();

        // create distribution histogram
        Map<String, Integer> licenseHistogram = new HashMap<String, Integer>();
        for (Map.Entry<String, Collection<ResourceInfo>> entry : result.getProjectNewResources().entrySet()) {
            for (ResourceInfo resource : entry.getValue()) {
                for (String license : resource.getLicenses()) {
                    licenseHistogram.put(license, MapUtils.getInteger(licenseHistogram, license, 0) + 1);
                }
            }
        }

        // sort by count descending
        List<Map.Entry<String, Integer>> licenses = new ArrayList<Map.Entry<String, Integer>>(
                licenseHistogram.entrySet());
        Collections.sort(licenses, new ValueComparator());

        // create data points
        if (!licenses.isEmpty()) {
            // first licenses
            for (Map.Entry<String, Integer> entry : licenses.subList(0, Math.min(LICENSE_LIMIT, licenses.size()))) {
                dataPoints.add(new LicenseHistogramDataPoint(entry.getKey(), entry.getValue()));
            }

            // aggregation of histogram tail
            int tailSize = licenses.size() - LICENSE_LIMIT;
            int tailSum = 0;
            if (tailSize > 0) {
                for (Map.Entry<String, Integer> entry : licenses.subList(LICENSE_LIMIT, licenses.size())) {
                    tailSum += entry.getValue();
                }
                dataPoints.add(new LicenseHistogramDataPoint(OTHER_LICENSE + " (" + tailSize + ")", tailSum));
            }

            // normalize bar height
            float factor = MAX_BAR_HEIGHT / (float) Math.max(tailSum, licenses.get(0).getValue());
            for (LicenseHistogramDataPoint dataPoint : dataPoints) {
                dataPoint.setHeight((int) (factor * dataPoint.getOccurrences()));
            }
        }

        return dataPoints;
    }

    private void updatePolicyRejectionSummary(PolicyCheckResourceNode node, String projectName,
            Map<RequestPolicyInfo, RejectingPolicy> policyResourceMap) {
        RequestPolicyInfo policy = node.getPolicy();
        if (policy != null && policy.getActionType().equals(REJECT)) {
            RejectingPolicy rejectingPolicy = policyResourceMap.get(policy);
            if (rejectingPolicy == null) {
                rejectingPolicy = new RejectingPolicy(policy.getDisplayName(), policy.getFilterType(),
                        policy.isProjectLevel(), policy.isInclusive());
                policyResourceMap.put(policy, rejectingPolicy);
            }

            Set<RejectedLibrary> rejectedResources = rejectingPolicy.getRejectedLibraries();
            ResourceInfo resource = node.getResource();
            RejectedLibrary rejectedLibrary = new RejectedLibrary(resource.getDisplayName(), resource.getSha1(),
                    resource.getLink());
            if (rejectedResources.contains(rejectedLibrary)) {
                for (RejectedLibrary summary : rejectedResources) {
                    if (rejectedLibrary.equals(summary)) {
                        // add project to existing resource
                        summary.getProjects().add(projectName);
                        break;
                    }
                }
            } else {
                rejectedLibrary.getProjects().add(projectName);
                rejectedResources.add(rejectedLibrary);
            }
        }

        for (PolicyCheckResourceNode childNode : node.getChildren()) {
            updatePolicyRejectionSummary(childNode, projectName, policyResourceMap);
        }
    }

    /* --- Nested classes --- */

    static class ValueComparator implements Comparator<Map.Entry<String, Integer>>, Serializable {

        private static final long serialVersionUID = 2134689708073092860L;

        @Override
        public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
            return o2.getValue().compareTo(o1.getValue());
        }
    }

    /* --- Getters / Setters --- */

    public BaseCheckPoliciesResult getResult() {
        return result;
    }

    public void setResult(BaseCheckPoliciesResult result) {
        this.result = result;
    }

    public String getBuildName() {
        return buildName;
    }

    public void setBuildName(String buildName) {
        this.buildName = buildName;
    }

    public String getBuildNumber() {
        return buildNumber;
    }

    public void setBuildNumber(String buildNumber) {
        this.buildNumber = buildNumber;
    }
}