You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by pa...@apache.org on 2020/10/27 08:20:39 UTC

[sling-org-apache-sling-feature-analyser] branch issues/SLING-9823_SLING-9822_SLING-9805 created (now 47fb1cc)

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

pauls pushed a change to branch issues/SLING-9823_SLING-9822_SLING-9805
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-analyser.git.


      at 47fb1cc  Rework the analyser to report more context and improve the meta data caching extension.

This branch includes the following new commits:

     new 47fb1cc  Rework the analyser to report more context and improve the meta data caching extension.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[sling-org-apache-sling-feature-analyser] 01/01: Rework the analyser to report more context and improve the meta data caching extension.

Posted by pa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

pauls pushed a commit to branch issues/SLING-9823_SLING-9822_SLING-9805
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-analyser.git

commit 47fb1cce82597ce81522b19b00f36cb5d928b388
Author: Karl Pauls <ka...@gmail.com>
AuthorDate: Tue Oct 27 09:20:19 2020 +0100

    Rework the analyser to report more context and improve the meta data caching extension.
    
    * SLING-9823 - Make analyzers report more context about issues and make it possible to filter reports.
    * SLING-9822 - Make artifact retrieval lazy for cached artifacts.
    * SLING-9805 - Add a method to create the metadata cache extension.
---
 .../apache/sling/feature/analyser/Analyser.java    |  79 ++++++++++--
 .../sling/feature/analyser/AnalyserResult.java     |  96 +++++++++++++-
 .../extensions/AnalyserMetaDataExtension.java      | 142 +++++++++++++++++++++
 .../feature/analyser/task/AnalyserTaskContext.java |  39 +++++-
 .../task/impl/CheckApisJarsProperties.java         |  21 ++-
 .../task/impl/CheckBundleExportsImports.java       |   8 +-
 .../analyser/task/impl/CheckBundleNativeCode.java  |   2 +-
 .../analyser/task/impl/CheckBundlesForConnect.java |  11 +-
 .../task/impl/CheckBundlesForInitialContent.java   |   2 +-
 .../task/impl/CheckBundlesForResources.java        |   2 +-
 .../impl/CheckContentPackageForInstallables.java   |   8 +-
 .../impl/CheckContentPackagesDependencies.java     |  24 ++--
 .../feature/analyser/task/impl/CheckRepoinit.java  |   4 +-
 .../task/impl/CheckRequirementsCapabilities.java   |   8 +-
 .../analyser/task/impl/CheckUnusedBundles.java     |   4 +-
 .../sling/feature/analyser/task/package-info.java  |   2 +-
 .../sling/feature/scanner/ArtifactDescriptor.java  |   2 +-
 .../org/apache/sling/feature/scanner/Scanner.java  |  47 +++----
 .../feature/scanner/impl/BundleDescriptorImpl.java |  31 ++++-
 .../scanner/impl/ContentPackageScanner.java        |  21 +--
 .../impl/ContentPackagesExtensionScanner.java      |  28 +++-
 .../extensions/AnalyserMetaDataExtensionTest.java  |  61 +++++++++
 ...iderTest.java => AnalyserTaskProviderTest.java} |   2 +-
 .../task/impl/CheckApisJarsPropertiesTest.java     |  24 ++++
 .../task/impl/CheckBundleExportsImportsTest.java   |   8 +-
 .../task/impl/CheckBundleNativeCodeTest.java       |   2 +-
 .../analyser/task/impl/CheckRepoinitTest.java      |  20 +++
 .../impl/CheckRequirementsCapabilitiesTest.java    |   2 +-
 28 files changed, 584 insertions(+), 116 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/analyser/Analyser.java b/src/main/java/org/apache/sling/feature/analyser/Analyser.java
index 82bcd1b..5c06fc2 100644
--- a/src/main/java/org/apache/sling/feature/analyser/Analyser.java
+++ b/src/main/java/org/apache/sling/feature/analyser/Analyser.java
@@ -32,6 +32,7 @@ import java.util.Set;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.ExecutionEnvironmentExtension;
 import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.analyser.extensions.AnalyserMetaDataExtension;
 import org.apache.sling.feature.analyser.task.AnalyserTask;
 import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
 import org.apache.sling.feature.builder.FeatureProvider;
@@ -198,8 +199,15 @@ public class Analyser {
         }
         final BundleDescriptor fwkDesc = bd;
 
-        final List<String> warnings = new ArrayList<>();
-        final List<String> errors = new ArrayList<>();
+        final List<AnalyserResult.GlobalReport> globalWarnings = new ArrayList<>();
+        final List<AnalyserResult.ArtifactReport> artifactWarnings = new ArrayList<>();
+        final List<AnalyserResult.ExtensionReport> extensionWarnings = new ArrayList<>();
+
+        final List<AnalyserResult.GlobalReport> globalErrors = new ArrayList<>();
+        final List<AnalyserResult.ArtifactReport> artifactErrors = new ArrayList<>();
+        final List<AnalyserResult.ExtensionReport> extensionErrors = new ArrayList<>();
+
+        AnalyserMetaDataExtension analyserMetaDataExtension = AnalyserMetaDataExtension.getAnalyserMetaDataExtension(feature);
 
         // execute analyser tasks
         for (final AnalyserTask task : tasks) {
@@ -236,29 +244,80 @@ public class Analyser {
 
                 @Override
                 public void reportWarning(final String message) {
-                    warnings.add(message);
+                    if (analyserMetaDataExtension != null && analyserMetaDataExtension.reportWarning(feature.getId())) {
+                        globalWarnings.add(new AnalyserResult.GlobalReport(message));
+                    }
+                }
+
+                @Override
+                public void reportArtifactWarning(ArtifactId artifactId, String message) {
+                    if (analyserMetaDataExtension != null && analyserMetaDataExtension.reportWarning(artifactId) && analyserMetaDataExtension.reportWarning(feature.getId())) {
+                        artifactWarnings.add(new AnalyserResult.ArtifactReport(artifactId, message));
+                    }
+                }
+
+                @Override
+                public void reportArtifactError(ArtifactId artifactId, String message) {
+                    if (analyserMetaDataExtension != null && analyserMetaDataExtension.reportError(artifactId)&& analyserMetaDataExtension.reportError(feature.getId())) {
+                        artifactErrors.add(new AnalyserResult.ArtifactReport(artifactId, message));
+                    }
+                }
+
+                @Override
+                public void reportExtensionWarning(String extension, String message) {
+                    if (analyserMetaDataExtension != null && analyserMetaDataExtension.reportWarning(feature.getId())) {
+                        extensionWarnings.add(new AnalyserResult.ExtensionReport(extension, message));
+                    }
+                }
+
+                @Override
+                public void reportExtensionError(String extension, String message) {
+                    if (analyserMetaDataExtension != null && analyserMetaDataExtension.reportError(feature.getId())) {
+                        extensionErrors.add(new AnalyserResult.ExtensionReport(extension, message));
+                    }
                 }
 
                 @Override
                 public void reportError(final String message) {
-                    errors.add(message);
+                    if (analyserMetaDataExtension != null && analyserMetaDataExtension.reportError(feature.getId())) {
+                        globalErrors.add(new AnalyserResult.GlobalReport(message));
+                    }
                 }
             });
         }
 
-        logger.info("Analyzing feature '" + feature.getId() + "' finished : " + warnings.size() + " warnings, "
-                + errors.size() + " errors.");
+        logger.info("Analyzing feature '" + feature.getId() + "' finished : " + globalWarnings.size() + artifactWarnings.size() + extensionWarnings.size()  + " warnings, "
+                + globalErrors.size() + artifactErrors.size() + extensionErrors.size() + " errors.");
 
         return new AnalyserResult() {
+            @Override
+            public List<GlobalReport> getGlobalWarnings() {
+                return globalWarnings;
+            }
+
+            @Override
+            public List<ArtifactReport> getArtifactWarnings() {
+                return artifactWarnings;
+            }
+
+            @Override
+            public List<ExtensionReport> getExtensionWarnings() {
+                return extensionWarnings;
+            }
+
+            @Override
+            public List<GlobalReport> getGlobalErrors() {
+                return globalErrors;
+            }
 
             @Override
-            public List<String> getWarnings() {
-                return warnings;
+            public List<ArtifactReport> getArtifactErrors() {
+                return artifactErrors;
             }
 
             @Override
-            public List<String> getErrors() {
-                return errors;
+            public List<ExtensionReport> getExtensionErrors() {
+                return extensionErrors;
             }
 
             @Override
diff --git a/src/main/java/org/apache/sling/feature/analyser/AnalyserResult.java b/src/main/java/org/apache/sling/feature/analyser/AnalyserResult.java
index 717eedf..5ab04c4 100644
--- a/src/main/java/org/apache/sling/feature/analyser/AnalyserResult.java
+++ b/src/main/java/org/apache/sling/feature/analyser/AnalyserResult.java
@@ -16,29 +16,117 @@
  */
 package org.apache.sling.feature.analyser;
 
-import java.util.List;
-
+import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.FeatureDescriptor;
 import org.osgi.annotation.versioning.ProviderType;
 
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
 /**
  * The result returned by the analyser
  */
 @ProviderType
 public interface AnalyserResult {
 
+    class Report<T> {
+        private final T key;
+        private final String value;
+        Report(T key, String value) {
+            this.key = key;
+            this.value = value;
+        }
+        T getKey() {
+            return key;
+        }
+        String getValue() {
+            return value;
+        }
+    }
+
+    class ArtifactReport extends Report<ArtifactId> {
+        ArtifactReport(ArtifactId key, String value) {
+            super(key, value);
+        }
+    }
+
+    class ExtensionReport extends Report<String> {
+        ExtensionReport(String key, String value) {
+            super(key, value);
+        }
+    }
+
+    class GlobalReport extends Report<Void> {
+
+        GlobalReport(String value) {
+            super(null, value);
+        }
+    }
+
     /**
      * List of warnings. Warnings can be used to improve the feature.
      * @return A list of warnings might be empty.
+     * @deprecated - use {@link #getGlobalWarnings()} ()}, {@link #getArtifactWarnings()} ()}, and {@link #getExtensionWarnings()} ()} instead.
+     */
+    default List<String> getWarnings() {
+        return Stream.of(getGlobalWarnings().stream().map(Report::getValue),
+                getArtifactWarnings().stream().map(report -> report.getKey() + ": " + report.getValue()),
+                getExtensionWarnings().stream().map(report -> report.getKey() + ": " + report.getValue()))
+                .flatMap(Function.identity())
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * List of global warnings. Warnings can be used to improve the feature.
+     * @return A list of warnings might be empty.
+     */
+    List<GlobalReport> getGlobalWarnings();
+
+    /**
+     * List of warnings for artifact ids. Warnings can be used to improve the feature.
+     * @return A list of warnings might be empty.
      */
-    List<String> getWarnings();
+    List<ArtifactReport> getArtifactWarnings();
+
+    /**
+     * List of warnings for extension names. Warnings can be used to improve the feature.
+     * @return A list of warnings might be empty.
+     */
+    List<ExtensionReport> getExtensionWarnings();
 
     /**
      * List of errors. Errors should be fixed in the feature
      * @return A list of errors might be empty
+     * @deprecated - use {@link #getGlobalErrors()}, {@link #getArtifactErrors()}, and {@link #getExtensionErrors()} instead.
+     */
+    default List<String> getErrors() {
+        return Stream.of(getGlobalErrors().stream().map(Report::getValue),
+                getArtifactErrors().stream().map(report -> report.getKey() + ": " + report.getValue()),
+                getExtensionErrors().stream().map(report -> report.getKey() + ": " + report.getValue()))
+                .flatMap(Function.identity())
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * List of global errors. Errors should be fixed in the feature
+     * @return A list of errors might be empty
+     */
+    List<GlobalReport> getGlobalErrors();
+
+    /**
+     * List of errors for artifact ids. Errors should be fixed in the feature
+     * @return A list of errors might be empty
+     */
+    List<ArtifactReport> getArtifactErrors();
+
+    /**
+     * List of errors for extension names. Errors should be fixed in the feature
+     * @return A list of errors might be empty
      */
-    List<String> getErrors();
+    List<ExtensionReport> getExtensionErrors();
 
     /**
      * Return the feature descriptor created during scanning
diff --git a/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtension.java b/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtension.java
new file mode 100644
index 0000000..9aca541
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtension.java
@@ -0,0 +1,142 @@
+/*
+ * 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.sling.feature.analyser.extensions;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.scanner.BundleDescriptor;
+
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+
+public class AnalyserMetaDataExtension {
+    public static final String EXTENSION_NAME = "analyser-metadata";
+
+    private final Map<ArtifactId, Map<String, String>> manifests = new HashMap<>();
+    private final Map<ArtifactId, Boolean> reportWarnings = new HashMap<>();
+    private final Map<ArtifactId, Boolean> reportErrors = new HashMap<>();
+
+    public static AnalyserMetaDataExtension getAnalyserMetaDataExtension(Feature feature) {
+        Extension ext = feature == null ? null : feature.getExtensions().getByName(EXTENSION_NAME);
+        return getAnalyserMetaDataExtension(ext);
+    }
+
+    public static AnalyserMetaDataExtension getAnalyserMetaDataExtension(Extension ext) {
+        if (ext == null) {
+            return null;
+        } else if (ext.getType() != ExtensionType.JSON) {
+            throw new IllegalArgumentException("Extension " + ext.getName() + " must have JSON type");
+        } else {
+            return new AnalyserMetaDataExtension(ext.getJSONStructure().asJsonObject());
+        }
+    }
+
+    private AnalyserMetaDataExtension(JsonObject json) {
+        for (Map.Entry<String, JsonValue> entry : json.entrySet()) {
+            ArtifactId id = ArtifactId.fromMvnId(entry.getKey());
+            JsonObject headers = entry.getValue().asJsonObject();
+            if (headers.containsKey("manifest")) {
+                Map<String, String> manifest = new LinkedHashMap<>();
+                JsonObject manifestHeaders = headers.getJsonObject("manifest");
+                for (String name : manifestHeaders.keySet()) {
+                    manifest.put(name, manifestHeaders.getString(name));
+                }
+                this.manifests.put(id, manifest);
+            }
+            if (headers.containsKey("report")) {
+                JsonObject report = headers.getJsonObject("report");
+                if (report.containsKey("warning")) {
+                    reportWarnings.put(id, report.getBoolean("warning"));
+                }
+                if (report.containsKey("error")) {
+                    reportErrors.put(id, report.getBoolean("error"));
+                }
+            }
+        }
+    }
+
+    public static boolean isAnalyserMetaDataExtension(Extension ext) {
+        return ext != null && ext.getName().equals(EXTENSION_NAME) && ext.getType() == ExtensionType.JSON;
+    }
+
+    public Map<String, String> getManifest(ArtifactId artifactId) {
+        return this.manifests.get(artifactId);
+    }
+
+    public boolean reportWarning(ArtifactId artifactId) {
+        return !this.reportWarnings.containsKey(artifactId) || this.reportWarnings.get(artifactId);
+    }
+
+    public boolean reportError(ArtifactId artifactId) {
+        return !this.reportErrors.containsKey(artifactId) || this.reportErrors.get(artifactId);
+    }
+
+    public Extension toExtension(Extension extension) {
+        if (isAnalyserMetaDataExtension(extension)) {
+            JsonObjectBuilder builder = Json.createObjectBuilder(extension.getJSONStructure().asJsonObject());
+            Stream.concat(Stream.concat(manifests.keySet().stream(), reportErrors.keySet().stream()), reportWarnings.keySet().stream()).distinct().forEachOrdered(
+                    id -> {
+                        JsonObjectBuilder metadata = Json.createObjectBuilder();
+                        if (manifests.containsKey(id)) {
+                            JsonObjectBuilder manifest = Json.createObjectBuilder();
+                            manifests.get(id).forEach(manifest::add);
+                            metadata.add("manifest", manifest);
+                        }
+                        if (reportErrors.containsKey(id) || reportWarnings.containsKey(id)) {
+                            JsonObjectBuilder report = Json.createObjectBuilder();
+                            if (reportErrors.containsKey(id)) {
+                                report.add("error", reportErrors.get(id));
+                            }
+                            if (reportWarnings.containsKey(id)) {
+                                report.add("warning", reportWarnings.get(id));
+                            }
+                            metadata.add("report", report);
+                        }
+                        builder.add(id.toMvnId(), metadata);
+                    }
+            );
+            extension.setJSONStructure(builder.build());
+        }
+        return extension;
+    }
+
+    public void add(BundleDescriptor... bundleDescriptors) {
+        for (BundleDescriptor descriptor : bundleDescriptors) {
+            Map<String, String> manifest = new LinkedHashMap<>();
+            descriptor.getManifest().getMainAttributes().entrySet().stream()
+                    .forEachOrdered(entry -> manifest.put(entry.getKey().toString(), (String) entry.getValue()));
+
+            manifests.put(descriptor.getArtifact().getId(), manifest);
+        }
+    }
+
+    public void setReportWarnings(ArtifactId id, boolean enabled) {
+        reportWarnings.put(id, enabled);
+    }
+
+    public void setReportErrors(ArtifactId id, boolean enabled) {
+        reportErrors.put(id, enabled);
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTaskContext.java b/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTaskContext.java
index d2043d8..f5cee5c 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTaskContext.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTaskContext.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sling.feature.analyser.task;
 
+import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.builder.FeatureProvider;
 import org.apache.sling.feature.scanner.BundleDescriptor;
@@ -61,16 +62,50 @@ public interface AnalyserTaskContext {
 
     /**
      * This method is invoked by a {@link AnalyserTask} to report
-     * a warning.
+     * a global warning.
      * @param message The message.
      */
     void reportWarning(String message);
 
     /**
      * This method is invoked by a {@link AnalyserTask} to report
-     * an error.
+     * an artifact warning.
+     * @param artifactId the artifactid
+     * @param message The message.
+     */
+    void reportArtifactWarning(ArtifactId artifactId, String message);
+
+    /**
+     * This method is invoked by a {@link AnalyserTask} to report
+     * an artifact error.
+     * @param artifactId the artifactid
+     * @param message The message.
+     */
+    void reportArtifactError(ArtifactId artifactId, String message);
+
+    /**
+     * This method is invoked by a {@link AnalyserTask} to report
+     * an extension warning.
+     * @param extension the extension.
+     * @param message The message.
+     */
+    void reportExtensionWarning(String extension, String message);
+
+    /**
+     * This method is invoked by a {@link AnalyserTask} to report
+     * an extension error.
+     * @param extension the extension.
+     * @param message The message.
+     */
+    void reportExtensionError(String extension, String message);
+
+    /**
+     * This method is invoked by a {@link AnalyserTask} to report
+     * a global error.
      * @param message The message.
      */
     void reportError(String message);
+
+
 }
 
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApisJarsProperties.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApisJarsProperties.java
index e1a138e..9a01c03 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApisJarsProperties.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApisJarsProperties.java
@@ -120,7 +120,7 @@ public class CheckApisJarsProperties implements AnalyserTask {
         final Extension ext = ctx.getFeature().getExtensions().getByName(EXTENSION_NAME);
         if ( ext != null ) {
             if ( ext.getType() != ExtensionType.JSON ) {
-                ctx.reportError("Extension ".concat(EXTENSION_NAME).concat(" is not of type JSON"));
+                ctx.reportExtensionError(EXTENSION_NAME,"is not of type JSON");
             } else {
                 final JsonObject obj = ext.getJSONStructure().asJsonObject();
                 checkStringType(ctx, obj, PROP_API_VERSION);
@@ -146,7 +146,7 @@ public class CheckApisJarsProperties implements AnalyserTask {
         if ( obj.containsKey(propName) ) {
             final JsonValue val = obj.get(propName);
             if ( val.getValueType() != ValueType.STRING ) {
-                ctx.reportError("Extension ".concat(EXTENSION_NAME).concat(" : property ").concat(propName).concat(" is not of type String"));
+                ctx.reportExtensionError(EXTENSION_NAME,"property ".concat(propName).concat(" is not of type String"));
             }
         }
     }
@@ -155,7 +155,7 @@ public class CheckApisJarsProperties implements AnalyserTask {
         if ( obj.containsKey(propName) ) {
             final JsonValue val = obj.get(propName);
             if ( val.getValueType() != ValueType.ARRAY ) {
-                ctx.reportError("Extension ".concat(EXTENSION_NAME).concat(" : property ").concat(propName).concat(" is not of type Array"));
+                ctx.reportExtensionError(EXTENSION_NAME,"property ".concat(propName).concat(" is not of type Array"));
             } else {
                 boolean hasNonStringValue = false;
                 for(final JsonValue v : val.asJsonArray()) {
@@ -164,7 +164,7 @@ public class CheckApisJarsProperties implements AnalyserTask {
                     }
                 }
                 if ( hasNonStringValue ) {
-                    ctx.reportError("Extension ".concat(EXTENSION_NAME).concat(" : array ").concat(propName).concat(" contains non string values"));
+                    ctx.reportExtensionError(EXTENSION_NAME,"array ".concat(propName).concat(" contains non string values"));
                 }
             }
         }
@@ -183,7 +183,7 @@ public class CheckApisJarsProperties implements AnalyserTask {
         if ( obj.containsKey(propName) ) {
             final JsonValue val = obj.get(propName);
             if ( val.getValueType() != ValueType.OBJECT ) {
-                ctx.reportError("Extension ".concat(EXTENSION_NAME).concat(" : property ").concat(propName).concat(" is not of type Object"));
+                ctx.reportExtensionError(EXTENSION_NAME,"property ".concat(propName).concat(" is not of type Object"));
             } else {
                 boolean hasNonStringValue = false;
                 for(final JsonValue v : val.asJsonObject().values()) {
@@ -192,7 +192,7 @@ public class CheckApisJarsProperties implements AnalyserTask {
                    }
                 }
                 if ( hasNonStringValue ) {
-                    ctx.reportError("Extension ".concat(EXTENSION_NAME).concat(" : object ").concat(propName).concat(" contains non string values"));
+                    ctx.reportExtensionError(EXTENSION_NAME,"object ".concat(propName).concat(" contains non string values"));
                 }
             }
         }
@@ -209,7 +209,7 @@ public class CheckApisJarsProperties implements AnalyserTask {
                         // at the moment we can not validate the availability of the artifact since there is no access to Maven APIs
                         ArtifactId.parse(el);
                     } catch ( IllegalArgumentException e) {
-                        ctx.reportError("Bundle " + a.getId().toMvnId() + " has invalid " + propName + " entry '" + el + "' : " + e.getMessage());
+                        ctx.reportArtifactError(a.getId()," has invalid " + propName + " entry '" + el + "' : " + e.getMessage());
                     }
                 });
         }
@@ -225,7 +225,7 @@ public class CheckApisJarsProperties implements AnalyserTask {
                 try {
                     new URL(v);
                 } catch ( final MalformedURLException mue) {
-                    ctx.reportError("Bundle " + a.getId().toMvnId() + " has invalid javadoc links URL : " + v);
+                    ctx.reportArtifactError(a.getId(),"has invalid javadoc links URL : " + v);
                 }
             }
         }
@@ -246,9 +246,8 @@ public class CheckApisJarsProperties implements AnalyserTask {
             count++;
         }
         if ( count > 1 ) {
-            ctx.reportError("Bundle ".concat(artifact.getId().toMvnId())
-                    .concat(" should either define ")
-                    .concat(SCM_LOCATION).concat(", ")
+            ctx.reportArtifactError(artifact.getId(),
+                    "should either define ".concat(SCM_LOCATION).concat(", ")
                     .concat(SCM_CLASSIFIER).concat(", or")
                     .concat(SCM_IDS).concat(" - but only one of them."));
         }
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImports.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImports.java
index 37ecafa..c93461c 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImports.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImports.java
@@ -165,20 +165,20 @@ public class CheckBundleExportsImports implements AnalyserTask {
             final String key = "Bundle " + entry.getKey().getArtifact().getId().getArtifactId() + ":" + entry.getKey().getArtifact().getId().getVersion();
 
             if ( !entry.getValue().importWithoutVersion.isEmpty() ) {
-                ctx.reportWarning(key + " is importing package(s) " + getPackageInfo(entry.getValue().importWithoutVersion, false) + " without specifying a version range.");
+                ctx.reportArtifactWarning(entry.getKey().getArtifact().getId(), " is importing package(s) " + getPackageInfo(entry.getValue().importWithoutVersion, false) + " without specifying a version range.");
             }
             if ( !entry.getValue().exportWithoutVersion.isEmpty() ) {
-                ctx.reportWarning(key + " is exporting package(s) " + getPackageInfo(entry.getValue().importWithoutVersion, false) + " without a version.");
+                ctx.reportArtifactWarning(entry.getKey().getArtifact().getId(), " is exporting package(s) " + getPackageInfo(entry.getValue().importWithoutVersion, false) + " without a version.");
             }
 
             if ( !entry.getValue().missingExports.isEmpty() ) {
-                ctx.reportError(key + " is importing package(s) " + getPackageInfo(entry.getValue().missingExports, false) + " in start level " +
+                ctx.reportArtifactError(entry.getKey().getArtifact().getId(), " is importing package(s) " + getPackageInfo(entry.getValue().missingExports, false) + " in start level " +
                         String.valueOf(entry.getKey().getArtifact().getStartOrder())
                         + " but no bundle is exporting these for that start level.");
                 errorReported = true;
             }
             if ( !entry.getValue().missingExportsWithVersion.isEmpty() ) {
-                ctx.reportError(key + " is importing package(s) "
+                ctx.reportArtifactError(entry.getKey().getArtifact().getId(), " is importing package(s) "
                         + getPackageInfo(entry.getValue().missingExportsWithVersion, true) + " in start level "
                         + String.valueOf(entry.getKey().getArtifact().getStartOrder())
                         + " but no bundle is exporting these for that start level in the required version range.");
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleNativeCode.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleNativeCode.java
index 5e8fb28..7bd1c47 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleNativeCode.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundleNativeCode.java
@@ -44,7 +44,7 @@ public class CheckBundleNativeCode implements AnalyserTask {
 
             final String nativeCode = mf.getMainAttributes().getValue(Constants.BUNDLE_NATIVECODE);
             if ( nativeCode != null ) {
-                ctx.reportError("Found native code instruction in bundle ".concat(descriptor.getArtifact().getId().toMvnId()).concat(" : ").concat(nativeCode) );
+                ctx.reportArtifactError(descriptor.getArtifact().getId(), "Found native code instruction in bundle: ".concat(nativeCode) );
             }
         }
     }
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForConnect.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForConnect.java
index ac82c48..799c243 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForConnect.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForConnect.java
@@ -55,7 +55,7 @@ public class CheckBundlesForConnect implements AnalyserTask {
                 String[] jars = null;
                 if (cp != null) {
                     jars = cp.split(",");
-                    ctx.reportWarning("Found bundle classpath in " + bd.getArtifact() + " : " + cp);
+                    ctx.reportArtifactWarning(bd.getArtifact().getId(), "Found bundle classpath in : " + cp);
                 }
 
                 final Set<String> packages = new HashSet<>();
@@ -65,7 +65,7 @@ public class CheckBundlesForConnect implements AnalyserTask {
                         if (entry.getName().endsWith(".class")) {
                             final int lastPos = entry.getName().lastIndexOf('/');
                             if (lastPos == -1) {
-                                ctx.reportError("Bundle contains classes in the default package: " + bd.getArtifact());
+                                ctx.reportArtifactError(bd.getArtifact().getId(),"Bundle contains classes in the default package");
                             } else {
                                 packages.add(entry.getName().substring(0, lastPos));
                             }
@@ -78,9 +78,8 @@ public class CheckBundlesForConnect implements AnalyserTask {
                                         if (inner.getName().endsWith(".class")) {
                                             final int lastPos = inner.getName().lastIndexOf('/');
                                             if (lastPos == -1) {
-                                                ctx.reportError(
-                                                        "Bundle contains (embedded) classes in the default package: "
-                                                                + bd.getArtifact());
+                                                ctx.reportArtifactError(bd.getArtifact().getId(),
+                                                        "Bundle contains (embedded) classes in the default package");
                                             } else {
                                                 packages.add(inner.getName().substring(0, lastPos));
                                             }
@@ -93,7 +92,7 @@ public class CheckBundlesForConnect implements AnalyserTask {
                         jis.closeEntry();
                     }
                 } catch (final IOException ioe) {
-                    ctx.reportError("Unable to scan bundle " + bd.getArtifact() + " : " + ioe.getMessage());
+                    ctx.reportArtifactError(bd.getArtifact().getId(),"Unable to scan bundle: " + ioe.getMessage());
                 }
                 for (final String p : packages) {
                     List<Artifact> list = packageMap.get(p);
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForInitialContent.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForInitialContent.java
index 5ea7e45..7ddb3d8 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForInitialContent.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForInitialContent.java
@@ -56,7 +56,7 @@ public class CheckBundlesForInitialContent implements AnalyserTask {
             final List<String> initialContent = extractInitialContent(info.getManifest());
 
             if ( !initialContent.isEmpty() ) {
-                ctx.reportWarning("Found initial content in " + info.getArtifact() + " : " + initialContent);
+                ctx.reportArtifactWarning(info.getArtifact().getId(), "Found initial content : " + initialContent);
             }
         }
     }
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForResources.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForResources.java
index a7116d4..044fe53 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForResources.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckBundlesForResources.java
@@ -55,7 +55,7 @@ public class CheckBundlesForResources implements AnalyserTask {
         for(final BundleDescriptor info : ctx.getFeatureDescriptor().getBundleDescriptors()) {
             final List<String> bundleResources = extractBundleResources(info.getManifest());
             if ( !bundleResources.isEmpty() ) {
-                ctx.reportWarning("Found bundle resources in " + info.getArtifact() + " : " + bundleResources);
+                ctx.reportArtifactWarning(info.getArtifact().getId(), "Found bundle resources : " + bundleResources);
             }
         }
     }
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackageForInstallables.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackageForInstallables.java
index 12d041a..167b584 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackageForInstallables.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackageForInstallables.java
@@ -53,12 +53,16 @@ public class CheckContentPackageForInstallables implements AnalyserTask {
         }
 
         for (final ContentPackageDescriptor cp : contentPackages) {
+            if (cp.getArtifactFile() ==  null) {
+                ctx.reportArtifactError(cp.getArtifact().getId(), "Content package " + cp.getName() + " is not resolved and can not be checked.");
+                continue;
+            }
             if (!cp.hasEmbeddedArtifacts() || cp.isEmbeddedInContentPackage()) {
                 continue;
             }
 
-            ctx.reportError("Content package " + cp.getName() + " (" + cp.getArtifact().getId().toMvnId()
-                    + " ) contains " + String.valueOf(cp.bundles.size()) + " bundles and "
+            ctx.reportArtifactError(cp.getArtifact().getId(), "Content package " + cp.getName() +
+                    " contains " + String.valueOf(cp.bundles.size()) + " bundles and "
                     + String.valueOf(cp.configs.size()) + " configurations.");
 
         }
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesDependencies.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesDependencies.java
index 4091da5..c51de09 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesDependencies.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckContentPackagesDependencies.java
@@ -74,20 +74,24 @@ public class CheckContentPackagesDependencies implements AnalyserTask {
 
     private void onDescriptor(AnalyserTaskContext ctx, ArtifactDescriptor descriptor, Map<PackageId, Dependency[]> dependenciesMap) throws Exception {
         URL resourceUrl = descriptor.getArtifactFile();
-        File artifactFile = IOUtils.getFileFromURL(resourceUrl, true, null);
-        if (!artifactFile.exists() || !artifactFile.isFile()) {
-            ctx.reportError("Artifact file " + artifactFile + " does not exist or it is not a file");
-            return;
-        }
+        if (resourceUrl != null) {
+            File artifactFile = IOUtils.getFileFromURL(resourceUrl, true, null);
+            if (!artifactFile.exists() || !artifactFile.isFile()) {
+                ctx.reportArtifactError(descriptor.getArtifact().getId(), "Artifact file " + artifactFile + " does not exist or it is not a file");
+                return;
+            }
 
-        try (VaultPackage vaultPackage = packageManager.open(artifactFile, true)) {
-            PackageId packageId = vaultPackage.getId();
+            try (VaultPackage vaultPackage = packageManager.open(artifactFile, true)) {
+                PackageId packageId = vaultPackage.getId();
 
-            logger.debug("Collecting " + packageId + " dependencies...");
+                logger.debug("Collecting " + packageId + " dependencies...");
 
-            dependenciesMap.put(packageId, vaultPackage.getDependencies());
+                dependenciesMap.put(packageId, vaultPackage.getDependencies());
 
-            logger.debug(packageId + " dependencies collected.");
+                logger.debug(packageId + " dependencies collected.");
+            }
+        } else {
+            ctx.reportArtifactError(descriptor.getArtifact().getId(), "Ignoring " + descriptor.getName() + " as file could not be found");
         }
     }
 
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRepoinit.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRepoinit.java
index 2df81a3..ce649ef 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRepoinit.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRepoinit.java
@@ -51,7 +51,7 @@ public class CheckRepoinit implements AnalyserTask {
         final Extension ext = ctx.getFeature().getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
         if ( ext != null ) {
             if ( ext.getType() != ExtensionType.TEXT ) {
-                ctx.reportError("Repoinit extension must be of type TEXT");
+                ctx.reportExtensionError(Extension.EXTENSION_NAME_REPOINIT, "Repoinit extension must be of type TEXT");
             } else  {
                 check(ctx, "extension", ext.getText());
             }
@@ -89,7 +89,7 @@ public class CheckRepoinit implements AnalyserTask {
         try {
             parser.parse(new StringReader(contents));
         } catch ( RepoInitParsingException e) {
-            ctx.reportError("Parsing error in repoinit from ".concat(id).concat(" : ").concat(e.getMessage()));
+            ctx.reportExtensionError(Extension.EXTENSION_NAME_REPOINIT, "Parsing error in repoinit from ".concat(id).concat(" : ").concat(e.getMessage()));
         }
     }
 }
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilities.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilities.java
index a6821b1..15bc39d 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilities.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilities.java
@@ -27,6 +27,7 @@ import org.apache.felix.utils.resource.CapabilitySet;
 import org.apache.felix.utils.resource.RequirementImpl;
 import org.apache.sling.feature.analyser.task.AnalyserTask;
 import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.apache.sling.feature.scanner.ArtifactDescriptor;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.Descriptor;
 import org.osgi.framework.wiring.BundleRevision;
@@ -105,7 +106,12 @@ public class CheckRequirementsCapabilities implements AnalyserTask {
                             {
                                 if (!RequirementImpl.isOptional(requirement))
                                 {
-                                    ctx.reportError(String.format(format, info.getName(), requirement.toString(), entry.getKey(), "no artifact is providing a matching capability in this start level."));
+                                    String message = String.format(format, info.getName(), requirement.toString(), entry.getKey(), "no artifact is providing a matching capability in this start level.");
+                                    if (info instanceof ArtifactDescriptor) {
+                                        ctx.reportArtifactError(((ArtifactDescriptor) info).getArtifact().getId(), message);
+                                    } else {
+                                        ctx.reportError(message);
+                                    }
                                     errorReported = true;
                                 }
                                 else
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckUnusedBundles.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckUnusedBundles.java
index 8b4c767..e2289dc 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckUnusedBundles.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckUnusedBundles.java
@@ -89,9 +89,9 @@ public class CheckUnusedBundles implements AnalyserTask {
 
                 if ( exports.size() == info.getExportedPackages().size() ) {
                     if ( !optionalImports.isEmpty() ) {
-                        ctx.reportWarning("Exports from bundle ".concat(info.getArtifact().getId().toMvnId()).concat(" are only imported optionally by other bundles."));
+                        ctx.reportArtifactWarning(info.getArtifact().getId(), "Exports from bundle are only imported optionally by other bundles.");
                     } else {
-                        ctx.reportWarning("Exports from bundle ".concat(info.getArtifact().getId().toMvnId()).concat(" are not imported by any other bundle."));
+                        ctx.reportArtifactWarning(info.getArtifact().getId(), "Exports from bundle are not imported by any other bundle.");
                     }
                 }
 
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/package-info.java b/src/main/java/org/apache/sling/feature/analyser/task/package-info.java
index 614898f..85a724d 100644
--- a/src/main/java/org/apache/sling/feature/analyser/task/package-info.java
+++ b/src/main/java/org/apache/sling/feature/analyser/task/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@org.osgi.annotation.versioning.Version("1.1.0")
+@org.osgi.annotation.versioning.Version("1.2.0")
 package org.apache.sling.feature.analyser.task;
 
 
diff --git a/src/main/java/org/apache/sling/feature/scanner/ArtifactDescriptor.java b/src/main/java/org/apache/sling/feature/scanner/ArtifactDescriptor.java
index c717356..43b6df8 100644
--- a/src/main/java/org/apache/sling/feature/scanner/ArtifactDescriptor.java
+++ b/src/main/java/org/apache/sling/feature/scanner/ArtifactDescriptor.java
@@ -36,7 +36,7 @@ public abstract class ArtifactDescriptor extends Descriptor {
 
     /**
      * Get the artifact file
-     * @return The artifact file
+     * @return The artifact file or <code>null</code> if not present.
      */
     public abstract URL getArtifactFile();
 
diff --git a/src/main/java/org/apache/sling/feature/scanner/Scanner.java b/src/main/java/org/apache/sling/feature/scanner/Scanner.java
index 81d5350..877ce92 100644
--- a/src/main/java/org/apache/sling/feature/scanner/Scanner.java
+++ b/src/main/java/org/apache/sling/feature/scanner/Scanner.java
@@ -29,15 +29,13 @@ import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Bundles;
 import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.analyser.extensions.AnalyserMetaDataExtension;
 import org.apache.sling.feature.builder.ArtifactProvider;
 import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl;
 import org.apache.sling.feature.scanner.impl.FeatureDescriptorImpl;
 import org.apache.sling.feature.scanner.spi.ExtensionScanner;
 import org.apache.sling.feature.scanner.spi.FrameworkScanner;
 
-import javax.json.JsonObject;
-import javax.json.JsonValue;
-
 /**
  * The scanner is a service that scans items and provides descriptions for
  * these. The following items can be scanned individually
@@ -176,7 +174,7 @@ public class Scanner {
     private void scanExtensions(final Feature f, final ContainerDescriptor desc)
     throws IOException {
         for (final Extension ext : f.getExtensions()) {
-            if (ext.getName().equals("analyser-metadata")) {
+            if (AnalyserMetaDataExtension.isAnalyserMetaDataExtension(ext)) {
                 continue;
             }
             ContainerDescriptor extDesc = null;
@@ -240,34 +238,21 @@ public class Scanner {
 
 
     private void populateCache(Feature feature) throws IOException {
-        Extension extension = feature.getExtensions().getByName("analyser-metadata");
+        AnalyserMetaDataExtension extension = AnalyserMetaDataExtension.getAnalyserMetaDataExtension(feature);
+
         if (extension != null) {
-            JsonObject json = extension.getJSONStructure().asJsonObject();
-            for (Map.Entry<String, JsonValue> entry : json.entrySet()) {
-                ArtifactId id = ArtifactId.fromMvnId(entry.getKey());
-                if (feature.getBundles().containsExact(id)) {
-                    Artifact bundle = feature.getBundles().getExact(id);
-                    final String key = id.toMvnId().concat(":")
-                            .concat(String.valueOf(bundle.getStartOrder())).concat(":")
-                            .concat(Stream.of(bundle.getFeatureOrigins()).map(ArtifactId::toMvnId).collect(Collectors.joining(",")));
-                    if (this.cache.get(key) == null) {
-                        JsonObject headers = entry.getValue().asJsonObject();
-                        if (headers.containsKey("manifest")) {
-                            URL file;
-                            try {
-                                file = artifactProvider.provide(id);
-                            } catch (Exception ex) {
-                                // Ignore, as we have the metadata cached we assume getting the file is a best effort.
-                                file = null;
-                            }
-                            Manifest manifest = new Manifest();
-                            JsonObject manifestHeaders = headers.getJsonObject("manifest");
-                            for (String name : manifestHeaders.keySet()) {
-                                manifest.getMainAttributes().putValue(name, manifestHeaders.getString(name));
-                            }
-                            BundleDescriptor desc = new BundleDescriptorImpl(bundle, file, manifest, bundle.getStartOrder());
-                            this.cache.put(key, desc);
-                        }
+            for (Artifact bundle : feature.getBundles()) {
+                ArtifactId id = bundle.getId();
+                final String key = id.toMvnId().concat(":")
+                        .concat(String.valueOf(bundle.getStartOrder())).concat(":")
+                        .concat(Stream.of(bundle.getFeatureOrigins()).map(ArtifactId::toMvnId).collect(Collectors.joining(",")));
+                if (this.cache.get(key) == null) {
+                    Map<String, String> headers = extension.getManifest(id);
+                    if (headers != null) {
+                        Manifest manifest = new Manifest();
+                        headers.forEach(manifest.getMainAttributes()::putValue);
+                        BundleDescriptor desc = new BundleDescriptorImpl(bundle, artifactProvider, manifest, bundle.getStartOrder());
+                        this.cache.put(key, desc);
                     }
                 }
             }
diff --git a/src/main/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImpl.java b/src/main/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImpl.java
index 641d269..7620946 100644
--- a/src/main/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImpl.java
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/BundleDescriptorImpl.java
@@ -21,10 +21,13 @@ import org.apache.felix.utils.manifest.Parser;
 import org.apache.felix.utils.resource.ResourceBuilder;
 import org.apache.felix.utils.resource.ResourceImpl;
 import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.builder.ArtifactProvider;
 import org.apache.sling.feature.io.IOUtils;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.PackageInfo;
 import org.osgi.framework.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.net.URL;
@@ -44,6 +47,11 @@ import java.util.stream.Collectors;
 public class BundleDescriptorImpl
     extends BundleDescriptor {
 
+    private static final Logger logger = LoggerFactory.getLogger(BundleDescriptorImpl.class);
+
+    /** The provider to use if now file is given up-front **/
+    private final ArtifactProvider artifactProvider;
+
     /** The bundle symbolic name. */
     private String symbolicName;
 
@@ -59,6 +67,7 @@ public class BundleDescriptorImpl
     /** The physical file for analyzing. */
     private final URL artifactFile;
 
+
     /** The corresponding artifact from the feature. */
     private final Artifact artifact;
 
@@ -71,17 +80,26 @@ public class BundleDescriptorImpl
     public BundleDescriptorImpl(final Artifact a,
             final URL file,
             final int startLevel) throws IOException  {
-        this(a, file, getManifest(file), startLevel);
+        this(a, file, null, getManifest(file), startLevel);
+    }
+
+    public BundleDescriptorImpl(final Artifact a,
+                                final ArtifactProvider provider,
+                                final Manifest manifest,
+                                final int startLevel) throws IOException {
+        this(a, null, provider, manifest, startLevel);
     }
 
     public BundleDescriptorImpl(final Artifact a,
                                 final URL file,
+                                final ArtifactProvider provider,
                                 final Manifest manifest,
                                 final int startLevel) throws IOException  {
         super(a.getId().toMvnId());
         this.artifact = a;
         this.startLevel = startLevel;
         this.artifactFile = file;
+        this.artifactProvider = provider;
         if ( manifest == null ) {
             throw new IOException("File has no manifest");
         }
@@ -120,7 +138,16 @@ public class BundleDescriptorImpl
 
     @Override
     public URL getArtifactFile() {
-        return artifactFile;
+        URL result = null;
+        if (artifactFile == null && artifactProvider != null) {
+            try {
+                result = artifactProvider.provide(artifact.getId());
+            } catch (Exception ex) {
+                // Ignore, we assume this is a best effort and callers can handle a null.
+                logger.debug("Unable to get artifact file for: " + artifact.getId(), ex);
+            }
+        }
+        return result;
     }
 
     @Override
diff --git a/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java
index bffd929..417f7a1 100644
--- a/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackageScanner.java
@@ -43,7 +43,7 @@ import org.slf4j.LoggerFactory;
 
 public class ContentPackageScanner {
 
-    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+    private static final Logger logger = LoggerFactory.getLogger(ContentPackageScanner.class);
 
     private final byte[] buffer = new byte[65536];
 
@@ -54,18 +54,19 @@ public class ContentPackageScanner {
     }
 
     public Set<ContentPackageDescriptor> scan(final Artifact desc, final URL file) throws IOException {
-
         final Set<ContentPackageDescriptor> contentPackages = new HashSet<>();
-        final ContentPackageDescriptor cp = new ContentPackageDescriptor(file.getPath());
-        final int lastDot = file.getPath().lastIndexOf(".");
-        cp.setName(file.getPath().substring(file.getPath().lastIndexOf("/") + 1, lastDot));
-        cp.setArtifact(desc);
-        cp.setArtifactFile(file);
+        if (file != null) {
+            final ContentPackageDescriptor cp = new ContentPackageDescriptor(file.getPath());
+            final int lastDot = file.getPath().lastIndexOf(".");
+            cp.setName(file.getPath().substring(file.getPath().lastIndexOf("/") + 1, lastDot));
+            cp.setArtifact(desc);
+            cp.setArtifactFile(file);
 
-        extractContentPackage(cp, contentPackages, file);
+            extractContentPackage(cp, contentPackages, file);
 
-        contentPackages.add(cp);
-        cp.lock();
+            contentPackages.add(cp);
+            cp.lock();
+        }
 
         return contentPackages;
     }
diff --git a/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackagesExtensionScanner.java b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackagesExtensionScanner.java
index 22d0971..ccf48b6 100644
--- a/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackagesExtensionScanner.java
+++ b/src/main/java/org/apache/sling/feature/scanner/impl/ContentPackagesExtensionScanner.java
@@ -27,9 +27,11 @@ import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.builder.ArtifactProvider;
 import org.apache.sling.feature.scanner.ContainerDescriptor;
 import org.apache.sling.feature.scanner.spi.ExtensionScanner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class ContentPackagesExtensionScanner implements ExtensionScanner {
-
+    private static final Logger logger = LoggerFactory.getLogger(ContentPackageScanner.class);
     @Override
     public String getId() {
         return Extension.EXTENSION_NAME_CONTENT_PACKAGES;
@@ -56,15 +58,27 @@ public class ContentPackagesExtensionScanner implements ExtensionScanner {
         final ContainerDescriptor cd = new ContainerDescriptor(feature.getId().toMvnId() + "(" + getId() + ")") {};
 
         for(final Artifact a : extension.getArtifacts()) {
-            final URL file = provider.provide(a.getId());
-            if ( file == null ) {
-                throw new IOException("Unable to find file for " + a.getId());
+            URL file = null;
+            try {
+                file = provider.provide(a.getId());
+            } catch (Exception ex) {
+                logger.debug("Unable to get artifact file for: " + a.getId(), ex);
             }
 
-            final Set<ContentPackageDescriptor> pcks = scanner.scan(a, file);
-            for(final ContentPackageDescriptor desc : pcks) {
+            if (file != null) {
+                final Set<ContentPackageDescriptor> pcks = scanner.scan(a, file);
+                for (final ContentPackageDescriptor desc : pcks) {
+                    cd.getArtifactDescriptors().add(desc);
+                    cd.getBundleDescriptors().addAll(desc.bundles);
+                }
+            }
+            else {
+                ContentPackageDescriptor desc = new ContentPackageDescriptor(a.getId().toMvnUrl());
+                final int lastDot = a.getId().toMvnPath().lastIndexOf(".");
+                desc.setName(a.getId().toMvnPath().substring(a.getId().toMvnPath().lastIndexOf("/") + 1, lastDot));
+                desc.setArtifact(a);
+                desc.lock();
                 cd.getArtifactDescriptors().add(desc);
-                cd.getBundleDescriptors().addAll(desc.bundles);
             }
         }
 
diff --git a/src/test/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtensionTest.java b/src/test/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtensionTest.java
new file mode 100644
index 0000000..fbb388a
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/analyser/extensions/AnalyserMetaDataExtensionTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.sling.feature.analyser.extensions;
+
+import org.apache.sling.feature.*;
+import org.apache.sling.feature.scanner.BundleDescriptor;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.jar.Manifest;
+
+public class AnalyserMetaDataExtensionTest {
+    @Test
+    public void testAnalyserMetaDataExtension() throws Exception {
+        Extension extension = new Extension(ExtensionType.JSON, AnalyserMetaDataExtension.EXTENSION_NAME, ExtensionState.REQUIRED);
+        extension.setJSON("{}");
+        AnalyserMetaDataExtension analyserMetaDataExtension = AnalyserMetaDataExtension.getAnalyserMetaDataExtension(extension);
+
+        Assert.assertNotNull(analyserMetaDataExtension);
+        Manifest manifest = new Manifest();
+        manifest.getMainAttributes().putValue("foo", "bar");
+        BundleDescriptor descriptor = Mockito.mock(BundleDescriptor.class);
+        Artifact artifact = new Artifact(ArtifactId.fromMvnId("org:foo:0.1"));
+        Mockito.when(descriptor.getManifest()).thenReturn(manifest);
+        Mockito.when(descriptor.getArtifact()).thenReturn(artifact);
+        analyserMetaDataExtension.add(descriptor);
+        analyserMetaDataExtension.setReportErrors(artifact.getId(), false);
+
+        analyserMetaDataExtension.toExtension(extension);
+
+        Extension testExtension = new Extension(ExtensionType.JSON, AnalyserMetaDataExtension.EXTENSION_NAME, ExtensionState.REQUIRED);
+        testExtension.setJSON(extension.getJSON());
+
+        Assert.assertTrue(AnalyserMetaDataExtension.isAnalyserMetaDataExtension(testExtension));
+
+        AnalyserMetaDataExtension testAnalyserMetaDataExtension = AnalyserMetaDataExtension.getAnalyserMetaDataExtension(testExtension);
+
+        Assert.assertNotNull(testAnalyserMetaDataExtension.getManifest(artifact.getId()));
+        Assert.assertEquals("bar", testAnalyserMetaDataExtension.getManifest(artifact.getId()).get("foo"));
+
+        Assert.assertFalse(testAnalyserMetaDataExtension.reportError(artifact.getId()));
+
+    }
+}
diff --git a/src/test/java/org/apache/sling/feature/analyser/task/impl/AnalyzerTaskProviderTest.java b/src/test/java/org/apache/sling/feature/analyser/task/impl/AnalyserTaskProviderTest.java
similarity index 98%
rename from src/test/java/org/apache/sling/feature/analyser/task/impl/AnalyzerTaskProviderTest.java
rename to src/test/java/org/apache/sling/feature/analyser/task/impl/AnalyserTaskProviderTest.java
index cec3f0a..ded11e6 100644
--- a/src/test/java/org/apache/sling/feature/analyser/task/impl/AnalyzerTaskProviderTest.java
+++ b/src/test/java/org/apache/sling/feature/analyser/task/impl/AnalyserTaskProviderTest.java
@@ -31,7 +31,7 @@ import java.util.ServiceLoader;
 import org.apache.sling.feature.analyser.task.AnalyserTask;
 import org.junit.Test;
 
-public final class AnalyzerTaskProviderTest {
+public final class AnalyserTaskProviderTest {
 
     private static int allTasks() {
         int size = 0;
diff --git a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApisJarsPropertiesTest.java b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApisJarsPropertiesTest.java
index 983b43f..e4c527b 100644
--- a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApisJarsPropertiesTest.java
+++ b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckApisJarsPropertiesTest.java
@@ -53,6 +53,30 @@ public class CheckApisJarsPropertiesTest {
         }
 
         @Override
+        public void reportArtifactWarning(ArtifactId artifactId, String message) {
+            System.out.println("[WARN] " + message);
+            warnings.add(message);
+        }
+
+        @Override
+        public void reportArtifactError(ArtifactId artifactId, String message) {
+            System.out.println("[ERROR] " + message);
+            errors.add(message);
+        }
+
+        @Override
+        public void reportExtensionWarning(String extension, String message) {
+            System.out.println("[WARN] " + message);
+            warnings.add(message);
+        }
+
+        @Override
+        public void reportExtensionError(String extension, String message) {
+            System.out.println("[ERROR] " + message);
+            errors.add(message);
+        }
+
+        @Override
         public void reportError(String message) {
             System.out.println("[ERROR] " + message);
             errors.add(message);
diff --git a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImportsTest.java b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImportsTest.java
index 5ebae03..ad2e4a5 100644
--- a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImportsTest.java
+++ b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckBundleExportsImportsTest.java
@@ -75,8 +75,8 @@ public class CheckBundleExportsImportsTest {
         Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
         t.execute(ctx);
 
-        Mockito.verify(ctx, Mockito.times(2)).reportError(Mockito.anyString());
-        Mockito.verify(ctx).reportError(Mockito.contains("org.foo.e"));
+        Mockito.verify(ctx, Mockito.times(1)).reportArtifactError(Mockito.any(), Mockito.anyString());
+        Mockito.verify(ctx).reportArtifactError(Mockito.any(), Mockito.contains("org.foo.e"));
         Mockito.verify(ctx).reportError(Mockito.contains("marked as 'complete'"));
         Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString());
     }
@@ -118,8 +118,8 @@ public class CheckBundleExportsImportsTest {
         Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
         t.execute(ctx);
 
-        Mockito.verify(ctx).reportError(Mockito.contains("org.foo.e"));
-        Mockito.verify(ctx, Mockito.times(1)).reportError(Mockito.anyString());
+        Mockito.verify(ctx).reportArtifactError(Mockito.any(), Mockito.contains("org.foo.e"));
+        Mockito.verify(ctx, Mockito.never()).reportError(Mockito.anyString());
         Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString());
     }
 
diff --git a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckBundleNativeCodeTest.java b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckBundleNativeCodeTest.java
index cffc839..d8a48d4 100644
--- a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckBundleNativeCodeTest.java
+++ b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckBundleNativeCodeTest.java
@@ -68,7 +68,7 @@ public class CheckBundleNativeCodeTest {
         fdAddBundle(fd, "g:b1:1", "something");
         task.execute(ctx);
 
-        Mockito.verify(ctx, Mockito.times(1)).reportError(Mockito.anyString());
+        Mockito.verify(ctx, Mockito.times(1)).reportArtifactError(Mockito.any(), Mockito.anyString());
         Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString());
     }
 
diff --git a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckRepoinitTest.java b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckRepoinitTest.java
index 1355e52..0cf89bc 100644
--- a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckRepoinitTest.java
+++ b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckRepoinitTest.java
@@ -141,6 +141,26 @@ public class CheckRepoinitTest {
         }
 
         @Override
+        public void reportArtifactWarning(ArtifactId artifactId, String message) {
+
+        }
+
+        @Override
+        public void reportArtifactError(ArtifactId artifactId, String message) {
+            errors.add(message);
+        }
+
+        @Override
+        public void reportExtensionWarning(String extension, String message) {
+
+        }
+
+        @Override
+        public void reportExtensionError(String extension, String message) {
+            errors.add(message);
+        }
+
+        @Override
         public void reportError(String message) {
             errors.add(message);
         }
diff --git a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilitiesTest.java b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilitiesTest.java
index ea693b9..b08e095 100644
--- a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilitiesTest.java
+++ b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckRequirementsCapabilitiesTest.java
@@ -93,7 +93,7 @@ public class CheckRequirementsCapabilitiesTest {
         CheckRequirementsCapabilities crc = new CheckRequirementsCapabilities();
         crc.execute(ctx);
 
-        Mockito.verify(ctx).reportError(Mockito.contains("org.foo.bar"));
+        Mockito.verify(ctx).reportArtifactError(Mockito.any(), Mockito.contains("org.foo.bar"));
     }
 
     @Test