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 2017/12/14 19:31:49 UTC
[karaf] branch master updated: [KARAF-5544] Allow generation of
bundle consistency report in karaf-maven-plugin:assembly
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 9f5db47 [KARAF-5544] Allow generation of bundle consistency report in karaf-maven-plugin:assembly
9f5db47 is described below
commit 9f5db47ab7f1ce60c5696fc7ace0aae6f6f25968
Author: Grzegorz Grzybek <gr...@gmail.com>
AuthorDate: Thu Dec 14 20:31:34 2017 +0100
[KARAF-5544] Allow generation of bundle consistency report in karaf-maven-plugin:assembly
---
.../org/apache/karaf/features/LocationPattern.java | 12 ++
.../features/internal/service/RepositoryImpl.java | 4 +
.../org/apache/karaf/profile/assembly/Builder.java | 140 ++++++++++++++++++
.../karaf/profile/assembly/URIAwareComparator.java | 38 +++++
profile/src/main/resources/bundle-report.xslt | 157 +++++++++++++++++++++
.../apache/karaf/profile/assembly/BuilderTest.java | 21 +++
profile/src/test/resources/repositories/repo1.xml | 35 +++++
.../org/apache/karaf/tooling/AssemblyMojo.java | 9 ++
8 files changed, 416 insertions(+)
diff --git a/features/core/src/main/java/org/apache/karaf/features/LocationPattern.java b/features/core/src/main/java/org/apache/karaf/features/LocationPattern.java
index f155b78..98753da 100644
--- a/features/core/src/main/java/org/apache/karaf/features/LocationPattern.java
+++ b/features/core/src/main/java/org/apache/karaf/features/LocationPattern.java
@@ -116,6 +116,18 @@ public class LocationPattern {
return originalUri;
}
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public String getArtifactId() {
+ return artifactId;
+ }
+
+ public String getVersionString() {
+ return versionString;
+ }
+
/**
* Converts a String with one special character (<code>*</code>) into working {@link Pattern}
* @param value
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
index c734e8a..6f6a04c 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryImpl.java
@@ -82,6 +82,10 @@ public class RepositoryImpl implements Repository {
.toArray(new Feature[features.getFeature().size()]);
}
+ public Features getFeaturesInternal() {
+ return features;
+ }
+
@Override
public boolean isBlacklisted() {
return blacklisted;
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 bc19b61..da6c938 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
@@ -16,10 +16,12 @@
*/
package org.apache.karaf.profile.assembly;
+import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
+import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
@@ -98,6 +100,7 @@ import org.apache.karaf.util.ThreadUtils;
import org.apache.karaf.util.Version;
import org.apache.karaf.util.config.PropertiesLoader;
import org.apache.karaf.util.maven.Parser;
+import org.ops4j.net.URLUtils;
import org.ops4j.pax.url.mvn.MavenResolver;
import org.ops4j.pax.url.mvn.MavenResolvers;
import org.osgi.framework.Constants;
@@ -302,6 +305,7 @@ public class Builder {
Map<String, String> system = new LinkedHashMap<>();
List<String> pidsToExtract = new LinkedList<>();
boolean writeProfiles;
+ String generateConsistencyReport;
private ScheduledExecutorService executor;
private DownloadManager manager;
@@ -564,6 +568,14 @@ public class Builder {
}
/**
+ * Configure builder to generate consistency report
+ * @param generateConsistencyReport
+ */
+ public void generateConsistencyReport(String generateConsistencyReport) {
+ this.generateConsistencyReport = generateConsistencyReport;
+ }
+
+ /**
* Configure Karaf version to target. This impacts the way some configuration files are generated.
* @param karafVersion
* @return
@@ -948,6 +960,18 @@ public class Builder {
}
}
+ if (generateConsistencyReport != null) {
+ File directory = new File(generateConsistencyReport);
+ if (directory.isDirectory()) {
+ LOGGER.info("Writing bundle report");
+ generateConsistencyReport(karRepositories, new File(directory, "bundle-report-full.xml"), true);
+ generateConsistencyReport(karRepositories, new File(directory, "bundle-report.xml"), false);
+ Files.copy(getClass().getResourceAsStream("/bundle-report.xslt"),
+ directory.toPath().resolve("bundle-report.xslt"),
+ StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
//
// Generate profiles. If user has configured additional profiles, they'll be used as parents
// of the generated ones.
@@ -1090,6 +1114,122 @@ public class Builder {
}
/**
+ * Produces human readable XML with <em>feature consistency report</em>.
+ * @param repositories
+ * @param result
+ */
+ public void generateConsistencyReport(Map<String, Features> repositories, File result, boolean full) {
+ 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()) {
+ 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(" </bundle>\n");
+ }
+ writer.write(" </duplicate>\n");
+ } catch (IOException e) {
+ }
+ }
+ });
+ 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("</consistency-report>\n");
+ } catch (IOException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Sanitize before putting to XML
+ * @param uri
+ * @return
+ */
+ public String sanitize(String uri) {
+ return uri.replaceAll("&", "&").replaceAll(">", "<").replaceAll("<", ">").replaceAll("\"", """);
+ }
+
+ /**
* Similar to {@link FeaturesProcessorImpl#hasInstructions()}, we check if there are any builder configuration
* options for blacklisted repos/features/bundles or overwrites.
* @return
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/URIAwareComparator.java b/profile/src/main/java/org/apache/karaf/profile/assembly/URIAwareComparator.java
new file mode 100644
index 0000000..bc75ea8
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/URIAwareComparator.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.karaf.profile.assembly;
+
+import java.util.Comparator;
+
+public class URIAwareComparator implements Comparator<String> {
+
+ @Override
+ public int compare(String o1, String o2) {
+ String u1 = o1;
+ if (u1.startsWith("wrap:mvn:")) {
+ u1 = u1.substring(5);
+ }
+ String u2 = o2;
+ if (u2.startsWith("wrap:mvn:")) {
+ u2 = u2.substring(5);
+ }
+ return u1.compareTo(u2);
+ }
+
+}
diff --git a/profile/src/main/resources/bundle-report.xslt b/profile/src/main/resources/bundle-report.xslt
new file mode 100644
index 0000000..4863b1c
--- /dev/null
+++ b/profile/src/main/resources/bundle-report.xslt
@@ -0,0 +1,157 @@
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:k="urn:apache:karaf:consistency:1.0">
+
+ <xsl:output doctype-public="html" encoding="UTF-8" method="html" />
+
+ <xsl:template match="/">
+ <html>
+ <head>
+ <meta charset="utf-8" />
+ <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 }
+ div.bundle { padding: 5px; margin: 5px 0 }
+ </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>
+ </li>
+ <li>
+ <a href="#bundles">All bundles (<xsl:value-of select="count(/k:consistency-report/k:bundles/k:bundle)" />)</a>
+ </li>
+ <li style="min-height: 50px">
+ <input id="toggle" class="btn btn-default" style="margin-top: 8px" type="button" value="Show details" />
+ </li>
+ </ul>
+ </div>
+ </div>
+ </nav>
+ </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>
+ </body>
+ </html>
+ </xsl:template>
+
+ <xsl:template match="k:duplicate">
+ <div class="bundle">
+ <strong class="text-danger"><xsl:value-of select="@ga" /></strong>
+ <ul class="feature" style="display: none">
+ <xsl:for-each select="k:bundle">
+ <li>
+ <strong class="text-primary"><xsl:value-of select="@uri" /></strong>
+ <ul>
+ <xsl:for-each select="k:feature">
+ <li><xsl:value-of select="text()" /> <span class="text-muted"> (<xsl:value-of select="@repository" />)</span></li>
+ </xsl:for-each>
+ </ul>
+ </li>
+ </xsl:for-each>
+ </ul>
+ </div>
+ </xsl:template>
+
+ <xsl:template name="bundles">
+ <table class="table table-condensed" style="table-layout: fixed">
+ <col width="30%" />
+ <col width="70%" />
+ <thead>
+ <tr>
+ <th>bundle</th>
+ <th>feature(s)</th>
+ </tr>
+ </thead>
+ <tbody>
+ <xsl:for-each select="/k:consistency-report/k:bundles/k:bundle">
+ <xsl:element name="tr">
+ <xsl:attribute name="class">
+ <xsl:if test="@duplicate='true'">danger</xsl:if>
+ </xsl:attribute>
+ <xsl:element name="td">
+ <span class="text-primary"><xsl:value-of select="@uri" /></span>
+ </xsl:element>
+ <xsl:element name="td">
+ <xsl:for-each select="k:feature">
+ <div><xsl:value-of select="text()" /></div>
+ </xsl:for-each>
+ </xsl:element>
+ </xsl:element>
+ </xsl:for-each>
+ </tbody>
+ </table>
+ </xsl:template>
+
+ <xsl:template name="package">
+ <xsl:param name="bundles-title" select="'bundles'" />
+ <div class="bundle">
+ <p class="bg-primary">
+ <xsl:value-of select="@package" />
+ </p>
+ <div class="text-info" style="padding: 2px 10px">
+ <xsl:value-of select="$bundles-title" />
+ </div>
+ <xsl:for-each select="k:version">
+ <div class="version">
+ <div class="version-id">
+ <xsl:value-of select="@version" />
+ </div>
+ <ul>
+ <xsl:for-each select="k:by-bundle">
+ <li><xsl:value-of select="@symbolic-name" />:<xsl:value-of select="@version" />
+ </li>
+ </xsl:for-each>
+ </ul>
+ </div>
+ </xsl:for-each>
+ </div>
+ </xsl:template>
+
+ <xsl:template name="import-export">
+ <tr>
+ <td>
+ <xsl:value-of select="@package" />
+ </td>
+ <td>
+ <xsl:value-of select="@version" />
+ </td>
+ </tr>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/profile/src/test/java/org/apache/karaf/profile/assembly/BuilderTest.java b/profile/src/test/java/org/apache/karaf/profile/assembly/BuilderTest.java
index 3b82371..44f059d 100644
--- a/profile/src/test/java/org/apache/karaf/profile/assembly/BuilderTest.java
+++ b/profile/src/test/java/org/apache/karaf/profile/assembly/BuilderTest.java
@@ -17,14 +17,20 @@
package org.apache.karaf.profile.assembly;
import java.io.BufferedWriter;
+import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
+import org.apache.karaf.features.internal.model.Features;
+import org.apache.karaf.features.internal.service.RepositoryCacheImpl;
+import org.apache.karaf.features.internal.service.RepositoryImpl;
import org.junit.Ignore;
import org.junit.Test;
import org.osgi.framework.Constants;
@@ -103,6 +109,21 @@ public class BuilderTest {
}
}
+ //@Test
+ //@Ignore("no need to run this test")
+ public void consistencyReport() {
+ Map<String, Features> features = new LinkedHashMap<>();
+ Builder builder = new Builder();
+ File[] uris = new File("src/test/resources/repositories").listFiles((dir, name) -> name.endsWith(".xml"));
+ if (uris != null) {
+ for (File f : uris) {
+ features.put(f.getName(), new RepositoryImpl(f.toURI(), false).getFeaturesInternal());
+ }
+ }
+ builder.generateConsistencyReport(features, new File("target/consistency.xml"), false);
+ builder.generateConsistencyReport(features, new File("target/consistency-full.xml"), true);
+ }
+
private static void recursiveDelete(Path path) throws IOException {
if (Files.exists(path)) {
if (Files.isDirectory(path)) {
diff --git a/profile/src/test/resources/repositories/repo1.xml b/profile/src/test/resources/repositories/repo1.xml
new file mode 100644
index 0000000..b830dc9
--- /dev/null
+++ b/profile/src/test/resources/repositories/repo1.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<features name="test" xmlns="http://karaf.apache.org/xmlns/features/v1.1.0">
+ <repository>urn:r1</repository>
+ <feature name="f1" region="foo">
+ <config name="c1">
+ k=v
+ </config>
+ <bundle>b1</bundle>
+ <bundle>b2</bundle>
+ </feature>
+ <feature name="f2">
+ <feature>f1</feature>
+ <bundle>b3</bundle>
+ </feature>
+ <feature name="f3">
+ <configfile finalname="cf1" override="true">cfloc</configfile>
+ <bundle>b4</bundle>
+ </feature>
+</features>
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 2830306..ec8a9e5 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
@@ -268,6 +268,14 @@ public class AssemblyMojo extends MojoSupport {
@Parameter(defaultValue = "false")
private boolean writeProfiles;
+ /**
+ * When assembly custom distribution, we can also generate an XML/XSLT report with the summary of bundles.
+ * This parameter specifies target directory, to which <code>bundle-report.xml</code> and <code>bundle-report-full.xml</code>
+ * (along with XSLT stylesheet) will be written.
+ */
+ @Parameter
+ private String generateConsistencyReport;
+
/*
* 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
@@ -467,6 +475,7 @@ public class AssemblyMojo extends MojoSupport {
builder.translatedUrls(configureTranslatedUrls());
builder.pidsToExtract(pidsToExtract);
builder.writeProfiles(writeProfiles);
+ builder.generateConsistencyReport(generateConsistencyReport);
builder.environment(environment);
builder.defaultStartLevel(defaultStartLevel);
if (featuresProcessing != null) {
--
To stop receiving notification emails like this one, please contact
['"commits@karaf.apache.org" <co...@karaf.apache.org>'].