SLING-11864 Add new exported interface for filtering default properties
diff --git a/pom.xml b/pom.xml
index 0ebfccd..7cc44df 100644
--- a/pom.xml
+++ b/pom.xml
@@ -119,6 +119,17 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <!-- JetBrains annotations for null-analysis (SLING-7798), https://github.com/JetBrains/java-annotations -->
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/ConfigurationMerger.java b/src/main/java/org/apache/sling/installer/factories/configuration/ConfigurationMerger.java
new file mode 100644
index 0000000..28b1b6e
--- /dev/null
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/ConfigurationMerger.java
@@ -0,0 +1,50 @@
+/*
+ * 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.installer.factories.configuration;
+
+import java.util.Dictionary;
+
+import org.jetbrains.annotations.NotNull;
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * Exposes helper methods to identify those configuration properties which are set through installer resources having a configured merge scheme.
+ * Those configuration properties are referred to as default properties. 
+ * Note though that both OSGi metatype and OSGi declarative services define default properties as well which are not meant here.
+ * @see <a href="https://sling.apache.org/documentation/bundles/configuration-installer-factory.html#merging-of-configurations">Merging of Configurations</a>
+ */
+@ProviderType
+public interface ConfigurationMerger {
+
+    /**
+     * Modifies the given dictionary so that all properties which have values equal to one 
+     * of the same named property values provided by the inherited configurations are removed.
+     * @param pid the PID of the configuration
+     * @param dict the configuration properties to modify
+     */
+    void removeDefaultProperties(@NotNull final String pid, @NotNull final Dictionary<String, Object> dict);
+
+    /**
+     * Returns all properties for the given PID which are set through any of the resource with the configured merge schemes.
+     * @param pid the PID of the configuration
+     * @return the properties set through specific installer resources in a mutable dictionary
+     */
+    Dictionary<String, Object> getDefaultProperties(@NotNull final String pid);
+
+}
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
index e4bd388..147101b 100644
--- a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
@@ -18,10 +18,8 @@
  */
 package org.apache.sling.installer.factories.configuration.impl;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Dictionary;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
@@ -29,9 +27,6 @@
 
 import org.apache.sling.installer.api.InstallableResource;
 import org.apache.sling.installer.api.ResourceChangeListener;
-import org.apache.sling.installer.api.info.InfoProvider;
-import org.apache.sling.installer.api.info.Resource;
-import org.apache.sling.installer.api.info.ResourceGroup;
 import org.apache.sling.installer.api.tasks.ChangeStateTask;
 import org.apache.sling.installer.api.tasks.InstallTask;
 import org.apache.sling.installer.api.tasks.InstallTaskFactory;
@@ -42,6 +37,7 @@
 import org.apache.sling.installer.api.tasks.TaskResourceGroup;
 import org.apache.sling.installer.api.tasks.TransformationResult;
 import org.apache.sling.installer.factories.configuration.ConfigurationConstants;
+import org.apache.sling.installer.factories.configuration.ConfigurationMerger;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceRegistration;
@@ -68,14 +64,14 @@
     private final ResourceChangeListener changeListener;
 
     /** Info Provider */
-    private final InfoProvider infoProvider;
+    private final ConfigurationMerger configMerger;
 
     public ConfigTaskCreator(final ResourceChangeListener listener,
             final ConfigurationAdmin configAdmin,
-            final InfoProvider infoProvider) {
+            final ConfigurationMerger configMerger) {
         this.changeListener = listener;
         this.configAdmin = configAdmin;
-        this.infoProvider = infoProvider;
+        this.configMerger = configMerger;
     }
 
     public ServiceRegistration<?> register(final BundleContext bundleContext) {
@@ -164,7 +160,7 @@
                             attrs.put(ConfigurationAdmin.SERVICE_FACTORYPID, event.getFactoryPid());
                         }
 
-                        removeDefaultProperties(this.infoProvider, event.getPid(), dict);
+                        configMerger.removeDefaultProperties(event.getPid(), dict);
                         this.changeListener.resourceAddedOrUpdated(InstallableResource.TYPE_CONFIG, event.getPid(), null, dict, attrs);
 
                     } else {
@@ -177,50 +173,6 @@
         }
     }
 
-    public static Dictionary<String, Object> getDefaultProperties(final InfoProvider infoProvider, final String pid) {
-        if ( Activator.MERGE_SCHEMES != null ) {
-            final List<Dictionary<String, Object>> propertiesList = new ArrayList<>();
-            final String entityId = InstallableResource.TYPE_CONFIG.concat(":").concat(pid);
-            boolean done = false;
-            for(final ResourceGroup group : infoProvider.getInstallationState().getInstalledResources()) {
-                for(final Resource rsrc : group.getResources()) {
-                    if ( rsrc.getEntityId().equals(entityId) ) {
-                        done = true;
-                        if ( Activator.MERGE_SCHEMES.contains(rsrc.getScheme()) ) {
-                            propertiesList.add(rsrc.getDictionary());
-                        }
-                    }
-                }
-                if ( done ) {
-                    break;
-                }
-            }
-            if ( !propertiesList.isEmpty() ) {
-                final Dictionary<String, Object> defaultProps = ConfigUtil.mergeReverseOrder(propertiesList);
-                return defaultProps;
-            }
-        }
-        return null;
-    }
-
-    public static void removeDefaultProperties(final InfoProvider infoProvider, final String pid, final Dictionary<String, Object> dict) {
-        if ( Activator.MERGE_SCHEMES != null ) {
-            final Dictionary<String, Object> defaultProps = getDefaultProperties(infoProvider, pid);
-            if ( defaultProps != null ) {
-                final Enumeration<String> keyEnum = defaultProps.keys();
-                while ( keyEnum.hasMoreElements() ) {
-                    final String key = keyEnum.nextElement();
-                    final Object value = defaultProps.get(key);
-
-                    final Object newValue = dict.get(key);
-                    if ( newValue != null && ConfigUtil.isSameValue(newValue, value)) {
-                        dict.remove(key);
-                    }
-                }
-            }
-        }
-    }
-
     /**
      * @see org.apache.sling.installer.api.tasks.ResourceTransformer#transform(org.apache.sling.installer.api.tasks.RegisteredResource)
      */
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationMergerImpl.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationMergerImpl.java
new file mode 100644
index 0000000..80f490e
--- /dev/null
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationMergerImpl.java
@@ -0,0 +1,91 @@
+/*
+ * 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.installer.factories.configuration.impl;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.List;
+
+import org.apache.sling.installer.api.InstallableResource;
+import org.apache.sling.installer.api.info.InfoProvider;
+import org.apache.sling.installer.api.info.Resource;
+import org.apache.sling.installer.api.info.ResourceGroup;
+import org.apache.sling.installer.factories.configuration.ConfigurationMerger;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component
+public class ConfigurationMergerImpl implements ConfigurationMerger {
+
+    private final InfoProvider infoProvider;
+
+    @Activate
+    public ConfigurationMergerImpl(@Reference InfoProvider infoProvider) {
+        this.infoProvider = infoProvider;
+    }
+
+    @Override
+    public void removeDefaultProperties(@NotNull final String pid, @NotNull final Dictionary<String, Object> dict) {
+        if ( Activator.MERGE_SCHEMES != null ) {
+            final Dictionary<String, Object> defaultProps = getDefaultProperties(pid);
+            if ( defaultProps != null ) {
+                final Enumeration<String> keyEnum = defaultProps.keys();
+                while ( keyEnum.hasMoreElements() ) {
+                    final String key = keyEnum.nextElement();
+                    final Object value = defaultProps.get(key);
+
+                    final Object newValue = dict.get(key);
+                    if ( newValue != null && ConfigUtil.isSameValue(newValue, value)) {
+                        dict.remove(key);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public Dictionary<String, Object> getDefaultProperties(@NotNull final String pid) {
+        if ( Activator.MERGE_SCHEMES != null ) {
+            final List<Dictionary<String, Object>> propertiesList = new ArrayList<>();
+            final String entityId = InstallableResource.TYPE_CONFIG.concat(":").concat(pid);
+            boolean done = false;
+            for(final ResourceGroup group : infoProvider.getInstallationState().getInstalledResources()) {
+                for(final Resource rsrc : group.getResources()) {
+                    if ( rsrc.getEntityId().equals(entityId) ) {
+                        done = true;
+                        if ( Activator.MERGE_SCHEMES.contains(rsrc.getScheme()) ) {
+                            propertiesList.add(rsrc.getDictionary());
+                        }
+                    }
+                }
+                if ( done ) {
+                    break;
+                }
+            }
+            if ( !propertiesList.isEmpty() ) {
+                final Dictionary<String, Object> defaultProps = ConfigUtil.mergeReverseOrder(propertiesList);
+                return defaultProps;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ServicesListener.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ServicesListener.java
index 139eb2f..216e41c 100644
--- a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ServicesListener.java
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ServicesListener.java
@@ -21,7 +21,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.sling.installer.api.ResourceChangeListener;
-import org.apache.sling.installer.api.info.InfoProvider;
+import org.apache.sling.installer.factories.configuration.ConfigurationMerger;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
@@ -53,7 +53,7 @@
     private final Listener configAdminListener;
 
     /** The listener for the installer info service. */
-    private final Listener infoServiceListener;
+    private final Listener configMergerListener;
 
     /** Registration the service. */
     private volatile ServiceRegistration<?> configTaskCreatorRegistration;
@@ -69,8 +69,8 @@
         this.bundleContext = bundleContext;
         this.changeHandlerListener = new Listener(ResourceChangeListener.class.getName());
         this.configAdminListener = new Listener(ConfigurationAdmin.class.getName());
-        this.infoServiceListener = new Listener(InfoProvider.class.getName());
-        this.infoServiceListener.start();
+        this.configMergerListener = new Listener(ConfigurationMerger.class.getName());
+        this.configMergerListener.start();
         this.changeHandlerListener.start();
         this.configAdminListener.start();
     }
@@ -79,13 +79,13 @@
         // check if all services are available
         final ResourceChangeListener listener = (ResourceChangeListener)this.changeHandlerListener.getService();
         final ConfigurationAdmin configAdmin = (ConfigurationAdmin)this.configAdminListener.getService();
-        final InfoProvider infoProvider = (InfoProvider)this.infoServiceListener.getService();
+        final ConfigurationMerger configMerger = (ConfigurationMerger)this.configMergerListener.getService();
 
-        if ( configAdmin != null && listener != null && infoProvider != null ) {
+        if ( configAdmin != null && listener != null && configMerger != null ) {
             if ( configTaskCreator == null ) {
                 active.set(true);
                 // start and register osgi installer service
-                this.configTaskCreator = new ConfigTaskCreator(listener, configAdmin, infoProvider);
+                this.configTaskCreator = new ConfigTaskCreator(listener, configAdmin, configMerger);
                 final ConfigUpdateHandler handler = new ConfigUpdateHandler(configAdmin, this);
                 configTaskCreatorRegistration = handler.register(this.bundleContext);
                 if ( Activator.MERGE_SCHEMES != null ) {
@@ -93,7 +93,7 @@
 
                         @Override
                         public Object getService(final Bundle bundle, final ServiceRegistration<Object> registration) {
-                            return new WebconsoleConfigurationHandler(bundleContext, infoProvider);
+                            return new WebconsoleConfigurationHandler(bundleContext, configMerger);
                         }
 
                         @Override
@@ -138,7 +138,7 @@
      * Deactivate this listener.
      */
     public void deactivate() {
-        this.infoServiceListener.deactivate();
+        this.configMergerListener.deactivate();
         this.changeHandlerListener.deactivate();
         this.configAdminListener.deactivate();
         this.stop();
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/WebconsoleConfigurationHandler.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/WebconsoleConfigurationHandler.java
index 3aa0161..d9ee81b 100644
--- a/src/main/java/org/apache/sling/installer/factories/configuration/impl/WebconsoleConfigurationHandler.java
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/WebconsoleConfigurationHandler.java
@@ -23,7 +23,7 @@
 
 import org.apache.felix.webconsole.spi.ConfigurationHandler;
 import org.apache.felix.webconsole.spi.ValidationException;
-import org.apache.sling.installer.api.info.InfoProvider;
+import org.apache.sling.installer.factories.configuration.ConfigurationMerger;
 import org.osgi.framework.BundleContext;
 import org.osgi.util.tracker.ServiceTracker;
 
@@ -31,14 +31,14 @@
 
     static final String META_TYPE_NAME = "org.osgi.service.metatype.MetaTypeService"; 
 
-    private final InfoProvider infoProvider;
+    private final ConfigurationMerger configMerger;
 
     private final ServiceTracker<Object, Object> metatypeTracker;
 
     private final BundleContext bundleContext;
 
-    public WebconsoleConfigurationHandler(final BundleContext context, final InfoProvider infoProvider) {
-        this.infoProvider = infoProvider;
+    public WebconsoleConfigurationHandler(final BundleContext context, final ConfigurationMerger configMerger) {
+        this.configMerger = configMerger;
         this.bundleContext = context;
         this.metatypeTracker = new ServiceTracker<>(context, META_TYPE_NAME, null);
         this.metatypeTracker.open();    
@@ -68,7 +68,7 @@
             throws ValidationException, IOException {
         final Object mts = this.metatypeTracker.getService();
         if ( mts != null ) {
-            final Dictionary<String, Object> defaultProps = ConfigTaskCreator.getDefaultProperties(infoProvider, pid);
+            final Dictionary<String, Object> defaultProps = configMerger.getDefaultProperties(pid);
             final MetatypeHandler mt = new MetatypeHandler(mts, this.bundleContext);
             mt.updateConfiguration(factoryPid, pid, props, defaultProps);
         }        
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/package-info.java b/src/main/java/org/apache/sling/installer/factories/configuration/package-info.java
index eb71c4d..747378d 100644
--- a/src/main/java/org/apache/sling/installer/factories/configuration/package-info.java
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@org.osgi.annotation.versioning.Version("1.1.2")
+@org.osgi.annotation.versioning.Version("1.2.0")
 package org.apache.sling.installer.factories.configuration;