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:40 UTC

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

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