You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gg...@apache.org on 2019/01/23 15:34:06 UTC

[karaf] branch master updated: [KARAF-6109] Combine 3 reports (possible more) in single XML

This is an automated email from the ASF dual-hosted git repository.

ggrzybek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/master by this push:
     new 2489f0b  [KARAF-6109] Combine 3 reports (possible more) in single XML
2489f0b is described below

commit 2489f0b40eaa6ccbdb8d7c992f2086b99736a6d6
Author: Grzegorz Grzybek <gr...@gmail.com>
AuthorDate: Wed Jan 23 16:33:47 2019 +0100

    [KARAF-6109] Combine 3 reports (possible more) in single XML
---
 assemblies/apache-karaf/pom.xml                    |   1 +
 .../org/apache/karaf/profile/assembly/Builder.java | 279 ++++++++++++++-------
 profile/src/main/resources/bundle-report.xslt      | 194 ++++++++++----
 .../org/apache/karaf/tooling/AssemblyMojo.java     |  14 ++
 4 files changed, 350 insertions(+), 138 deletions(-)

diff --git a/assemblies/apache-karaf/pom.xml b/assemblies/apache-karaf/pom.xml
index e4bd2da..2871373 100644
--- a/assemblies/apache-karaf/pom.xml
+++ b/assemblies/apache-karaf/pom.xml
@@ -161,6 +161,7 @@
                     </libraries>
                     <javase>1.8</javase>
                     <generateConsistencyReport>${project.build.directory}</generateConsistencyReport>
+                    <consistencyReportProjectName>Apache Karaf (full)</consistencyReportProjectName>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
index 42fb74f..b802c53 100644
--- a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
@@ -52,7 +52,9 @@ import java.util.TreeSet;
 import java.util.UUID;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.BiPredicate;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.jar.Attributes;
 import java.util.jar.Manifest;
 import java.util.regex.Pattern;
@@ -306,6 +308,8 @@ public class Builder {
     List<String> pidsToExtract = new LinkedList<>();
     boolean writeProfiles;
     String generateConsistencyReport;
+    String consistencyReportProjectName;
+    String consistencyReportProjectVersion;
 
     private ScheduledExecutorService executor;
     private DownloadManager manager;
@@ -577,6 +581,22 @@ public class Builder {
     }
 
     /**
+     * Configure project name to be used in consistency report
+     * @param consistencyReportProjectName
+     */
+    public void setConsistencyReportProjectName(String consistencyReportProjectName) {
+        this.consistencyReportProjectName = consistencyReportProjectName;
+    }
+
+    /**
+     * Configure project version to be used in consistency report
+     * @param consistencyReportProjectVersion
+     */
+    public void setConsistencyReportProjectVersion(String consistencyReportProjectVersion) {
+        this.consistencyReportProjectVersion = consistencyReportProjectVersion;
+    }
+
+    /**
      * Configure Karaf version to target. This impacts the way some configuration files are generated.
      * @param karafVersion
      * @return
@@ -1107,13 +1127,19 @@ public class Builder {
 
         if (generateConsistencyReport != null) {
             File directory = new File(generateConsistencyReport);
-            if (directory.isDirectory()) {
-                LOGGER.info("Writing bundle report");
-                generateConsistencyReport(karRepositories, allInstalledFeatures, installedProfile, new File(directory, "bundle-report-full.xml"), true);
-                generateConsistencyReport(karRepositories, allInstalledFeatures, installedProfile, new File(directory, "bundle-report.xml"), false);
-                Files.copy(getClass().getResourceAsStream("/bundle-report.xslt"),
-                        directory.toPath().resolve("bundle-report.xslt"),
-                        StandardCopyOption.REPLACE_EXISTING);
+            if (directory.isFile()) {
+                LOGGER.warn("Can't generate consistency report into {} - it's not a directory", generateConsistencyReport);
+            } else {
+                if (!directory.exists()) {
+                    directory.mkdirs();
+                }
+                if (directory.isDirectory()) {
+                    LOGGER.info("Writing bundle report");
+                    generateConsistencyReport(karRepositories, allInstalledFeatures, installedProfile, new File(directory, "bundle-report.xml"));
+                    Files.copy(getClass().getResourceAsStream("/bundle-report.xslt"),
+                            directory.toPath().resolve("bundle-report.xslt"),
+                            StandardCopyOption.REPLACE_EXISTING);
+                }
             }
         }
     }
@@ -1121,9 +1147,11 @@ public class Builder {
     /**
      * Produces human readable XML with <em>feature consistency report</em>.
      * @param repositories
+     * @param allInstalledFeatures
+     * @param installedProfile
      * @param result
      */
-    public void generateConsistencyReport(Map<String, Features> repositories, Set<Feature> allInstalledFeatures, Profile installedProfile, File result, boolean full) {
+    public void generateConsistencyReport(Map<String, Features> repositories, Set<Feature> allInstalledFeatures, Profile installedProfile, File result) {
         Profile installedOverlay = Profiles.getOverlay(installedProfile, allProfiles, environment);
         Profile installedEffective = Profiles.getEffective(installedOverlay, false);
 
@@ -1134,107 +1162,174 @@ public class Builder {
         FeatureSelector selector = new FeatureSelector(allInstalledFeatures);
         Set<Feature> effectiveInstalledFeatures = selector.getMatching(installFeatures);
 
-        Map<String, String> featureId2repository = new HashMap<>();
-        // list of feature IDs containing given bundle URIs
-        Map<String, Set<String>> bundle2featureId = new TreeMap<>(new URIAwareComparator());
-        // map of groupId/artifactId to full URI list to detect "duplicates"
-        Map<String, List<String>> ga2uri = new TreeMap<>();
-        Set<String> haveDuplicates = new HashSet<>();
-
-        // collect closure of bundles and features
-        repositories.forEach((name, features) -> {
-            if (full || !features.isBlacklisted()) {
-                features.getFeature().forEach(feature -> {
-                    if (full || (!feature.isBlacklisted() && effectiveInstalledFeatures.contains(feature))) {
-                        featureId2repository.put(feature.getId(), name);
-                        feature.getBundle().forEach(bundle -> {
-                            // normal bundles of feature
-                            bundle2featureId.computeIfAbsent(bundle.getLocation().trim(), k -> new TreeSet<>()).add(feature.getId());
-                        });
-                        feature.getConditional().forEach(cond -> {
-                            cond.asFeature().getBundles().forEach(bundle -> {
-                                // conditional bundles of feature
-                                bundle2featureId.computeIfAbsent(bundle.getLocation().trim(), k -> new TreeSet<>()).add(feature.getId());
-                            });
-                        });
-                    }
-                });
-            }
-        });
-        // collect bundle URIs - for now, only wrap:mvn: and mvn: are interesting
-        bundle2featureId.keySet().forEach(uri -> {
-            String originalUri = uri;
-            if (uri.startsWith("wrap:mvn:")) {
-                uri = uri.substring(5);
-                if (uri.indexOf(";") > 0) {
-                    uri = uri.substring(0, uri.indexOf(";"));
-                }
-                if (uri.indexOf("$") > 0) {
-                    uri = uri.substring(0, uri.indexOf("$"));
-                }
-            }
-            if (uri.startsWith("mvn:")) {
-                try {
-                    LocationPattern pattern = new LocationPattern(uri);
-                    String ga = String.format("%s/%s", pattern.getGroupId(), pattern.getArtifactId());
-                    ga2uri.computeIfAbsent(ga, k -> new LinkedList<>()).add(originalUri);
-                } catch (IllegalArgumentException ignored) {
-                    /*
-                        <!-- hibernate-validator-osgi-karaf-features-5.3.4.Final-features.xml -->
-                        <feature name="hibernate-validator-paranamer" version="5.3.4.Final">
-                            <feature>hibernate-validator</feature>
-                            <bundle>wrap:mvn:com.thoughtworks.paranamer:paranamer:2.8</bundle>
-                        </feature>
-                     */
-                }
-            }
-        });
-        ga2uri.values().forEach(l -> {
-            if (l.size() > 1) {
-                haveDuplicates.addAll(l);
-            }
-        });
-
         if (result == null) {
             return;
         }
         try (BufferedWriter writer = new BufferedWriter(new FileWriter(result))) {
             writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
             writer.write("<?xml-stylesheet type=\"text/xsl\" href=\"bundle-report.xslt\"?>\n");
-            writer.write("<consistency-report xmlns=\"urn:apache:karaf:consistency:1.0\">\n");
-            writer.write("    <duplicates>\n");
-            ga2uri.forEach((key, uris) -> {
-                if (uris.size() > 1) {
-                    try {
-                        writer.write(String.format("        <duplicate ga=\"%s\">\n", key));
-                        for (String uri : uris) {
-                            writer.write(String.format("            <bundle uri=\"%s\">\n", sanitize(uri)));
-                            for (String fid : bundle2featureId.get(uri)) {
-                                writer.write(String.format("                <feature repository=\"%s\">%s</feature>\n", featureId2repository.get(fid), fid));
+            writer.write("<consistency-report xmlns=\"urn:apache:karaf:consistency:1.0\" project=\"" + consistencyReportProjectName + "\" version=\"" + consistencyReportProjectVersion + "\">\n");
+
+            ReportFlavor[] flavors = new ReportFlavor[] {
+                    all,
+                    notBlacklisted,
+                    new ReportFlavor() {
+                        @Override
+                        public String name() {
+                            return "installed";
+                        }
+
+                        @Override
+                        public boolean include(Features repository) {
+                            return !repository.isBlacklisted();
+                        }
+
+                        @Override
+                        public boolean include(Feature feature) {
+                            return !feature.isBlacklisted()
+                                    && effectiveInstalledFeatures.contains(feature);
+                        }
+                    }
+            };
+
+            for (ReportFlavor flavor : flavors) {
+                writer.write("<report flavor=\"" + flavor.name() + "\">\n");
+
+                Map<String, String> featureId2repository = new HashMap<>();
+                // list of feature IDs containing given bundle URIs
+                Map<String, Set<String>> bundle2featureId = new TreeMap<>(new URIAwareComparator());
+                // map of groupId/artifactId to full URI list to detect "duplicates"
+                Map<String, List<String>> ga2uri = new TreeMap<>();
+                Set<String> haveDuplicates = new HashSet<>();
+
+                // collect closure of bundles and features
+                repositories.forEach((name, features) -> {
+                    if (flavor.include(features)) {
+                        features.getFeature().forEach(feature -> {
+                            if (flavor.include(feature)) {
+                                featureId2repository.put(feature.getId(), name);
+                                feature.getBundle().forEach(bundle -> {
+                                    // normal bundles of feature
+                                    bundle2featureId.computeIfAbsent(bundle.getLocation().trim(), k -> new TreeSet<>()).add(feature.getId());
+                                });
+                                feature.getConditional().forEach(cond -> {
+                                    cond.asFeature().getBundles().forEach(bundle -> {
+                                        // conditional bundles of feature
+                                        bundle2featureId.computeIfAbsent(bundle.getLocation().trim(), k -> new TreeSet<>()).add(feature.getId());
+                                    });
+                                });
                             }
-                            writer.write("            </bundle>\n");
+                        });
+                    }
+                });
+                // collect bundle URIs - for now, only wrap:mvn: and mvn: are interesting
+                bundle2featureId.keySet().forEach(uri -> {
+                    String originalUri = uri;
+                    if (uri.startsWith("wrap:mvn:")) {
+                        uri = uri.substring(5);
+                        if (uri.indexOf(";") > 0) {
+                            uri = uri.substring(0, uri.indexOf(";"));
+                        }
+                        if (uri.indexOf("$") > 0) {
+                            uri = uri.substring(0, uri.indexOf("$"));
                         }
-                        writer.write("        </duplicate>\n");
-                    } catch (IOException e) {
                     }
+                    if (uri.startsWith("mvn:")) {
+                        try {
+                            LocationPattern pattern = new LocationPattern(uri);
+                            String ga = String.format("%s/%s", pattern.getGroupId(), pattern.getArtifactId());
+                            ga2uri.computeIfAbsent(ga, k -> new LinkedList<>()).add(originalUri);
+                        } catch (IllegalArgumentException ignored) {
+                        /*
+                            <!-- hibernate-validator-osgi-karaf-features-5.3.4.Final-features.xml -->
+                            <feature name="hibernate-validator-paranamer" version="5.3.4.Final">
+                                <feature>hibernate-validator</feature>
+                                <bundle>wrap:mvn:com.thoughtworks.paranamer:paranamer:2.8</bundle>
+                            </feature>
+                         */
+                        }
+                    }
+                });
+                ga2uri.values().forEach(l -> {
+                    if (l.size() > 1) {
+                        haveDuplicates.addAll(l);
+                    }
+                });
+                writer.write("    <duplicates>\n");
+                ga2uri.forEach((key, uris) -> {
+                    if (uris.size() > 1) {
+                        try {
+                            writer.write(String.format("        <duplicate ga=\"%s\">\n", key));
+                            for (String uri : uris) {
+                                writer.write(String.format("            <bundle uri=\"%s\">\n", sanitize(uri)));
+                                for (String fid : bundle2featureId.get(uri)) {
+                                    writer.write(String.format("                <feature repository=\"%s\">%s</feature>\n", featureId2repository.get(fid), fid));
+                                }
+                                writer.write("            </bundle>\n");
+                            }
+                            writer.write("        </duplicate>\n");
+                        } catch (IOException ignored) {
+                        }
+                    }
+                });
+                writer.write("    </duplicates>\n");
+                writer.write("    <bundles>\n");
+                for (String uri : bundle2featureId.keySet()) {
+                    writer.write(String.format("        <bundle uri=\"%s\" duplicate=\"%b\">\n", sanitize(uri), haveDuplicates.contains(uri)));
+                    for (String fid : bundle2featureId.get(uri)) {
+                        writer.write(String.format("            <feature>%s</feature>\n", fid));
+                    }
+                    writer.write("        </bundle>\n");
                 }
-            });
-            writer.write("    </duplicates>\n");
-            writer.write("    <bundles>\n");
-            for (String uri : bundle2featureId.keySet()) {
-                writer.write(String.format("        <bundle uri=\"%s\" duplicate=\"%b\">\n", sanitize(uri), haveDuplicates.contains(uri)));
-                for (String fid : bundle2featureId.get(uri)) {
-                    writer.write(String.format("            <feature>%s</feature>\n", fid));
-                }
-                writer.write("        </bundle>\n");
+                writer.write("    </bundles>\n");
+                writer.write("</report>\n");
             }
-            writer.write("    </bundles>\n");
             writer.write("</consistency-report>\n");
         } catch (IOException e) {
             throw new RuntimeException(e.getMessage(), e);
         }
     }
 
+    private interface ReportFlavor {
+        String name();
+        boolean include(Features repository);
+        boolean include(Feature feature);
+    }
+
+    private ReportFlavor all = new ReportFlavor() {
+        @Override
+        public String name() {
+            return "all";
+        }
+
+        @Override
+        public boolean include(Features repository) {
+            return true;
+        }
+
+        @Override
+        public boolean include(Feature feature) {
+            return true;
+        }
+    };
+
+    private ReportFlavor notBlacklisted = new ReportFlavor() {
+        @Override
+        public String name() {
+            return "available";
+        }
+
+        @Override
+        public boolean include(Features repository) {
+            return !repository.isBlacklisted();
+        }
+
+        @Override
+        public boolean include(Feature feature) {
+            return !feature.isBlacklisted();
+        }
+    };
+
     /**
      * Sanitize before putting to XML
      * @param uri
diff --git a/profile/src/main/resources/bundle-report.xslt b/profile/src/main/resources/bundle-report.xslt
index b4c21d5..0c43977 100644
--- a/profile/src/main/resources/bundle-report.xslt
+++ b/profile/src/main/resources/bundle-report.xslt
@@ -24,66 +24,167 @@
         <html>
             <head>
                 <meta charset="utf-8" />
+                <meta name="viewport" content="width=device-width, initial-scale=1" />
                 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous" />
-                <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous" />
-
                 <style type="text/css">
                     body { position: relative }
-                    h1 { margin: 2em 0 0.5em 0 }
+                    li.active a { background-color: #eee }
                     div.bundle { padding: 5px; margin: 5px 0 }
+                    #n { padding: 5px 15px }
+                    #n a { padding: 5px 15px; margin: 0 5px }
                 </style>
-                <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
-                <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
-                <script>
-                    $(function() {
-                        var hidden = true;
-                        $("#toggle").click(function(ev) {
-                            if (hidden) {
-                                $(".feature").show();
-                                hidden = false;
-                                $("#toggle").prop('value', 'Hide details');
-                            } else {
-                                $(".feature").hide();
-                                hidden = true;
-                                $("#toggle").prop('value', 'Show details');
-                            }
-                        });
-                    });
-                </script>
             </head>
-            <body data-spy="scroll" data-target="#n">
-                <div id="n">
-                    <nav id="nav" class="navbar navbar-inverse navbar-fixed-top" role="navigation">
-                        <div class="container">
-                            <div class="navbar-collapse collapse">
-                                <ul class="nav navbar-nav" role="tablist">
-                                    <li class="active">
-                                        <a href="#duplicates">Bundle <em>Duplicates</em> (<xsl:value-of select="count(/k:consistency-report/k:duplicates/k:duplicate)" />)</a>
+            <body data-spy="scroll" data-target="#n" data-offset="5">
+                <div class="container-fluid" style="position: fixed; z-index: 500; background-color: white; width: 100%; top: 0">
+                    <div class="row">
+                        <div class="col-lg-12">
+                            <h3><xsl:value-of select="/k:consistency-report/@project" /><xsl:value-of select="' '" /><xsl:value-of select="/k:consistency-report/@version" /> consistency report</h3>
+                        </div>
+                    </div>
+                    <div class="row">
+                        <div class="col-lg-12">
+                            <div class="container-fluid">
+                                <ul class="nav nav-tabs" role="tablist">
+                                    <li id="installed" class="active">
+                                        <a role="tab" data-toggle="tab" href="#r1" data-t1="#t1b,#t1c,#t3,#t4" data-t2="#t1a,#t2">Installed features only</a>
                                     </li>
                                     <li>
-                                        <a href="#bundles">All bundles (<xsl:value-of select="count(/k:consistency-report/k:bundles/k:bundle)" />)</a>
+                                        <a role="tab" data-toggle="tab" href="#r2" data-t1="#t1a,#t1c,#t2,#t4" data-t2="#t1b,#t3">Available features</a>
                                     </li>
-                                    <li style="min-height: 50px">
-                                        <input id="toggle" class="btn btn-default" style="margin-top: 8px" type="button" value="Show details" />
+                                    <li>
+                                        <a role="tab" data-toggle="tab" href="#r3" data-t1="#t1a,#t1b,#t2,#t3" data-t2="#t1c,#t4">All features (including blacklisted)</a>
                                     </li>
                                 </ul>
                             </div>
                         </div>
-                    </nav>
+                    </div>
+                    <div class="row">
+                        <div class="col-lg-12">
+                            <div class="collapse navbar-collapse" id="n">
+                                <ul class="nav navbar-nav">
+                                    <li class="nav-item active" id="t1a">
+                                        <a class="nav-link" href="#d0">
+                                            Bundle <em>Duplicates</em> (<xsl:value-of select="count(/k:consistency-report/k:report[@flavor='installed']/k:duplicates/k:duplicate)" />)
+                                        </a>
+                                    </li>
+                                    <li class="nav-item" id="t1b" style="display: none">
+                                        <a class="nav-link" href="#d0">
+                                            Bundle <em>Duplicates</em> (<xsl:value-of select="count(/k:consistency-report/k:report[@flavor='available']/k:duplicates/k:duplicate)" />)
+                                        </a>
+                                    </li>
+                                    <li class="nav-item" id="t1c" style="display: none">
+                                        <a class="nav-link" href="#d0">
+                                            Bundle <em>Duplicates</em> (<xsl:value-of select="count(/k:consistency-report/k:report[@flavor='all']/k:duplicates/k:duplicate)" />)
+                                        </a>
+                                    </li>
+                                    <li class="nav-item" id="t2">
+                                        <a class="nav-link" href="#d1">
+                                            All bundles (<xsl:value-of select="count(/k:consistency-report/k:report[@flavor='installed']/k:bundles/k:bundle)" />)
+                                        </a>
+                                    </li>
+                                    <li class="nav-item" id="t3" style="display: none">
+                                        <a class="nav-link" href="#d2">
+                                            All bundles (<xsl:value-of select="count(/k:consistency-report/k:report[@flavor='available']/k:bundles/k:bundle)" />)
+                                        </a>
+                                    </li>
+                                    <li class="nav-item" id="t4" style="display: none">
+                                        <a class="nav-link" href="#d3">
+                                            All bundles (<xsl:value-of select="count(/k:consistency-report/k:report[@flavor='all']/k:bundles/k:bundle)" />)
+                                        </a>
+                                    </li>
+                                </ul>
+                            </div>
+                        </div>
+                    </div>
                 </div>
+                <div id="d0" style="height: 150px">a</div>
 
-                <div class="container-fluid">
-                    <a id="duplicates" />
-                    <h1>Bundle <em>Duplicates</em></h1>
-                    <div class="help">(A <em>duplicate bundle</em> is a bundle that is referenced multiple times
-                        with the same Maven <code>groupId</code> and <code>artifactId</code> but with different versions.
-                        For each bundle that is used with different version, there's a list of all these versions and
-                        features (and their repositories) which include them.)</div>
-                    <xsl:apply-templates select="/k:consistency-report/k:duplicates" />
-                    <a id="bundles" />
-                    <h1>All bundles</h1>
-                    <xsl:call-template name="bundles" />
+                <div id="report" class="container-fluid" style="z-index: -1">
+                    <div class="row">
+                        <div class="tab-content col-lg-12">
+                            <div id="r1" class="tab-pane active">
+                                <div class="container-fluid">
+                                    <h4>All features that are actually installed in <code>etc/org.apache.karaf.features.cfg</code>.</h4>
+                                    <div>
+                                        <h1>Bundle <em>Duplicates</em></h1>
+                                        <div class="help">(A <em>duplicate bundle</em> is a bundle that is referenced multiple times
+                                            with the same Maven <code>groupId</code> and <code>artifactId</code> but with different versions.
+                                            For each bundle that is used with different version, there's a list of all these versions and
+                                            features (and their repositories) which include them.)</div>
+                                        <xsl:apply-templates select="/k:consistency-report/k:report[@flavor='installed']/k:duplicates" />
+                                    </div>
+                                    <div style="position: relative; margin-top: -150px">
+                                        <div id="d1" style="position: relative; top: 0; height: 150px"></div>
+                                        <div>
+                                            <h1>All bundles</h1>
+                                            <xsl:call-template name="bundles">
+                                                <xsl:with-param name="f" select="'installed'" />
+                                            </xsl:call-template>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div id="r2" class="tab-pane">
+                                <div class="container-fluid">
+                                    <h4>All non-blacklisted features availalable to install, referenced from non-blacklisted repositories.</h4>
+                                    <div>
+                                        <h1>Bundle <em>Duplicates</em></h1>
+                                        <div class="help">(A <em>duplicate bundle</em> is a bundle that is referenced multiple times
+                                            with the same Maven <code>groupId</code> and <code>artifactId</code> but with different versions.
+                                            For each bundle that is used with different version, there's a list of all these versions and
+                                            features (and their repositories) which include them.)</div>
+                                        <xsl:apply-templates select="/k:consistency-report/k:report[@flavor='available']/k:duplicates" />
+                                    </div>
+                                    <div style="position: relative; margin-top: -150px">
+                                        <div id="d2" style="position: relative; top: 0; height: 150px"></div>
+                                        <div>
+                                            <h1>All bundles</h1>
+                                            <xsl:call-template name="bundles">
+                                                <xsl:with-param name="f" select="'available'" />
+                                            </xsl:call-template>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div id="r3" class="tab-pane">
+                                <div class="container-fluid">
+                                    <h4>All features, including blacklisted ones.</h4>
+                                    <div>
+                                        <h1>Bundle <em>Duplicates</em></h1>
+                                        <div class="help">(A <em>duplicate bundle</em> is a bundle that is referenced multiple times
+                                            with the same Maven <code>groupId</code> and <code>artifactId</code> but with different versions.
+                                            For each bundle that is used with different version, there's a list of all these versions and
+                                            features (and their repositories) which include them.)</div>
+                                        <xsl:apply-templates select="/k:consistency-report/k:report[@flavor='all']/k:duplicates" />
+                                    </div>
+                                    <div style="position: relative; margin-top: -150px">
+                                        <div id="d3" style="position: relative; top: 0; height: 150px"></div>
+                                        <div>
+                                            <h1>All bundles</h1>
+                                            <xsl:call-template name="bundles">
+                                                <xsl:with-param name="f" select="'all'" />
+                                            </xsl:call-template>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
                 </div>
+
+                <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
+                <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+                <script>
+                    $(function () {
+                        $("#installed").addClass("active");
+                        $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
+                            $(window).scrollTop(0);
+                            $($(e.target).data("t1")).hide();
+                            $($(e.target).data("t2")).show();
+                            $("body").scrollspy("refresh");
+                        })
+                    });
+                </script>
             </body>
         </html>
     </xsl:template>
@@ -91,7 +192,7 @@
     <xsl:template match="k:duplicate">
         <div class="bundle">
             <strong class="text-danger"><xsl:value-of select="@ga" /></strong>
-            <ul class="feature" style="display: none">
+            <ul class="feature" style="display: block">
                 <xsl:for-each select="k:bundle">
                     <li>
                         <strong class="text-primary"><xsl:value-of select="@uri" /></strong>
@@ -107,6 +208,7 @@
     </xsl:template>
 
     <xsl:template name="bundles">
+        <xsl:param name="f" />
         <table class="table table-condensed" style="table-layout: fixed">
             <col width="30%" />
             <col width="70%" />
@@ -117,7 +219,7 @@
                 </tr>
             </thead>
             <tbody>
-                <xsl:for-each select="/k:consistency-report/k:bundles/k:bundle">
+                <xsl:for-each select="/k:consistency-report/k:report[@flavor=$f]/k:bundles/k:bundle">
                     <xsl:element name="tr">
                         <xsl:attribute name="class">
                             <xsl:if test="@duplicate='true'">danger</xsl:if>
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
index 254d993..427daf9 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
@@ -276,6 +276,18 @@ public class AssemblyMojo extends MojoSupport {
     @Parameter
     private String generateConsistencyReport;
 
+    /**
+     * When generating consistency report, we can specify project name. By default it's "Apache Karaf"
+     */
+    @Parameter(defaultValue = "Apache Karaf")
+    private String consistencyReportProjectName;
+
+    /**
+     * When generating consistency report, we can specify project version. By default it's "${project.version}"
+     */
+    @Parameter(defaultValue = "${project.version}")
+    private String consistencyReportProjectVersion;
+
     /*
      * KARs are not configured using Maven plugin configuration, but rather detected from dependencies.
      * All KARs are just unzipped into the assembly being constructed, but additionally KAR's embedded
@@ -475,6 +487,8 @@ public class AssemblyMojo extends MojoSupport {
         builder.pidsToExtract(pidsToExtract);
         builder.writeProfiles(writeProfiles);
         builder.generateConsistencyReport(generateConsistencyReport);
+        builder.setConsistencyReportProjectName(consistencyReportProjectName);
+        builder.setConsistencyReportProjectVersion(consistencyReportProjectVersion);
         builder.environment(environment);
         builder.defaultStartLevel(defaultStartLevel);
         if (featuresProcessing != null) {