SLING-10494 : Configuration API should have a way to allow empty configurations
diff --git a/docs/api-regions.md b/docs/api-regions.md
index 4e0f18a..c8324f0 100644
--- a/docs/api-regions.md
+++ b/docs/api-regions.md
@@ -189,7 +189,10 @@
 * `description` : A human readable description
 * `properties` : An object containing all properties that are allowed to be configured
 * `deprecated` : If this configuration should not be used anymore a human readable message.
-* `mode` : Validation mode for the configuration overriding the global one. This mode applies to all properties. 
+* `mode` : Validation mode for the configuration overriding the global one. This mode applies to all properties.
+* `region` : Optional property to restrict the configuration to the internal region if this is set to "INTERNAL". With this set, configurations for the internal region can be validated.
+* `allow-additional-properties` : Optional property. If set to true, additional properties not listed in the description are allowed.
+* `internal-property-names` : Specify property names which are internal.
 
 ``` json
 "configuration-api:JSON" : {
@@ -338,6 +341,8 @@
 }
 ```
 
+Note, if you specify `allow-additional-properties` to be true for configurations without properties, the configurations are not marked as internal anymore and can be used, for example as a marker configuration.
+
 ### OSGi Configuration Regions
 
 Two regions are supported for OSGi configurations, internal and global. Without any further information, a feature is considered to be part of the global configuration region. If a feature wants to be part of the internal region it can specify this in the extension:
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntity.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntity.java
index 8a0b984..43617f9 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntity.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntity.java
@@ -38,9 +38,17 @@
 	private final Map<String, JsonValue> attributes = new LinkedHashMap<>();
 
     /**
+     * Apply the non-null default values.
+     */
+    void setDefaults() {
+        // nothing to do
+    }
+
+        /**
      * Clear the object and reset to defaults
      */
 	public void clear() {
+        this.setDefaults();
 		this.attributes.clear();
 	}
 	
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntity.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntity.java
index 765ba62..4e3b1f3 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntity.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntity.java
@@ -17,9 +17,12 @@
 package org.apache.sling.feature.extension.apiregions.api.config;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 import javax.json.Json;
+import javax.json.JsonArrayBuilder;
 import javax.json.JsonException;
 import javax.json.JsonObject;
 import javax.json.JsonObjectBuilder;
@@ -43,6 +46,30 @@
      */
     private Mode mode;
     
+    /** 
+     * Allow additional properties
+     * @since 1.4
+     */
+    private boolean allowAdditional;
+    
+    /** 
+     * The region
+     * @since 1.4
+     */
+    private Region region;
+
+    /** 
+     * 
+     * @since 1.4
+     */
+    private final List<String> internalProperties = new ArrayList<>();
+
+    void setDefaults() {
+        super.setDefaults();
+        this.setAllowAdditionalProperties(false);
+        this.setRegion(Region.GLOBAL);
+    }
+
     /**
      * Clear the object and reset to defaults
      */
@@ -51,6 +78,7 @@
         super.clear();
 		this.properties.clear();
         this.setMode(null);
+        this.getInternalPropertyNames().clear();
     }
 
 	/**
@@ -64,7 +92,7 @@
 	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
         super.fromJSONObject(jsonObj);
         try {
-            final JsonValue val = this.getAttributes().remove(InternalConstants.KEY_PROPERTIES);
+            JsonValue val = this.getAttributes().remove(InternalConstants.KEY_PROPERTIES);
             if ( val != null ) {
                 for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
 					final PropertyDescription prop = new PropertyDescription();
@@ -78,6 +106,17 @@
 			if ( modeVal != null ) {
                 this.setMode(Mode.valueOf(modeVal.toUpperCase()));				
 			}
+            final String regionVal = this.getString(InternalConstants.KEY_REGION);
+            if ( regionVal != null ) {
+                this.setRegion(Region.valueOf(regionVal.toUpperCase()));				
+            }
+			this.setAllowAdditionalProperties(this.getBoolean(InternalConstants.KEY_ALLOW_ADDITIONAL_PROPERTIES, this.isAllowAdditionalProperties()));
+            val = this.getAttributes().remove(InternalConstants.KEY_INTERNAL_PROPERTIES);
+            if ( val != null ) {
+                for(final JsonValue v : val.asJsonArray()) {
+                    this.getInternalPropertyNames().add(getString(v));
+                }
+            }
         } catch (final JsonException | IllegalArgumentException e) {
             throw new IOException(e);
         }
@@ -110,6 +149,51 @@
     }
 
     /**
+     * Are additional properties allowed?
+     * @return {@code true} if additional properties are allowed
+     * @since 1.4
+     */
+    public boolean isAllowAdditionalProperties() {
+        return allowAdditional;
+    }
+
+    /**
+     * Set whether additional properties are allowed
+     * @param flag Set to {@code true} to allow additional properties
+     * @since 1.4
+     */
+    public void setAllowAdditionalProperties(final boolean flag) {
+        this.allowAdditional = flag;
+    }
+
+    /**
+     * Which region does this entity apply to?
+     * @return the region
+     * @since 1.4
+     */
+    public Region getRegion() {
+        return this.region;
+    }
+
+    /**
+     * Set the region of this entity.
+     * @param region The region
+     * @since 1.4
+     */
+    public void setRegion(final Region region) {
+        this.region = region == null ? Region.GLOBAL : region;
+    }
+
+    /**
+     * Get the list of internal property names.
+     * @return the mutable list of internal property names
+     * @since 1.4
+     */
+    public List<String> getInternalPropertyNames() {
+        return internalProperties;
+    }
+
+    /**
      * Convert this object into JSON
      *
      * @return The json object builder
@@ -129,7 +213,20 @@
         if ( this.getMode() != null ) {
             objBuilder.add(InternalConstants.KEY_MODE, this.getMode().name());
         }
-
+        if ( this.getRegion() != Region.GLOBAL ) {
+            objBuilder.add(InternalConstants.KEY_REGION, this.getRegion().name());
+        }
+		if ( this.isAllowAdditionalProperties() ) {
+			objBuilder.add(InternalConstants.KEY_ALLOW_ADDITIONAL_PROPERTIES, this.isAllowAdditionalProperties());
+		}
+        if ( !this.getInternalPropertyNames().isEmpty() ) {
+            final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+            for(final String name : this.getInternalPropertyNames()) {
+                arrayBuilder.add(name);
+            }
+            objBuilder.add(InternalConstants.KEY_INTERNAL_PROPERTIES, arrayBuilder);
+        }
+ 
 		return objBuilder;
    }
 }
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 1152260..5108580 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
@@ -137,7 +137,16 @@
      * The default validation mode. 
      * @since 1.2
      */
-    private Mode mode = Mode.STRICT;
+    private Mode mode;
+    
+    public ConfigurationApi() {
+        this.setDefaults();
+    }
+    
+    void setDefaults() {
+        super.setDefaults();
+        this.setMode(Mode.STRICT);
+    }
     
     /**
      * Clear the object and reset to defaults
@@ -153,7 +162,6 @@
         this.internalFrameworkProperties.clear();
         this.setRegion(null);
         this.getFeatureToRegionCache().clear();
-        this.setMode(Mode.STRICT);
     }
 
 	/**
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationDescription.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationDescription.java
index 03d3fae..c75be8f 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationDescription.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationDescription.java
@@ -23,4 +23,8 @@
 public class ConfigurationDescription extends ConfigurableEntity {
 
     // marker class to distinguish between different configurable entities
+
+    public ConfigurationDescription() {
+        this.setDefaults();
+    }
 }
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescription.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescription.java
index 6a36cf9..71c5606 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescription.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescription.java
@@ -44,6 +44,7 @@
     }
 
     void setDefaults() {
+        super.setDefaults();
         this.getOperations().add(Operation.CREATE);
         this.getOperations().add(Operation.UPDATE);
     }
@@ -54,7 +55,6 @@
     @Override
     public void clear() {
         super.clear();
-        this.setDefaults();
         this.internalNames.clear();
     }
 
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/InternalConstants.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/InternalConstants.java
index e1e4979..7fe12df 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/InternalConstants.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/InternalConstants.java
@@ -80,4 +80,8 @@
     static final String KEY_PLACEHOLDER_POLICY = "placeholder-policy";
 
     static final String KEY_PLACEHOLDER_REGEX = "placeholder-regex";
+
+    static final String KEY_ALLOW_ADDITIONAL_PROPERTIES = "allow-additional-properties";
+
+    static final String KEY_INTERNAL_PROPERTIES = "internal-property-names";
 }
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescription.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescription.java
index 1441e7c..367e118 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescription.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescription.java
@@ -95,6 +95,7 @@
     }
 
     void setDefaults() {
+        super.setDefaults();
 		this.setType(PropertyType.STRING);
         this.setCardinality(1);
         this.setRequired(false);
@@ -107,7 +108,6 @@
     @Override
 	public void clear() {
         super.clear();
-        this.setDefaults();
 		this.setVariable(null);
 		this.setRange(null);
 		this.setIncludes(null);
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 b8a44cf..ac5f0b1 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.3.0")
+@org.osgi.annotation.versioning.Version("1.4.0")
 package org.apache.sling.feature.extension.apiregions.api.config;
 
 
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 5dd16c0..ad47da4 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
@@ -89,8 +89,10 @@
      * @return The result
      * @since 1.2
      */
-    public ConfigurationValidationResult validate(final Configuration config, final ConfigurableEntity desc, 
-            final Region region, final Mode mode) {
+    public ConfigurationValidationResult validate(final Configuration config,
+            final ConfigurableEntity desc, 
+            final Region region,
+            final Mode mode) {
         final Mode validationMode = desc.getMode() != null ? desc.getMode() : (mode != null ? mode : Mode.STRICT);
         
         final ConfigurationValidationResult result = new ConfigurationValidationResult();
@@ -99,9 +101,15 @@
                 result.getErrors().add("Factory configuration cannot be validated against non factory configuration description");
             } else {
                 if ( desc.getPropertyDescriptions().isEmpty()) {
-                    setResult(result, region, validationMode, "Factory configuration is not allowed");
+                    if ( region == Region.GLOBAL && !desc.isAllowAdditionalProperties() ) {
+                        setResult(result, validationMode, "Factory configuration is not allowed");
+                    }
                 } else {
-                    validateProperties(config, desc, result.getPropertyResults(), region, validationMode);
+                    if ( region == Region.GLOBAL && desc.getRegion() == Region.INTERNAL ) {
+                        setResult(result, validationMode, "Factory configuration is not allowed");
+                    } else {
+                        validateProperties(config, desc, result.getPropertyResults(), region, validationMode);
+                    }
                 }
             }
         } else {
@@ -109,9 +117,15 @@
                 result.getErrors().add("Configuration cannot be validated against factory configuration description");
             } else {
                 if ( desc.getPropertyDescriptions().isEmpty()) {
-                    setResult(result, region, validationMode, "Configuration is not allowed");
+                    if ( region == Region.GLOBAL && !desc.isAllowAdditionalProperties() ) {
+                        setResult(result, validationMode, "Configuration is not allowed");
+                    }
                 } else {
-                    validateProperties(config, desc, result.getPropertyResults(), region, validationMode);
+                    if ( region == Region.GLOBAL && desc.getRegion() == Region.INTERNAL ) {
+                        setResult(result, validationMode, "Configuration is not allowed");
+                    } else {
+                        validateProperties(config, desc, result.getPropertyResults(), region, validationMode);
+                    }
                 }
             }
         }
@@ -128,6 +142,7 @@
      * @param desc The configuration description
      * @param results The map of results per property
      * @param region The configuration region
+     * @param mode The validation mode.
      */
     void validateProperties(final Configuration configuration,
             final ConfigurableEntity desc,  
@@ -135,12 +150,14 @@
             final Region region,
             final Mode mode) {
         final Dictionary<String, Object> properties = configuration.getConfigurationProperties();
+
         // validate the described properties
         for(final Map.Entry<String, PropertyDescription> propEntry : desc.getPropertyDescriptions().entrySet()) {
             final Object value = properties.get(propEntry.getKey());
             final PropertyValidationResult result = propertyValidator.validate(value, propEntry.getValue(), mode);
             results.put(propEntry.getKey(), result);
         }
+
         // validate additional properties
         final Enumeration<String> keyEnum = properties.keys();
         while ( keyEnum.hasMoreElements() ) {
@@ -148,28 +165,31 @@
             if ( !desc.getPropertyDescriptions().containsKey(propName) ) {
                 final PropertyValidationResult result = new PropertyValidationResult();
                 results.put(propName, result);
-                if ( Constants.SERVICE_RANKING.equalsIgnoreCase(propName) ) {
+
+                if ( desc.getInternalPropertyNames().contains(propName ) ) {
+                    if  ( region != Region.INTERNAL ) {
+                        result.getErrors().add("Property is not allowed");
+                    }
+                } else if ( Constants.SERVICE_RANKING.equalsIgnoreCase(propName) ) {
                     final Object value = properties.get(propName);
                     if ( !(value instanceof Integer) ) {
                         setResult(result, mode, "service.ranking must be of type Integer");
                     }    
-                } else if ( !isAllowedProperty(propName) && region != Region.INTERNAL ) {
+                } else if ( !isAllowedProperty(propName) && region != Region.INTERNAL && !desc.isAllowAdditionalProperties() ) {
                     result.getErrors().add("Property is not allowed");
                 }
             }
         }
     }
 
-    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 ConfigurationValidationResult result, final Mode validationMode, final String msg) {
+        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);
         }
     }
 
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 0c81064..8e9b68a 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.4.0")
+@org.osgi.annotation.versioning.Version("1.5.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/ConfigurableEntityTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntityTest.java
index b9ea7d7..90115b7 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntityTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntityTest.java
@@ -17,6 +17,7 @@
 package org.apache.sling.feature.extension.apiregions.api.config;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -34,6 +35,10 @@
 
     public static class CE extends ConfigurableEntity {
         // ConfigurableEntity is abstract, therefore subclassing for testing
+
+        public CE() {
+            setDefaults();
+        }
     }
 
     @Test public void testClear() {
@@ -51,6 +56,9 @@
         assertNull(entity.getDescription());
         assertTrue(entity.getPropertyDescriptions().isEmpty());
         assertNull(entity.getMode());
+        assertTrue(entity.getInternalPropertyNames().isEmpty());
+        assertFalse(entity.isAllowAdditionalProperties());
+        assertEquals(Region.GLOBAL, entity.getRegion());
     }
 
     @Test public void testFromJSONObject() throws IOException {
@@ -96,4 +104,46 @@
         entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
         assertEquals(Mode.SILENT, entity.getMode());
     }
+
+    @Test public void testSerialisingRegion() throws IOException {
+        final CE entity = new CE();
+        entity.setRegion(Region.INTERNAL);
+
+        final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"region\" : \"INTERNAL\"}");
+
+        assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());
+        entity.clear();
+        entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
+        assertEquals(Region.INTERNAL, entity.getRegion());
+    }
+
+    @Test public void testSerialisingAllowAdditionalProperties() throws IOException {
+        final CE entity = new CE();
+        entity.setAllowAdditionalProperties(true);
+
+        final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"allow-additional-properties\" : true}");
+
+        assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());
+        entity.clear();
+        entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
+        assertTrue(entity.isAllowAdditionalProperties());
+    }
+
+    @Test public void testSerialisingInternalProperties() throws IOException {
+        final CE entity = new CE();
+        entity.getInternalPropertyNames().add("a");
+        entity.getInternalPropertyNames().add("b");
+
+        final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"internal-property-names\" : [\"a\",\"b\"]}");
+
+        assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());
+        entity.clear();
+        entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
+        assertEquals(2, entity.getInternalPropertyNames().size());
+        assertTrue(entity.getInternalPropertyNames().contains("a"));
+        assertTrue(entity.getInternalPropertyNames().contains("b"));
+    }
 }
\ No newline at end of file
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 483d67f..826c255 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
@@ -937,6 +937,164 @@
         assertEquals(0, f1.getConfigurations().getConfiguration(FACTORY_PID.concat("~print")).getConfigurationProperties().size());
     }
 
+    @Test public void testInternalConfigurationNoPropertyDescriptionsButAllowAdditional() {
+        Feature f1 = createFeature("g:a:1");
+        ConfigurationApi api = createApi();
+        // no property descriptions -> internal
+        api.getConfigurationDescriptions().get(PID).getPropertyDescriptions().clear();        
+        api.getConfigurationDescriptions().get(PID).setAllowAdditionalProperties(true);        
+        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 -> valid
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getConfigurationDescriptions().get(PID).getPropertyDescriptions().clear();        
+        api.getConfigurationDescriptions().get(PID).setAllowAdditionalProperties(true);        
+        api.setRegion(Region.GLOBAL);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(1, f1.getConfigurations().getConfiguration(PID).getConfigurationProperties().size());
+
+        // global -> valid, but mode DEFINITIVE
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getConfigurationDescriptions().get(PID).getPropertyDescriptions().clear();        
+        api.getConfigurationDescriptions().get(PID).setAllowAdditionalProperties(true);        
+        api.setRegion(Region.GLOBAL);
+        api.setMode(Mode.DEFINITIVE);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(1, f1.getConfigurations().getConfiguration(PID).getConfigurationProperties().size());
+
+        // global -> valid, but mode LENIENT
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getConfigurationDescriptions().get(PID).getPropertyDescriptions().clear();        
+        api.getConfigurationDescriptions().get(PID).setAllowAdditionalProperties(true);        
+        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 -> valid, but mode SILENT
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getConfigurationDescriptions().get(PID).getPropertyDescriptions().clear();        
+        api.getConfigurationDescriptions().get(PID).setAllowAdditionalProperties(true);        
+        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 -> valid, but mode SILENT_DEFINITIVE
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getConfigurationDescriptions().get(PID).getPropertyDescriptions().clear();        
+        api.getConfigurationDescriptions().get(PID).setAllowAdditionalProperties(true);        
+        api.setRegion(Region.GLOBAL);
+        api.setMode(Mode.SILENT_DEFINITIVE);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(1, f1.getConfigurations().getConfiguration(PID).getConfigurationProperties().size());
+    }
+
+    @Test public void testInternalFactoryConfigurationNoPropertyDescriptionsButAllowAdditional() {
+        Feature f1 = createFeature("g:a:1");
+        ConfigurationApi api = createApi();
+        // no property descriptions -> internal
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getPropertyDescriptions().clear();        
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).setAllowAdditionalProperties(true);        
+        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.getFactoryConfigurationDescriptions().get(FACTORY_PID).setAllowAdditionalProperties(true);        
+        api.setRegion(Region.GLOBAL);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(1, f1.getConfigurations().getConfiguration(FACTORY_PID.concat("~print")).getConfigurationProperties().size());
+
+        // global -> valid, but mode DEFINITIVE
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getPropertyDescriptions().clear();        
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).setAllowAdditionalProperties(true);        
+        api.setRegion(Region.GLOBAL);
+        api.setMode(Mode.DEFINITIVE);
+        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 LENIENT
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getPropertyDescriptions().clear();        
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).setAllowAdditionalProperties(true);        
+        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 -> valid, but mode SILENT
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getPropertyDescriptions().clear();        
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).setAllowAdditionalProperties(true);        
+        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 -> valid, but mode SILENT_DEFINITIVE
+        f1 = createFeature("g:a:1");
+        api = createApi();
+        // no property descriptions -> internal
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getPropertyDescriptions().clear();        
+        api.getFactoryConfigurationDescriptions().get(FACTORY_PID).setAllowAdditionalProperties(true);        
+        api.setRegion(Region.GLOBAL);
+        api.setMode(Mode.SILENT_DEFINITIVE);
+        result = validator.validate(f1, api);
+        assertTrue(result.isValid());
+        validator.applyDefaultValues(f1, result);
+        assertEquals(1, f1.getConfigurations().getConfiguration(FACTORY_PID.concat("~print")).getConfigurationProperties().size());
+    }
+
     @Test
     public void testLiveValidation() {
         assertFalse(validator.isLiveValues());
@@ -960,4 +1118,40 @@
             validator.setLiveValues(false);
         }
     }
+
+    @Test
+    public void testInternalPropertyNames() {
+        final Feature feature = createFeature("g:a:1");
+        feature.getConfigurations().getConfiguration(PID).getProperties().put("xyz", true);
+
+        final ConfigurationApi api = createApi();
+        api.getConfigurationDescriptions().get(PID).getInternalPropertyNames().add("xyz");
+
+        // validate global region
+        FeatureValidationResult result = validator.validate(feature, api);
+        assertFalse(result.isValid());
+
+        // validate internal region
+        api.setRegion(Region.INTERNAL);
+        result = validator.validate(feature, api);
+        assertTrue(result.isValid());       
+    }
+
+    @Test
+    public void testAllowAdditional() {
+        final Feature feature = createFeature("g:a:1");
+        feature.getConfigurations().getConfiguration(PID).getProperties().put("xyz", true);
+
+        final ConfigurationApi api = createApi();
+        api.getConfigurationDescriptions().get(PID).setAllowAdditionalProperties(true);
+
+        // validate global region
+        FeatureValidationResult result = validator.validate(feature, api);
+        assertTrue(result.isValid());       
+
+        // validate internal region
+        api.setRegion(Region.INTERNAL);
+        result = validator.validate(feature, api);
+        assertTrue(result.isValid());       
+    }
 }