SLING-10305 : Allow analyser tasks to report configuration warnings/errors
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 36532dc..262c540 100644
--- a/src/main/java/org/apache/sling/feature/analyser/Analyser.java
+++ b/src/main/java/org/apache/sling/feature/analyser/Analyser.java
@@ -31,6 +31,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
 import org.apache.sling.feature.ExecutionEnvironmentExtension;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.analyser.extensions.AnalyserMetaDataExtension;
@@ -203,10 +204,12 @@
         final List<AnalyserResult.GlobalReport> globalWarnings = new ArrayList<>();
         final List<AnalyserResult.ArtifactReport> artifactWarnings = new ArrayList<>();
         final List<AnalyserResult.ExtensionReport> extensionWarnings = new ArrayList<>();
+        final List<AnalyserResult.ConfigurationReport> configurationWarnings = new ArrayList<>();
 
         final List<AnalyserResult.GlobalReport> globalErrors = new ArrayList<>();
         final List<AnalyserResult.ArtifactReport> artifactErrors = new ArrayList<>();
         final List<AnalyserResult.ExtensionReport> extensionErrors = new ArrayList<>();
+        final List<AnalyserResult.ConfigurationReport> configurationErrors = new ArrayList<>();
 
         AnalyserMetaDataExtension analyserMetaDataExtension = AnalyserMetaDataExtension.getAnalyserMetaDataExtension(feature);
 
@@ -291,6 +294,20 @@
                         globalErrors.add(new AnalyserResult.GlobalReport(message));
                     }
                 }
+
+                @Override
+                public void reportConfigurationError(Configuration cfg, String message) {
+                    if (analyserMetaDataExtension == null || analyserMetaDataExtension.reportWarning(feature.getId())) {
+                        configurationErrors.add(new AnalyserResult.ConfigurationReport(cfg, message));
+                    }
+                }
+
+                @Override
+                public void reportConfigurationWarning(Configuration cfg, String message) {
+                    if (analyserMetaDataExtension == null || analyserMetaDataExtension.reportWarning(feature.getId())) {
+                        configurationWarnings.add(new AnalyserResult.ConfigurationReport(cfg, message));
+                    }
+                }
             });
         }
 
@@ -340,6 +357,16 @@
             public BundleDescriptor getFrameworkDescriptor() {
                 return fwkDesc;
             }
+
+            @Override
+            public List<ConfigurationReport> getConfigurationErrors() {
+                return configurationErrors;
+            }
+
+            @Override
+            public List<ConfigurationReport> getConfigurationWarnings() {
+                return configurationWarnings;
+            }
         };
     }
 
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 a84a1b1..6076b5c 100644
--- a/src/main/java/org/apache/sling/feature/analyser/AnalyserResult.java
+++ b/src/main/java/org/apache/sling/feature/analyser/AnalyserResult.java
@@ -17,6 +17,7 @@
 package org.apache.sling.feature.analyser;
 
 import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
 import org.apache.sling.feature.scanner.BundleDescriptor;
 import org.apache.sling.feature.scanner.FeatureDescriptor;
 import org.osgi.annotation.versioning.ProviderType;
@@ -32,9 +33,10 @@
 @ProviderType
 public interface AnalyserResult {
 
-    class Report<T> {
+    public class Report<T> {
         private final T key;
         private final String value;
+        
         Report(T key, String value) {
             this.key = key;
             this.value = value;
@@ -42,39 +44,63 @@
         public T getKey() {
             return key;
         }
+        
         public String getValue() {
             return value;
         }
+        
+        public String toString() {
+            return this.getKey().toString().concat(": ").concat(this.getValue());
+        }
     }
 
-    class ArtifactReport extends Report<ArtifactId> {
+    /**
+     * Report about a configuration
+     * @since 1.4.0
+     */
+    public class ConfigurationReport extends Report<Configuration> {
+        ConfigurationReport(Configuration key, String value) {
+            super(key, value);
+        }
+
+        public String toString() {
+            return "Configuration ".concat(this.getKey().getPid()).concat(": ").concat(this.getValue());
+        }
+    }
+
+    public class ArtifactReport extends Report<ArtifactId> {
         ArtifactReport(ArtifactId key, String value) {
             super(key, value);
         }
     }
 
-    class ExtensionReport extends Report<String> {
+    public class ExtensionReport extends Report<String> {
         ExtensionReport(String key, String value) {
             super(key, value);
         }
     }
 
-    class GlobalReport extends Report<Void> {
+    public class GlobalReport extends Report<Void> {
 
         GlobalReport(String value) {
             super(null, value);
         }
+
+        public String toString() {
+            return this.getValue();
+        }
     }
 
     /**
      * 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.
+     * @deprecated - use {@link #getGlobalWarnings()}, {@link #getArtifactWarnings()}, {@link #getExtensionWarnings()}, and {@link #getConfigurationWarnings()} 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()))
+        return Stream.of(getGlobalWarnings().stream().map(GlobalReport::toString),
+                getArtifactWarnings().stream().map(Report::toString),
+                getExtensionWarnings().stream().map(Report::toString),
+                getConfigurationWarnings().stream().map(Report::toString))
                 .flatMap(Function.identity())
                 .collect(Collectors.toList());
     }
@@ -98,14 +124,22 @@
     List<ExtensionReport> getExtensionWarnings();
 
     /**
+     * List of warnings for configurations. Warnings can be used to improve the feature.
+     * @return A list of warnings might be empty.
+     * @since 1.4.0
+     */
+    List<ConfigurationReport> getConfigurationWarnings();
+
+    /**
      * 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.
+     * @deprecated - use {@link #getGlobalErrors()}, {@link #getArtifactErrors()}, {@link #getExtensionErrors()}, and {@link #getConfigurationErrors()} 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()))
+        return Stream.of(getGlobalErrors().stream().map(Report::toString),
+                getArtifactErrors().stream().map(Report::toString),
+                getExtensionErrors().stream().map(Report::toString),
+                getConfigurationErrors().stream().map(Report::toString))
                 .flatMap(Function.identity())
                 .collect(Collectors.toList());
     }
@@ -129,6 +163,13 @@
     List<ExtensionReport> getExtensionErrors();
 
     /**
+     * List of errors for configurations. Errors should be fixed in the feature
+     * @return A list of errors might be empty
+     * @since 1.4.0
+     */
+    List<ConfigurationReport> getConfigurationErrors();
+
+    /**
      * Return the feature descriptor created during scanning
      * @return The feature descriptor
      * @since 1.2.0
diff --git a/src/main/java/org/apache/sling/feature/analyser/package-info.java b/src/main/java/org/apache/sling/feature/analyser/package-info.java
index ffa4b92..184d445 100644
--- a/src/main/java/org/apache/sling/feature/analyser/package-info.java
+++ b/src/main/java/org/apache/sling/feature/analyser/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@org.osgi.annotation.versioning.Version("1.3.0")
+@org.osgi.annotation.versioning.Version("1.4.0")
 package org.apache.sling.feature.analyser;
 
 
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 f5cee5c..24718df 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
@@ -17,6 +17,7 @@
 package org.apache.sling.feature.analyser.task;
 
 import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.builder.FeatureProvider;
 import org.apache.sling.feature.scanner.BundleDescriptor;
@@ -101,6 +102,24 @@
 
     /**
      * This method is invoked by a {@link AnalyserTask} to report
+     * a configuration warning.
+     * @param cfg the configuration.
+     * @param message The message.
+     * @since 1.3.0
+     */
+    void reportConfigurationWarning(Configuration cfg, String message);
+
+    /**
+     * This method is invoked by a {@link AnalyserTask} to report
+     * a configuration error.
+     * @param cfg the configuration.
+     * @param message The message.
+     * @since 1.3.0
+     */
+    void reportConfigurationError(Configuration cfg, String message);
+
+    /**
+     * This method is invoked by a {@link AnalyserTask} to report
      * a global error.
      * @param message The message.
      */
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 85a724d..0ab2231 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.2.0")
+@org.osgi.annotation.versioning.Version("1.3.0")
 package org.apache.sling.feature.analyser.task;
 
 
diff --git a/src/test/java/org/apache/sling/feature/analyser/AnalyserTest.java b/src/test/java/org/apache/sling/feature/analyser/AnalyserTest.java
index c62b382..dbbb799 100644
--- a/src/test/java/org/apache/sling/feature/analyser/AnalyserTest.java
+++ b/src/test/java/org/apache/sling/feature/analyser/AnalyserTest.java
@@ -16,14 +16,23 @@
  */
 package org.apache.sling.feature.analyser;
 
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.analyser.task.AnalyserTask;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.apache.sling.feature.scanner.Scanner;
 import org.junit.Test;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 public class AnalyserTest {
     @Test
@@ -51,4 +60,79 @@
         Map<String, String> cfg = a.getConfiguration("an-analyser");
         assertEquals(0, cfg.size());
     }
+
+    @Test public void testResult() throws Exception {
+        final Feature f = new Feature(ArtifactId.parse("g:a:1"));
+        final Configuration c = new Configuration("config.pid");
+        final Artifact bundle = new Artifact(ArtifactId.parse("g:b:1"));
+
+        final Scanner scanner = new Scanner(null);
+        final Analyser a = new Analyser(scanner, new AnalyserTask(){
+
+            @Override
+            public void execute(AnalyserTaskContext ctx) throws Exception {
+                ctx.reportArtifactError(bundle.getId(), "artifact-error");
+                ctx.reportArtifactWarning(bundle.getId(), "artifact-warn");
+                ctx.reportExtensionError("name", "extension-error");
+                ctx.reportExtensionWarning("name", "extension-warn");
+                ctx.reportConfigurationError(c, "config-error");
+                ctx.reportConfigurationWarning(c, "config-warn");
+                ctx.reportError("global-error");
+                ctx.reportWarning("global-warn");                
+            }            
+        });
+        final AnalyserResult result = a.analyse(f);
+        assertNotNull(result);
+
+        assertEquals(4, result.getErrors().size());
+        assertEquals(Arrays.asList("global-error",
+            bundle.getId().toString()+": artifact-error",
+            "name: extension-error", 
+            "Configuration " + c.getPid() + ": config-error"), result.getErrors());
+        assertEquals(4, result.getWarnings().size());
+        assertEquals(Arrays.asList("global-warn",
+            bundle.getId().toString()+": artifact-warn",
+            "name: extension-warn", 
+            "Configuration " + c.getPid() + ": config-warn"), result.getWarnings());
+
+        assertEquals(1, result.getGlobalErrors().size());
+        assertEquals("global-error", result.getGlobalErrors().get(0).toString());
+        assertEquals("global-error", result.getGlobalErrors().get(0).getValue());
+        assertNull(result.getGlobalErrors().get(0).getKey());
+
+        assertEquals(1, result.getGlobalWarnings().size());
+        assertEquals("global-warn", result.getGlobalWarnings().get(0).toString());
+        assertEquals("global-warn", result.getGlobalWarnings().get(0).getValue());
+        assertNull(result.getGlobalWarnings().get(0).getKey());
+
+        assertEquals(1, result.getArtifactErrors().size());
+        assertEquals(bundle.getId().toString()+": artifact-error", result.getArtifactErrors().get(0).toString());
+        assertEquals("artifact-error", result.getArtifactErrors().get(0).getValue());
+        assertEquals(bundle.getId(), result.getArtifactErrors().get(0).getKey());
+
+        assertEquals(1, result.getArtifactWarnings().size());
+        assertEquals(bundle.getId().toString()+": artifact-warn", result.getArtifactWarnings().get(0).toString());
+        assertEquals("artifact-warn", result.getArtifactWarnings().get(0).getValue());
+        assertEquals(bundle.getId(), result.getArtifactWarnings().get(0).getKey());
+
+        assertEquals(1, result.getExtensionErrors().size());
+        assertEquals("name: extension-error", result.getExtensionErrors().get(0).toString());
+        assertEquals("extension-error", result.getExtensionErrors().get(0).getValue());
+        assertEquals("name", result.getExtensionErrors().get(0).getKey());
+
+        assertEquals(1, result.getExtensionWarnings().size());
+        assertEquals("name: extension-warn", result.getExtensionWarnings().get(0).toString());
+        assertEquals("extension-warn", result.getExtensionWarnings().get(0).getValue());
+        assertEquals("name", result.getExtensionWarnings().get(0).getKey());
+
+        assertEquals(1, result.getConfigurationErrors().size());
+        assertEquals("Configuration " + c.getPid() + ": config-error", result.getConfigurationErrors().get(0).toString());
+        assertEquals("config-error", result.getConfigurationErrors().get(0).getValue());
+        assertEquals(c, result.getConfigurationErrors().get(0).getKey());
+
+        assertEquals(1, result.getConfigurationWarnings().size());
+        assertEquals("Configuration " + c.getPid() + ": config-warn", result.getConfigurationWarnings().get(0).toString());
+        assertEquals("config-warn", result.getConfigurationWarnings().get(0).getValue());
+        assertEquals(c, result.getConfigurationWarnings().get(0).getKey());
+    }
 }
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 e4c527b..8ba07fc 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
@@ -28,6 +28,7 @@
 
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
 import org.apache.sling.feature.builder.FeatureProvider;
@@ -114,6 +115,18 @@
         public List<String> getWarnings() {
             return warnings;
         }
+
+        @Override
+        public void reportConfigurationError(Configuration cfg, String message) {
+            System.out.println("[WARN] " + message);
+            errors.add(message);
+        }
+
+        @Override
+        public void reportConfigurationWarning(Configuration cfg, String message) {
+            System.out.println("[ERROR] " + message);
+            warnings.add(message);
+        }
     }
     
     static class FeatureStub extends Feature {