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("&", "&amp;").replaceAll(">", "&lt;").replaceAll("<", "&gt;").replaceAll("\"", "&quot;");
+    }
+
+    /**
      * 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>'].