SLING-10412 : Unify and simplify internal configuration handling
diff --git a/docs/api-regions.md b/docs/api-regions.md
index f288d27..fa164c6 100644
--- a/docs/api-regions.md
+++ b/docs/api-regions.md
@@ -314,16 +314,20 @@
 
 ### Internal Configurations
 
-Some OSGi configurations and factory configurations are not part of the public API and cannot be created/updated by application configuration. Same applies to framework properties. The PIDs, factory PIDs and names of these can be specified as well:
+Some OSGi configurations and factory configurations are not part of the public API and cannot be created/updated by application configuration. Same applies to framework properties. For configurations and factory configurations, define a configuration description without any properties. Internal framework properties are listed with just their names:
 
 ``` json
 "configuration-api:JSON" : {
-    "internal-configurations" : [
-        // list of PIDs
-    ],
-    "internal-factory-configurations" : [
-        // list of factory PIDs
-    ],
+    "configurations" : {
+        "org.apache.sling.engine.impl.InternalRequestHandling" : {
+            // no properties
+        }
+    },
+    "factory-configurations" : {
+        "org.apache.sling.engine.impl.InternalLogger" : {
+            // no properties
+        }
+    },
     "internal-framework-properties" : {
         // list of property names
     }
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApi.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApi.java
index b65f14e..1152260 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApi.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApi.java
@@ -266,6 +266,7 @@
 	/**
      * Get the internal configuration pids
 	 * @return Mutable set of internal configuration pids
+     * @deprecated Please use empty configuration descriptions via {@link #getConfigurationDescriptions()}
 	 */
 	public Set<String> getInternalConfigurations() {
 		return internalConfigurations;
@@ -274,6 +275,7 @@
 	/**
      * Get the internal factory pids
 	 * @return Mutable set of internal factory configuration pids
+     * @deprecated Please use empty factory configuration descriptions via {@link #getFactoryConfigurationDescriptions()}
 	 */
 	public Set<String> getInternalFactoryConfigurations() {
 		return internalFactories;
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/package-info.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/package-info.java
index 6ac6f43..64d49cb 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/package-info.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@org.osgi.annotation.versioning.Version("1.2.0")
+@org.osgi.annotation.versioning.Version("1.2.1")
 package org.apache.sling.feature.extension.apiregions.api.config;
 
 
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidationResult.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidationResult.java
index 7066ff1..4c4ccb6 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidationResult.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidationResult.java
@@ -33,6 +33,12 @@
     
     private final List<String> warnings = new ArrayList<>();
 
+    /** 
+     * Should the default configuration be used?
+     * @since 1.3
+     */
+    private boolean useDefault = false;
+
     /**
      * Is the configuration valid?
      * @return {@code true} if it is valid
@@ -73,4 +79,23 @@
     public List<String> getWarnings() {
         return this.warnings;
     }
+
+	/**
+     * Should the default be used instead of the configuration values?
+	 * @return {@code true} if the default should be used.
+     * @see #getDefaultValue()
+     * @since 1.3
+	 */
+	public boolean isUseDefaultValue() {
+		return useDefault;
+	}
+
+	/**
+     * Set whether the default values should be used
+	 * @param useDefault boolean flag
+     * @since 1.3
+	 */
+	public void setUseDefaultValue(final boolean useDefault) {
+		this.useDefault = useDefault;
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidator.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidator.java
index ced08c7..b73022a 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidator.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidator.java
@@ -77,13 +77,21 @@
             if ( !(desc instanceof FactoryConfigurationDescription) ) {
                 result.getErrors().add("Factory configuration cannot be validated against non factory configuration description");
             } else {
-                validateProperties(config, desc, result.getPropertyResults(), region, validationMode);
+                if ( desc.getPropertyDescriptions().isEmpty()) {
+                    setResult(result, region, validationMode, "Factory configuration is not allowed");
+                } else {
+                    validateProperties(config, desc, result.getPropertyResults(), region, validationMode);
+                }
             }
         } else {
             if ( !(desc instanceof ConfigurationDescription) ) {
                 result.getErrors().add("Configuration cannot be validated against factory configuration description");
             } else {
-                validateProperties(config, desc, result.getPropertyResults(), region, validationMode);
+                if ( desc.getPropertyDescriptions().isEmpty()) {
+                    setResult(result, region, validationMode, "Configuration is not allowed");
+                } else {
+                    validateProperties(config, desc, result.getPropertyResults(), region, validationMode);
+                }
             }
         }
 
@@ -131,6 +139,19 @@
         }
     }
 
+    void setResult(final ConfigurationValidationResult result, final Region region, final Mode validationMode, final String msg) {
+        if ( region != Region.INTERNAL ) {
+            if ( validationMode == Mode.STRICT ) {
+                result.getErrors().add(msg);
+            } else if ( validationMode == Mode.LENIENT || validationMode == Mode.DEFINITIVE ) {
+                result.getWarnings().add(msg);
+            }
+            if ( validationMode == Mode.DEFINITIVE || validationMode == Mode.SILENT_DEFINITIVE ) {
+                result.setUseDefaultValue(true);
+            }
+        }
+    }
+
     void setResult(final PropertyValidationResult result, final Mode validationMode, final String msg) {
         if ( validationMode == Mode.STRICT ) {
             result.getErrors().add(msg);
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidator.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidator.java
index e26f773..7ab6187 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidator.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidator.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sling.feature.extension.apiregions.api.config.validation;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -172,6 +174,15 @@
         boolean changed = false;
 
         for(final Map.Entry<String, ConfigurationValidationResult> entry : result.getConfigurationResults().entrySet()) {
+            if ( entry.getValue().isUseDefaultValue() ) {
+                final Configuration cfg = feature.getConfigurations().getConfiguration(entry.getKey());
+                if ( cfg != null ) {
+                    final List<String> keys = new ArrayList<>(Collections.list(cfg.getConfigurationProperties().keys()));
+                    for(final String k : keys ) {
+                        cfg.getProperties().remove(k);
+                    }
+                }
+            }
             for(final Map.Entry<String, PropertyValidationResult> propEntry : entry.getValue().getPropertyResults().entrySet()) {
                 if ( propEntry.getValue().isUseDefaultValue() ) {
                     final Configuration cfg = feature.getConfigurations().getConfiguration(entry.getKey());
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/package-info.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/package-info.java
index 0cc3da8..332f38d 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/package-info.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/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.extension.apiregions.api.config.validation;
 
 
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidatorTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidatorTest.java
index e09d24d..b233ee0 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidatorTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidatorTest.java
@@ -23,6 +23,7 @@
 import org.apache.sling.feature.Configuration;
 import org.apache.sling.feature.extension.apiregions.api.config.ConfigurationDescription;
 import org.apache.sling.feature.extension.apiregions.api.config.FactoryConfigurationDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.Mode;
 import org.apache.sling.feature.extension.apiregions.api.config.PropertyDescription;
 import org.apache.sling.feature.extension.apiregions.api.config.PropertyType;
 import org.apache.sling.feature.extension.apiregions.api.config.Region;
@@ -54,6 +55,8 @@
     @Test public void testDeprecated() {
         final Configuration cfg = new Configuration("org.apache");
         final ConfigurationDescription cd = new ConfigurationDescription();
+        final PropertyDescription prop = new PropertyDescription();
+        cd.getPropertyDescriptions().put("a", prop);
         
         ConfigurationValidationResult result = validator.validate(cfg, cd, null);
         assertTrue(result.isValid());
@@ -70,6 +73,8 @@
         final Configuration cfg = new Configuration("org.apache");
         final ConfigurationDescription cd = new ConfigurationDescription();
         cfg.getProperties().put(Constants.SERVICE_RANKING, 5); 
+        final PropertyDescription prop = new PropertyDescription();
+        cd.getPropertyDescriptions().put("a", prop);
 
         ConfigurationValidationResult result = validator.validate(cfg, cd, null);
         assertTrue(result.isValid());
@@ -82,6 +87,8 @@
     @Test public void testAllowedProperties() {
         final Configuration cfg = new Configuration("org.apache");
         final ConfigurationDescription cd = new ConfigurationDescription();
+        final PropertyDescription prop = new PropertyDescription();
+        cd.getPropertyDescriptions().put("a", prop);
         cfg.getProperties().put(Constants.SERVICE_DESCRIPTION, "desc");
         cfg.getProperties().put(Constants.SERVICE_VENDOR, "vendor");
 
@@ -133,4 +140,124 @@
         assertTrue(result.getPropertyResults().get("a").isValid());
         assertFalse(result.getPropertyResults().get("b").isValid());
     }
+
+    @Test public void testInternalConfiguration() {
+        // internal -> valid
+        final Configuration cfg = new Configuration("org.apache");
+        cfg.getProperties().put("a", "desc");
+        cfg.getProperties().put("b", "vendor");
+
+        // description without properties -> internal
+        final ConfigurationDescription cd = new ConfigurationDescription();
+
+        ConfigurationValidationResult result = validator.validate(cfg, cd, Region.INTERNAL);
+        assertTrue(result.isValid());
+        assertFalse(result.isUseDefaultValue());
+        assertTrue(result.getWarnings().isEmpty());
+        assertEquals(2, cfg.getProperties().size());
+
+        // global -> invalid
+        result = validator.validate(cfg, cd, Region.GLOBAL);
+        assertFalse(result.isValid());
+        assertFalse(result.isUseDefaultValue());
+        assertEquals(1, result.getErrors().size());
+        assertTrue(result.getPropertyResults().isEmpty());
+        assertTrue(result.getWarnings().isEmpty());
+
+        // global -> invalid, but mode DEFINITIVE
+        cd.setMode(Mode.DEFINITIVE);
+        result = validator.validate(cfg, cd, Region.GLOBAL);
+        assertTrue(result.isValid());
+        assertTrue(result.isUseDefaultValue());
+        assertEquals(1, result.getWarnings().size());
+        assertTrue(result.getPropertyResults().isEmpty());
+        assertTrue(result.getErrors().isEmpty());
+
+        // global -> invalid, but mode LENIENT
+        cd.setMode(Mode.LENIENT);
+        result = validator.validate(cfg, cd, Region.GLOBAL);
+        assertTrue(result.isValid());
+        assertFalse(result.isUseDefaultValue());
+        assertEquals(1, result.getWarnings().size());
+        assertTrue(result.getPropertyResults().isEmpty());
+        assertTrue(result.getErrors().isEmpty());
+
+        // global -> invalid, but mode SILENT
+        cd.setMode(Mode.SILENT);
+        result = validator.validate(cfg, cd, Region.GLOBAL);
+        assertTrue(result.isValid());
+        assertFalse(result.isUseDefaultValue());
+        assertTrue(result.getWarnings().isEmpty());
+        assertTrue(result.getPropertyResults().isEmpty());
+        assertTrue(result.getErrors().isEmpty());
+
+        // global -> invalid, but mode SILENT
+        cd.setMode(Mode.SILENT_DEFINITIVE);
+        result = validator.validate(cfg, cd, Region.GLOBAL);
+        assertTrue(result.isValid());
+        assertTrue(result.isUseDefaultValue());
+        assertTrue(result.getWarnings().isEmpty());
+        assertTrue(result.getPropertyResults().isEmpty());
+        assertTrue(result.getErrors().isEmpty());
+    }
+
+    @Test public void testInternalFactoryConfiguration() {
+        // internal -> valid
+        final Configuration cfg = new Configuration("org.apache~foo");
+        cfg.getProperties().put("a", "desc");
+        cfg.getProperties().put("b", "vendor");
+
+        // description without properties -> internal
+        final FactoryConfigurationDescription cd = new FactoryConfigurationDescription();
+
+        ConfigurationValidationResult result = validator.validate(cfg, cd, Region.INTERNAL);
+        assertTrue(result.isValid());
+        assertFalse(result.isUseDefaultValue());
+        assertTrue(result.getWarnings().isEmpty());
+        assertEquals(2, cfg.getProperties().size());
+
+        // global -> invalid
+        result = validator.validate(cfg, cd, Region.GLOBAL);
+        assertFalse(result.isValid());
+        assertFalse(result.isUseDefaultValue());
+        assertEquals(1, result.getErrors().size());
+        assertTrue(result.getPropertyResults().isEmpty());
+        assertTrue(result.getWarnings().isEmpty());
+
+        // global -> invalid, but mode DEFINITIVE
+        cd.setMode(Mode.DEFINITIVE);
+        result = validator.validate(cfg, cd, Region.GLOBAL);
+        assertTrue(result.isValid());
+        assertTrue(result.isUseDefaultValue());
+        assertEquals(1, result.getWarnings().size());
+        assertTrue(result.getPropertyResults().isEmpty());
+        assertTrue(result.getErrors().isEmpty());
+
+        // global -> invalid, but mode LENIENT
+        cd.setMode(Mode.LENIENT);
+        result = validator.validate(cfg, cd, Region.GLOBAL);
+        assertTrue(result.isValid());
+        assertFalse(result.isUseDefaultValue());
+        assertEquals(1, result.getWarnings().size());
+        assertTrue(result.getPropertyResults().isEmpty());
+        assertTrue(result.getErrors().isEmpty());
+
+        // global -> invalid, but mode SILENT
+        cd.setMode(Mode.SILENT);
+        result = validator.validate(cfg, cd, Region.GLOBAL);
+        assertTrue(result.isValid());
+        assertFalse(result.isUseDefaultValue());
+        assertTrue(result.getWarnings().isEmpty());
+        assertTrue(result.getPropertyResults().isEmpty());
+        assertTrue(result.getErrors().isEmpty());
+
+        // global -> invalid, but mode SILENT
+        cd.setMode(Mode.SILENT_DEFINITIVE);
+        result = validator.validate(cfg, cd, Region.GLOBAL);
+        assertTrue(result.isValid());
+        assertTrue(result.isUseDefaultValue());
+        assertTrue(result.getWarnings().isEmpty());
+        assertTrue(result.getPropertyResults().isEmpty());
+        assertTrue(result.getErrors().isEmpty());
+    }
 }
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidatorTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidatorTest.java
index 20330cc..09bc337 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidatorTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidatorTest.java
@@ -794,4 +794,146 @@
             assertEquals("hi", f.getFrameworkProperties().get("c"));
         }
     }
+
+    @Test public void testInternalConfigurationNoPropertyDescriptions() {
+        Feature f1 = createFeature("g:a:1");
+        ConfigurationApi api = createApi();
+        // no property descriptions -> internal
+        api.getConfigurationDescriptions().get(PID).getPropertyDescriptions().clear();        
+        api.setRegion(Region.INTERNAL);
+
+        // internal -> valid
+        FeatureValidationResult result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(1, f1.getConfigurations().getConfiguration(PID).getConfigurationProperties().size());
+
+        // global -> invalid
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getConfigurationDescriptions().get(PID).getPropertyDescriptions().clear();        
+        api.setRegion(Region.GLOBAL);
+        result = validator.validate(f1, api);
+        assertFalse(result.isValid());
+
+        // global -> invalid, but mode DEFINITIVE
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getConfigurationDescriptions().get(PID).getPropertyDescriptions().clear();        
+        api.setRegion(Region.GLOBAL);
+        api.setMode(Mode.DEFINITIVE);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(0, f1.getConfigurations().getConfiguration(PID).getConfigurationProperties().size());
+
+        // global -> invalid, but mode LENIENT
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getConfigurationDescriptions().get(PID).getPropertyDescriptions().clear();        
+        api.setRegion(Region.GLOBAL);
+        api.setMode(Mode.LENIENT);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(1, f1.getConfigurations().getConfiguration(PID).getConfigurationProperties().size());
+
+        // global -> invalid, but mode SILENT
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getConfigurationDescriptions().get(PID).getPropertyDescriptions().clear();        
+        api.setRegion(Region.GLOBAL);
+        api.setMode(Mode.SILENT);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(1, f1.getConfigurations().getConfiguration(PID).getConfigurationProperties().size());
+
+        // global -> invalid, but mode SILENT_DEFINITIVE
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getConfigurationDescriptions().get(PID).getPropertyDescriptions().clear();        
+        api.setRegion(Region.GLOBAL);
+        api.setMode(Mode.SILENT_DEFINITIVE);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(0, f1.getConfigurations().getConfiguration(PID).getConfigurationProperties().size());
+    }
+
+    @Test public void testInternalFactoryConfigurationNoPropertyDescriptions() {
+        Feature f1 = createFeature("g:a:1");
+        ConfigurationApi api = createApi();
+        // no property descriptions -> internal
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getPropertyDescriptions().clear();        
+        api.setRegion(Region.INTERNAL);
+
+        // internal -> valid
+        FeatureValidationResult result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(1, f1.getConfigurations().getConfiguration(FACTORY_PID.concat("~print")).getConfigurationProperties().size());
+
+        // global -> invalid
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getPropertyDescriptions().clear();        
+        api.setRegion(Region.GLOBAL);
+        result = validator.validate(f1, api);
+        assertFalse(result.isValid());
+
+        // global -> invalid, but mode DEFINITIVE
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getPropertyDescriptions().clear();        
+        api.setRegion(Region.GLOBAL);
+        api.setMode(Mode.DEFINITIVE);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(0, f1.getConfigurations().getConfiguration(FACTORY_PID.concat("~print")).getConfigurationProperties().size());
+
+        // global -> invalid, but mode LENIENT
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getPropertyDescriptions().clear();        
+        api.setRegion(Region.GLOBAL);
+        api.setMode(Mode.LENIENT);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(1, f1.getConfigurations().getConfiguration(FACTORY_PID.concat("~print")).getConfigurationProperties().size());
+
+        // global -> invalid, but mode SILENT
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getPropertyDescriptions().clear();        
+        api.setRegion(Region.GLOBAL);
+        api.setMode(Mode.SILENT);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(1, f1.getConfigurations().getConfiguration(FACTORY_PID.concat("~print")).getConfigurationProperties().size());
+
+        // global -> invalid, but mode SILENT_DEFINITIVE
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getPropertyDescriptions().clear();        
+        api.setRegion(Region.GLOBAL);
+        api.setMode(Mode.SILENT_DEFINITIVE);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(0, f1.getConfigurations().getConfiguration(FACTORY_PID.concat("~print")).getConfigurationProperties().size());
+    }
 }