SLING-11447 : Allow to extend configuration descriptions
diff --git a/docs/api-regions.md b/docs/api-regions.md
index 58c4841..02979aa 100644
--- a/docs/api-regions.md
+++ b/docs/api-regions.md
@@ -366,6 +366,29 @@
 
 When two features are aggregated, the resulting feature is only in the internal region if both source features are in the internal region. Otherwise, the resulting aggregate is always in the global region.
 
+### Configuration Additions
+
+If two features are merged, only one feature is allowed to have a configuration description for a given PID or factory PID. To enable use cases where a feature wants to enhance an existing description, additions can be specified in the configuration API. It is allowed to add additional include values and for factory confgirations additional internal configuration names.
+
+``` json
+"configuration-api:JSON|optional" : {
+  "configuration-additions" : {
+    "org.apache.sling.engine.RequestHandling" : {
+      "properties":{
+        "someprop" : {
+          "includes" : ["additional-include"]
+        }
+      }
+    }
+  },
+  "factory-configuration-additions" : {
+    "org.apache.sling.engine.impl.InternalLogger" : {
+      "internal-names" : ["additional-name]"
+    }
+  }
+}
+```
+
 ## Artifact Rules
 
 The artifact rules extension allows to specify version rules for bundles and artifacts. For an artifact identity allowed and denied version ranges can be specified. A version range follows the OSGi version range syntax. If no ranges are specified, the artifact is not allowed. An artifact version must match at least one allowed version range and must not match any denied version range (if specified).
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/ConfigurationApiMergeHandler.java b/src/main/java/org/apache/sling/feature/extension/apiregions/ConfigurationApiMergeHandler.java
index 45bf7a6..37bd902 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/ConfigurationApiMergeHandler.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/ConfigurationApiMergeHandler.java
@@ -16,16 +16,25 @@
  */
 package org.apache.sling.feature.extension.apiregions;
 
+import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.builder.HandlerContext;
 import org.apache.sling.feature.builder.MergeHandler;
+import org.apache.sling.feature.extension.apiregions.api.config.ConfigurableEntity;
+import org.apache.sling.feature.extension.apiregions.api.config.ConfigurableEntityAddition;
 import org.apache.sling.feature.extension.apiregions.api.config.ConfigurationApi;
 import org.apache.sling.feature.extension.apiregions.api.config.ConfigurationDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.ConfigurationDescriptionAddition;
 import org.apache.sling.feature.extension.apiregions.api.config.FactoryConfigurationDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.FactoryConfigurationDescriptionAddition;
 import org.apache.sling.feature.extension.apiregions.api.config.FrameworkPropertyDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.PropertyDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.PropertyDescriptionAddition;
 import org.apache.sling.feature.extension.apiregions.api.config.Region;
 
 /**
@@ -103,8 +112,87 @@
                 targetApi.getFeatureToRegionCache().put(sourceFeature.getId(), sourceApi.detectRegion());
                 targetApi.getFeatureToRegionCache().putAll(sourceApi.getFeatureToRegionCache());
             }
-            
+
+            // check for additions
+            processAdditions(targetApi, targetApi, false);
+            processAdditions(targetApi, sourceApi, true);
+
             ConfigurationApi.setConfigurationApi(targetFeature, targetApi);
         }
     }
+
+    /**
+     * Process/add the additions from another configuration api
+     */
+    private void processAdditions(final ConfigurationApi targetApi, final ConfigurationApi sourceApi, final boolean keep) {
+        // configuration additions
+        final Iterator<Map.Entry<String, ConfigurationDescriptionAddition>> itc = sourceApi.getConfigurationDescriptionAdditions().entrySet().iterator();
+        while ( itc.hasNext() ) {
+            final Map.Entry<String, ConfigurationDescriptionAddition> entry = itc.next();
+            final ConfigurationDescription cd = targetApi.getConfigurationDescriptions().get(entry.getKey());
+            if ( cd == null && keep ) {
+                // if configuration is not found, keep the addition
+                if ( targetApi.getConfigurationDescriptionAdditions().get(entry.getKey()) == null ) {
+                    targetApi.getConfigurationDescriptionAdditions().put(entry.getKey(), entry.getValue());
+                } else {
+                    throw new IllegalStateException("Duplicate configuration description addition " + entry.getKey());
+                }
+            } else if ( cd != null ) {
+                processAddition("configuration ".concat(entry.getKey()), cd, entry.getValue());
+                if ( !keep ) {
+                    itc.remove();
+                }
+            }
+        }
+        // factory configuration additions
+        final Iterator<Map.Entry<String, FactoryConfigurationDescriptionAddition>> itf = sourceApi.getFactoryConfigurationDescriptionAdditions().entrySet().iterator();
+        while ( itf.hasNext() ) {
+            final Map.Entry<String, FactoryConfigurationDescriptionAddition> entry = itf.next();
+            final FactoryConfigurationDescription cd = targetApi.getFactoryConfigurationDescriptions().get(entry.getKey());
+            if ( cd == null && keep ) {
+                // if configuration is not found, keep the addition
+                if ( targetApi.getFactoryConfigurationDescriptionAdditions().get(entry.getKey()) == null ) {
+                    targetApi.getFactoryConfigurationDescriptionAdditions().put(entry.getKey(), entry.getValue());
+                } else {
+                    throw new IllegalStateException("Duplicate factory configuration description addition " + entry.getKey());
+                }
+            } else if ( cd != null ) {
+                processAddition("factory configuration ".concat(entry.getKey()), cd, entry.getValue());
+                if ( !keep ) {
+                    itf.remove();
+                }
+            }
+        }
+    }
+
+    private void processAddition(final String descId,
+            final ConfigurableEntity entity,
+            final ConfigurableEntityAddition addition) {
+        for(final Map.Entry<String, PropertyDescriptionAddition> entry : addition.getPropertyDescriptionAdditions().entrySet()) {
+            final String propName = entry.getKey();
+            final PropertyDescription pd = entity.getPropertyDescriptions().get(propName);
+            if ( pd == null ) {
+                throw new IllegalStateException("Property named '" + propName + "' is not defined in " + descId);
+            }
+            if ( entry.getValue().getIncludes() != null ) {
+                final Set<String> includes = new LinkedHashSet<>();
+                if ( pd.getIncludes() != null ) {
+                    for(final String v : pd.getIncludes()) {
+                        includes.add(v);
+                    }
+                }
+                for(final String v : entry.getValue().getIncludes()) {
+                    includes.add(v);
+                }
+                pd.setIncludes(includes.toArray(new String[includes.size()]));    
+            }
+        }
+        if ( entity instanceof FactoryConfigurationDescription ) {
+            final FactoryConfigurationDescription fcd = (FactoryConfigurationDescription)entity;
+            final FactoryConfigurationDescriptionAddition fcda = (FactoryConfigurationDescriptionAddition)addition;
+            for(final String n : fcda.getInternalNames()) {
+                fcd.getInternalNames().add(n);
+            }
+        }
+    }
 }
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntityAddition.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntityAddition.java
new file mode 100644
index 0000000..a6b849c
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntityAddition.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.extension.apiregions.api.config;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+import org.apache.felix.cm.json.Configurations;
+
+/**
+ * A description of an OSGi configuration addition
+ * This class is not thread safe.
+ * @since 1.8
+ */
+public abstract class ConfigurableEntityAddition extends AttributeableEntity {
+
+    /** The properties */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    private final Map<String, PropertyDescriptionAddition> properties = (Map)Configurations.newConfiguration();
+
+    /**
+     * Create a new addition
+     */
+    public ConfigurableEntityAddition() {
+        this.setDefaults();
+    }
+
+    /**
+     * Get the property additions
+     * @return The map of property additions
+     */
+    public Map<String, PropertyDescriptionAddition> getPropertyDescriptionAdditions() {
+        return properties;
+    }
+
+    /**
+     * Clear the object and reset to defaults
+     */
+    @Override
+	public void clear() {
+        super.clear();
+        this.getPropertyDescriptionAdditions().clear();
+    }
+
+	/**
+	 * Extract the metadata from the JSON object.
+	 * This method first calls {@link #clear()}
+     * 
+	 * @param jsonObj The JSON Object
+	 * @throws IOException If JSON parsing fails
+	 */
+    @Override
+	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+        super.fromJSONObject(jsonObj);
+        try {
+            JsonValue val = this.getAttributes().remove(InternalConstants.KEY_PROPERTIES);
+            if ( val != null ) {
+                for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
+					final PropertyDescriptionAddition prop = new PropertyDescriptionAddition();
+					prop.fromJSONObject(innerEntry.getValue().asJsonObject());
+                    if ( this.getPropertyDescriptionAdditions().put(innerEntry.getKey(), prop) != null )  {
+                        throw new IOException("Duplicate key for property description (keys are case-insensitive) : ".concat(innerEntry.getKey()));
+                    }
+                }
+            }            
+        } catch (final JsonException | IllegalArgumentException e) {
+            throw new IOException(e);
+        }
+	}
+
+    /**
+     * Convert this object into JSON
+     *
+     * @return The json object builder
+     * @throws IOException If generating the JSON fails
+     */
+    @Override
+	protected JsonObjectBuilder createJson() throws IOException {
+		final JsonObjectBuilder objBuilder = super.createJson();
+
+		if ( !this.getPropertyDescriptionAdditions().isEmpty() ) {
+			final JsonObjectBuilder propBuilder = Json.createObjectBuilder();
+			for(final Map.Entry<String, PropertyDescriptionAddition> entry : this.getPropertyDescriptionAdditions().entrySet()) {
+				propBuilder.add(entry.getKey(), entry.getValue().createJson());
+			}
+			objBuilder.add(InternalConstants.KEY_PROPERTIES, propBuilder);
+		}
+ 
+		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 dccf6b4..6626287 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
@@ -115,6 +115,12 @@
     /** The map of factory configurations */
     private final Map<String, FactoryConfigurationDescription> factories = new LinkedHashMap<>();
 
+    /** The map of configuration additions @since 1.8 */
+    private final Map<String, ConfigurationDescriptionAddition> configurationAdditions = new LinkedHashMap<>();
+
+    /** The map of factory configuration additions @since 1.8 */
+    private final Map<String, FactoryConfigurationDescriptionAddition> factoryAdditions = new LinkedHashMap<>();
+
     /** The map of framework properties */
     private final Map<String, FrameworkPropertyDescription> frameworkProperties = new LinkedHashMap<>();
 
@@ -163,6 +169,8 @@
         this.internalFrameworkProperties.clear();
         this.setRegion(null);
         this.getFeatureToRegionCache().clear();
+        this.getConfigurationDescriptionAdditions().clear();
+        this.getFactoryConfigurationDescriptionAdditions().clear();
     }
 
 	/**
@@ -243,6 +251,23 @@
                 this.setMode(Mode.valueOf(modeVal.toUpperCase()));
 			}
 
+            val = this.getAttributes().remove(InternalConstants.KEY_CONFIGURATION_ADDITIONS);
+            if ( val != null ) {
+                for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
+                    final ConfigurationDescriptionAddition cfg = new ConfigurationDescriptionAddition();
+                    cfg.fromJSONObject(innerEntry.getValue().asJsonObject());
+                    this.getConfigurationDescriptionAdditions().put(innerEntry.getKey(), cfg);
+                }
+            }
+
+            val = this.getAttributes().remove(InternalConstants.KEY_FACTORY_ADDITIONS);
+            if ( val != null ) {
+                for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
+                    final FactoryConfigurationDescriptionAddition cfg = new FactoryConfigurationDescriptionAddition();
+                    cfg.fromJSONObject(innerEntry.getValue().asJsonObject());
+                    this.getFactoryConfigurationDescriptionAdditions().put(innerEntry.getKey(), cfg);
+                }
+            }
         } catch (final JsonException | IllegalArgumentException e) {
             throw new IOException(e);
         }
@@ -458,7 +483,39 @@
         if ( this.getMode() != Mode.STRICT ) {
             objBuilder.add(InternalConstants.KEY_MODE, this.getMode().name());
         }
+        if ( !this.getConfigurationDescriptionAdditions().isEmpty() ) {
+            final JsonObjectBuilder propBuilder = Json.createObjectBuilder();
+            for(final Map.Entry<String, ConfigurationDescriptionAddition> entry : this.getConfigurationDescriptionAdditions().entrySet()) {
+                propBuilder.add(entry.getKey(), entry.getValue().createJson());
+            }
+            objBuilder.add(InternalConstants.KEY_CONFIGURATION_ADDITIONS, propBuilder);
+        }
+        if ( !this.getFactoryConfigurationDescriptionAdditions().isEmpty() ) {
+            final JsonObjectBuilder propBuilder = Json.createObjectBuilder();
+            for(final Map.Entry<String, FactoryConfigurationDescriptionAddition> entry : this.getFactoryConfigurationDescriptionAdditions().entrySet()) {
+                propBuilder.add(entry.getKey(), entry.getValue().createJson());
+            }
+            objBuilder.add(InternalConstants.KEY_FACTORY_ADDITIONS, propBuilder);
+        }
 
 		return objBuilder;
     }
+
+    /**
+     * Get the map of configuration description additions
+     * @return The map, might be empty
+     * @since 1.8
+     */
+    public Map<String, ConfigurationDescriptionAddition> getConfigurationDescriptionAdditions() {
+        return this.configurationAdditions;
+    }
+
+    /**
+     * Get the map of factory configuration description additions
+     * @return The map, might be empty
+     * @since 1.8
+     */
+    public Map<String, FactoryConfigurationDescriptionAddition> getFactoryConfigurationDescriptionAdditions() {
+        return this.factoryAdditions;
+    }
 }
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationDescriptionAddition.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationDescriptionAddition.java
new file mode 100644
index 0000000..5343483
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationDescriptionAddition.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.extension.apiregions.api.config;
+
+/**
+ * A description of an OSGi configuration addition
+ * This class is not thread safe.
+ */
+public class ConfigurationDescriptionAddition extends ConfigurableEntityAddition {
+
+    // marker class to distinguish between different configurable entities
+
+    public ConfigurationDescriptionAddition() {
+        this.setDefaults();
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescriptionAddition.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescriptionAddition.java
new file mode 100644
index 0000000..f240a29
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescriptionAddition.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.extension.apiregions.api.config;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+/**
+ * Description of an OSGi factory configuration addition
+ * This class is not thread safe.
+ */
+public class FactoryConfigurationDescriptionAddition extends ConfigurableEntityAddition {
+    
+    private final List<String> internalNames = new ArrayList<>();
+
+    public FactoryConfigurationDescriptionAddition() {
+        this.setDefaults();
+    }
+    
+    /**
+     * Clear the object and set the defaults
+     */
+    @Override
+    public void clear() {
+        super.clear();
+        this.internalNames.clear();
+    }
+
+	/**
+	 * Extract the metadata from the JSON object.
+	 * This method first calls {@link #clear()}
+     *
+	 * @param jsonObj The JSON Object
+	 * @throws IOException If JSON parsing fails
+	 */
+    @Override
+    public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+        super.fromJSONObject(jsonObj);
+        try {
+            JsonValue val;
+            val = this.getAttributes().remove(InternalConstants.KEY_INTERNAL_NAMES);
+            if ( val != null ) {
+                for(final JsonValue innerVal : val.asJsonArray()) {
+                    this.getInternalNames().add(getString(innerVal));
+                }
+            }
+
+		} catch (final JsonException | IllegalArgumentException e) {
+            throw new IOException(e);
+        }
+    }
+
+	/**
+     * Get the internal factory configuration name
+	 * @return Mutable list of internal names
+	 */
+	public List<String> getInternalNames() {
+		return internalNames;
+	}
+
+   /**
+     * Convert this object into JSON
+     *
+     * @return The json object builder
+     * @throws IOException If generating the JSON fails
+     */
+    @Override
+    protected JsonObjectBuilder createJson() throws IOException {
+		final JsonObjectBuilder objBuilder = super.createJson();
+		
+		if ( !this.getInternalNames().isEmpty() ) {
+            final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+            for(final String n : this.getInternalNames()) {
+                arrayBuilder.add(n);
+            }
+			objBuilder.add(InternalConstants.KEY_INTERNAL_NAMES, arrayBuilder);
+		}
+		return objBuilder;
+   }
+}
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 b2b9cf7..959da77 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
@@ -37,6 +37,10 @@
 
     static final String KEY_FACTORIES = "factory-configurations";
     
+    static final String KEY_CONFIGURATION_ADDITIONS = "configuration-additions";
+
+    static final String KEY_FACTORY_ADDITIONS = "factory-configuration-additions";
+
     static final String KEY_FWK_PROPERTIES = "framework-properties";
 
     static final String KEY_INTERNAL_CONFIGURATIONS = "internal-configurations";
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescriptionAddition.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescriptionAddition.java
new file mode 100644
index 0000000..e65d43c
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescriptionAddition.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.extension.apiregions.api.config;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+/**
+ * Instances of this class represent an addition to a configuration property
+ * This class is not thread safe.
+ * @since 1.8
+ */
+public class PropertyDescriptionAddition extends AttributeableEntity {
+
+	/** The required includes for an array/collection (optional) */
+	private String[] includes;
+
+    /**
+     * Create a new description
+     */
+    public PropertyDescriptionAddition() {
+        this.setDefaults();
+    }
+
+    /**
+     * Clear the object and reset to defaults
+     */
+    @Override
+	public void clear() {
+        super.clear();
+		this.setIncludes(null);
+    }
+
+	/**
+	 * Extract the metadata from the JSON object.
+	 * This method first calls {@link #clear()}
+	 * @param jsonObj The JSON Object
+	 * @throws IOException If JSON parsing fails
+	 */
+    @Override
+	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+        super.fromJSONObject(jsonObj);
+        try {
+			final JsonValue incs = this.getAttributes().remove(InternalConstants.KEY_INCLUDES);
+			if ( incs != null ) {
+				final List<String> list = new ArrayList<>();
+				for(final JsonValue innerVal : incs.asJsonArray()) {
+                    list.add(getString(innerVal));
+                }
+                this.setIncludes(list.toArray(new String[list.size()]));
+			}
+ 		} catch (final JsonException | IllegalArgumentException e) {
+            throw new IOException(e);
+        }
+	}
+	
+    /**
+     * Convert this object into JSON
+     *
+     * @return The json object builder
+     * @throws IOException If generating the JSON fails
+     */
+    @Override
+    protected JsonObjectBuilder createJson() throws IOException {
+		final JsonObjectBuilder objectBuilder = super.createJson();
+
+		if ( this.getIncludes() != null && this.getIncludes().length > 0 ) {
+			final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+			for(final String v : this.getIncludes()) {
+				arrayBuilder.add(v);
+			}
+			objectBuilder.add(InternalConstants.KEY_INCLUDES, arrayBuilder);
+		}
+        return objectBuilder;
+	}
+
+	/**
+	 * Get the includes
+	 * @return the includes or {@code null}
+	 */
+	public String[] getIncludes() {
+		return includes;
+	}
+
+	/**
+	 * Set the includes
+	 * @param includes the includes to set
+	 */
+	public void setIncludes(final String[] includes) {
+		this.includes = includes;
+	}
+}
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 1db8dcc..6c6795d 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.7.0")
+@org.osgi.annotation.versioning.Version("1.8.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/FeatureValidator.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidator.java
index 4609b64..9d35533 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
@@ -18,11 +18,9 @@
 
 import java.lang.reflect.Array;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -40,7 +38,6 @@
 import org.apache.sling.feature.extension.apiregions.api.config.Mode;
 import org.apache.sling.feature.extension.apiregions.api.config.Operation;
 import org.apache.sling.feature.extension.apiregions.api.config.Region;
-import org.osgi.util.converter.Converter;
 import org.osgi.util.converter.Converters;
 
 /**
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/ConfigurationApiMergeHandlerTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/ConfigurationApiMergeHandlerTest.java
index 3231fbe..3d15778 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/ConfigurationApiMergeHandlerTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/ConfigurationApiMergeHandlerTest.java
@@ -16,11 +16,17 @@
  */
  package org.apache.sling.feature.extension.apiregions;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import java.util.Arrays;
+
+import javax.json.Json;
+import javax.json.JsonObject;
+
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.Prototype;
@@ -28,8 +34,12 @@
 import org.apache.sling.feature.builder.FeatureBuilder;
 import org.apache.sling.feature.extension.apiregions.api.config.ConfigurationApi;
 import org.apache.sling.feature.extension.apiregions.api.config.ConfigurationDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.ConfigurationDescriptionAddition;
 import org.apache.sling.feature.extension.apiregions.api.config.FactoryConfigurationDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.FactoryConfigurationDescriptionAddition;
 import org.apache.sling.feature.extension.apiregions.api.config.FrameworkPropertyDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.PropertyDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.PropertyDescriptionAddition;
 import org.apache.sling.feature.extension.apiregions.api.config.Region;
 import org.junit.Test;
 
@@ -397,7 +407,6 @@
         apiB.setRegion(Region.GLOBAL);
         ConfigurationApi.setConfigurationApi(featureB, apiB);
 
-
         final Feature featureC = new Feature(ArtifactId.parse("g:c:1"));
         final ConfigurationApi apiC = new ConfigurationApi();
         apiC.setRegion(Region.INTERNAL);
@@ -423,4 +432,244 @@
         assertEquals(Region.INTERNAL, api.getFeatureToRegionCache().get(featureD.getId()));
         assertEquals(Region.GLOBAL, api.getFeatureToRegionCache().get(idIntermediate));
     }
+
+    @Test public void testConfigurationAdditions() {
+        final ConfigurationDescriptionAddition cda1 = new ConfigurationDescriptionAddition();
+        final PropertyDescriptionAddition pda11 = new PropertyDescriptionAddition();
+        pda11.setIncludes(new String[] {"c"});
+        cda1.getPropertyDescriptionAdditions().put("p1", pda11);
+        final PropertyDescriptionAddition pda12 = new PropertyDescriptionAddition();
+        pda12.setIncludes(new String[] {"x"});
+        cda1.getPropertyDescriptionAdditions().put("p2", pda12);
+        final ConfigurationDescriptionAddition cda2 = new ConfigurationDescriptionAddition();
+        final PropertyDescriptionAddition pda21 = new PropertyDescriptionAddition();
+        pda21.setIncludes(new String[] {"d"});
+        cda2.getPropertyDescriptionAdditions().put("p3", pda21);
+        final PropertyDescriptionAddition pda22 = new PropertyDescriptionAddition();
+        pda22.setIncludes(new String[] {"y"});
+        cda2.getPropertyDescriptionAdditions().put("p4", pda22);
+
+        final BuilderContext context = new BuilderContext(id -> null);
+        context.addMergeExtensions(new ConfigurationApiMergeHandler());
+
+        final Feature featureA = new Feature(ArtifactId.parse("g:a:1"));
+        final ConfigurationApi apiA = new ConfigurationApi();
+        apiA.getConfigurationDescriptionAdditions().put("pid1", cda1);
+
+        final ConfigurationDescription cd1 = new ConfigurationDescription();
+        final PropertyDescription pd11 = new PropertyDescription();
+        pd11.setCardinality(-1);
+        pd11.setIncludes(new String[] {"a", "b"});
+        cd1.getPropertyDescriptions().put("p1", pd11);
+
+        final PropertyDescription pd12 = new PropertyDescription();
+        pd12.setCardinality(-1);
+        cd1.getPropertyDescriptions().put("p2", pd12);
+
+        final ConfigurationDescription cd2 = new ConfigurationDescription();
+        final PropertyDescription pd21 = new PropertyDescription();
+        pd21.setCardinality(-1);
+        pd21.setIncludes(new String[] {"a", "b"});
+        cd2.getPropertyDescriptions().put("p3", pd21);
+
+        final PropertyDescription pd22 = new PropertyDescription();
+        pd22.setCardinality(-1);
+        cd2.getPropertyDescriptions().put("p4", pd22);
+
+        apiA.getConfigurationDescriptions().put("pid1", cd1);
+        apiA.getConfigurationDescriptions().put("pid2", cd2);
+        ConfigurationApi.setConfigurationApi(featureA, apiA);
+
+        final Feature featureB = new Feature(ArtifactId.parse("g:b:1"));
+        final ConfigurationApi apiB = new ConfigurationApi();
+        apiB.getConfigurationDescriptionAdditions().put("pid2", cda2);
+        ConfigurationApi.setConfigurationApi(featureB, apiB);
+
+        final ArtifactId id = ArtifactId.parse("g:m:1");
+        final Feature result = FeatureBuilder.assemble(id, context, featureA, featureB);
+
+        final ConfigurationApi resultApi = ConfigurationApi.getConfigurationApi(result);
+        assertTrue(resultApi.getConfigurationDescriptionAdditions().isEmpty());
+        final ConfigurationDescription resultCD1 = resultApi.getConfigurationDescriptions().get("pid1");
+        assertNotNull(resultCD1);
+        final PropertyDescription resultPD11 = resultCD1.getPropertyDescriptions().get("p1");
+        assertNotNull(resultPD11);
+        assertArrayEquals(new String[] {"a", "b", "c"}, resultPD11.getIncludes());
+        final PropertyDescription resultPD12 = resultCD1.getPropertyDescriptions().get("p2");
+        assertNotNull(resultPD12);
+        assertArrayEquals(new String[] {"x"}, resultPD12.getIncludes());
+        final ConfigurationDescription resultCD2 = resultApi.getConfigurationDescriptions().get("pid2");
+        assertNotNull(resultCD2);
+        final PropertyDescription resultPD21 = resultCD2.getPropertyDescriptions().get("p3");
+        assertNotNull(resultPD21);
+        assertArrayEquals(new String[] {"a", "b", "d"}, resultPD21.getIncludes());
+        final PropertyDescription resultPD22 = resultCD2.getPropertyDescriptions().get("p4");
+        assertNotNull(resultPD22);
+        assertArrayEquals(new String[] {"y"}, resultPD22.getIncludes());
+    }
+
+    @Test public void testFactoryConfigurationAdditions() {
+        final FactoryConfigurationDescriptionAddition cda1 = new FactoryConfigurationDescriptionAddition();
+        final PropertyDescriptionAddition pda11 = new PropertyDescriptionAddition();
+        pda11.setIncludes(new String[] {"c"});
+        cda1.getPropertyDescriptionAdditions().put("p1", pda11);
+        cda1.getInternalNames().add("mrx");
+        final PropertyDescriptionAddition pda12 = new PropertyDescriptionAddition();
+        pda12.setIncludes(new String[] {"x"});
+        cda1.getPropertyDescriptionAdditions().put("p2", pda12);
+        final FactoryConfigurationDescriptionAddition cda2 = new FactoryConfigurationDescriptionAddition();
+        final PropertyDescriptionAddition pda21 = new PropertyDescriptionAddition();
+        pda21.setIncludes(new String[] {"d"});
+        cda2.getPropertyDescriptionAdditions().put("p3", pda21);
+        final PropertyDescriptionAddition pda22 = new PropertyDescriptionAddition();
+        pda22.setIncludes(new String[] {"y"});
+        cda2.getPropertyDescriptionAdditions().put("p4", pda22);
+
+        final BuilderContext context = new BuilderContext(id -> null);
+        context.addMergeExtensions(new ConfigurationApiMergeHandler());
+
+        final Feature featureA = new Feature(ArtifactId.parse("g:a:1"));
+        final ConfigurationApi apiA = new ConfigurationApi();
+        apiA.getFactoryConfigurationDescriptionAdditions().put("factory1", cda1);
+
+        final FactoryConfigurationDescription cd1 = new FactoryConfigurationDescription();
+        cd1.getInternalNames().add("i1");
+        final PropertyDescription pd11 = new PropertyDescription();
+        pd11.setCardinality(-1);
+        pd11.setIncludes(new String[] {"a", "b"});
+        cd1.getPropertyDescriptions().put("p1", pd11);
+
+        final PropertyDescription pd12 = new PropertyDescription();
+        pd12.setCardinality(-1);
+        cd1.getPropertyDescriptions().put("p2", pd12);
+
+        final FactoryConfigurationDescription cd2 = new FactoryConfigurationDescription();
+        cd2.getInternalNames().add("i2");
+        final PropertyDescription pd21 = new PropertyDescription();
+        pd21.setCardinality(-1);
+        pd21.setIncludes(new String[] {"a", "b"});
+        cd2.getPropertyDescriptions().put("p3", pd21);
+
+        final PropertyDescription pd22 = new PropertyDescription();
+        pd22.setCardinality(-1);
+        cd2.getPropertyDescriptions().put("p4", pd22);
+
+        apiA.getFactoryConfigurationDescriptions().put("factory1", cd1);
+        apiA.getFactoryConfigurationDescriptions().put("factory2", cd2);
+        ConfigurationApi.setConfigurationApi(featureA, apiA);
+
+        final Feature featureB = new Feature(ArtifactId.parse("g:b:1"));
+        final ConfigurationApi apiB = new ConfigurationApi();
+        apiB.getFactoryConfigurationDescriptionAdditions().put("factory2", cda2);
+        ConfigurationApi.setConfigurationApi(featureB, apiB);
+
+        final ArtifactId id = ArtifactId.parse("g:m:1");
+        final Feature result = FeatureBuilder.assemble(id, context, featureA, featureB);
+
+        final ConfigurationApi resultApi = ConfigurationApi.getConfigurationApi(result);
+        assertTrue(resultApi.getFactoryConfigurationDescriptionAdditions().isEmpty());
+        final FactoryConfigurationDescription resultCD1 = resultApi.getFactoryConfigurationDescriptions().get("factory1");
+        assertNotNull(resultCD1);
+        assertEquals(Arrays.asList("i1", "mrx"), resultCD1.getInternalNames());
+        final PropertyDescription resultPD11 = resultCD1.getPropertyDescriptions().get("p1");
+        assertNotNull(resultPD11);
+        assertArrayEquals(new String[] {"a", "b", "c"}, resultPD11.getIncludes());
+        final PropertyDescription resultPD12 = resultCD1.getPropertyDescriptions().get("p2");
+        assertNotNull(resultPD12);
+        assertArrayEquals(new String[] {"x"}, resultPD12.getIncludes());
+        final FactoryConfigurationDescription resultCD2 = resultApi.getFactoryConfigurationDescriptions().get("factory2");
+        assertNotNull(resultCD2);
+        assertEquals(Arrays.asList("i2"), resultCD2.getInternalNames());
+        final PropertyDescription resultPD21 = resultCD2.getPropertyDescriptions().get("p3");
+        assertNotNull(resultPD21);
+        assertArrayEquals(new String[] {"a", "b", "d"}, resultPD21.getIncludes());
+        final PropertyDescription resultPD22 = resultCD2.getPropertyDescriptions().get("p4");
+        assertNotNull(resultPD22);
+        assertArrayEquals(new String[] {"y"}, resultPD22.getIncludes());
+    }
+
+    @Test public void testConfigurationAdditionsConfigDoesNotExist() {
+        final ConfigurationDescriptionAddition cda = new ConfigurationDescriptionAddition();
+        final PropertyDescriptionAddition pda = new PropertyDescriptionAddition();
+        pda.setIncludes(new String[] {"a"});
+        cda.getPropertyDescriptionAdditions().put("p1", pda);
+
+        final FactoryConfigurationDescriptionAddition cdb = new FactoryConfigurationDescriptionAddition();
+        cdb.getPropertyDescriptionAdditions().put("p1", pda);
+
+        final BuilderContext context = new BuilderContext(id -> null);
+        context.addMergeExtensions(new ConfigurationApiMergeHandler());
+
+        final Feature featureA = new Feature(ArtifactId.parse("g:a:1"));
+        final ConfigurationApi apiA = new ConfigurationApi();
+        apiA.getConfigurationDescriptionAdditions().put("pid1", cda);
+        apiA.getFactoryConfigurationDescriptionAdditions().put("factory1", cdb);
+        ConfigurationApi.setConfigurationApi(featureA, apiA);
+
+        final Feature featureB = new Feature(ArtifactId.parse("g:b:1"));
+        final ConfigurationApi apiB = new ConfigurationApi();
+        apiB.getConfigurationDescriptionAdditions().put("pid2", cda);
+        apiB.getFactoryConfigurationDescriptionAdditions().put("factory2", cdb);
+        ConfigurationApi.setConfigurationApi(featureB, apiB);
+
+        final ArtifactId id = ArtifactId.parse("g:m:1");
+        final Feature result = FeatureBuilder.assemble(id, context, featureA, featureB);
+        final ConfigurationApi resultApi = ConfigurationApi.getConfigurationApi(result);
+        assertEquals(2, resultApi.getConfigurationDescriptionAdditions().size());
+        assertNotNull(resultApi.getConfigurationDescriptionAdditions().get("pid1"));
+        assertNotNull(resultApi.getConfigurationDescriptionAdditions().get("pid2"));
+        assertEquals(2, resultApi.getFactoryConfigurationDescriptionAdditions().size());
+        assertNotNull(resultApi.getFactoryConfigurationDescriptionAdditions().get("factory1"));
+        assertNotNull(resultApi.getFactoryConfigurationDescriptionAdditions().get("factory2"));
+    }
+
+    @Test(expected = IllegalStateException.class) 
+    public void testConfigurationAdditionsConfigPropDoesNotExist() {
+        final ConfigurationDescriptionAddition cda = new ConfigurationDescriptionAddition();
+        final PropertyDescriptionAddition pda = new PropertyDescriptionAddition();
+        pda.setIncludes(new String[] {"c"});
+        cda.getPropertyDescriptionAdditions().put("p1", pda);
+
+        final BuilderContext context = new BuilderContext(id -> null);
+        context.addMergeExtensions(new ConfigurationApiMergeHandler());
+
+        final Feature featureA = new Feature(ArtifactId.parse("g:a:1"));
+        final ConfigurationApi apiA = new ConfigurationApi();
+        final ConfigurationDescription cd = new ConfigurationDescription();
+        apiA.getConfigurationDescriptions().put("pid", cd);
+        ConfigurationApi.setConfigurationApi(featureA, apiA);
+
+        final Feature featureB = new Feature(ArtifactId.parse("g:b:1"));
+        final ConfigurationApi apiB = new ConfigurationApi();
+        apiB.getConfigurationDescriptionAdditions().put("pid", cda);
+        ConfigurationApi.setConfigurationApi(featureB, apiB);
+
+        final ArtifactId id = ArtifactId.parse("g:m:1");
+        FeatureBuilder.assemble(id, context, featureA, featureB);
+    }
+
+    @Test(expected = IllegalStateException.class) 
+    public void testConfigurationAdditionsFactoryConfigPropDoesNotExist() {
+        final FactoryConfigurationDescriptionAddition cda = new FactoryConfigurationDescriptionAddition();
+        final PropertyDescriptionAddition pda = new PropertyDescriptionAddition();
+        pda.setIncludes(new String[] {"c"});
+        cda.getPropertyDescriptionAdditions().put("p1", pda);
+
+        final BuilderContext context = new BuilderContext(id -> null);
+        context.addMergeExtensions(new ConfigurationApiMergeHandler());
+
+        final Feature featureA = new Feature(ArtifactId.parse("g:a:1"));
+        final ConfigurationApi apiA = new ConfigurationApi();
+        final FactoryConfigurationDescription cd = new FactoryConfigurationDescription();
+        apiA.getFactoryConfigurationDescriptions().put("pid", cd);
+        ConfigurationApi.setConfigurationApi(featureA, apiA);
+
+        final Feature featureB = new Feature(ArtifactId.parse("g:b:1"));
+        final ConfigurationApi apiB = new ConfigurationApi();
+        apiB.getFactoryConfigurationDescriptionAdditions().put("pid", cda);
+        ConfigurationApi.setConfigurationApi(featureB, apiB);
+
+        final ArtifactId id = ArtifactId.parse("g:m:1");
+        FeatureBuilder.assemble(id, context, featureA, featureB);
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTaskTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTaskTest.java
index c6c1484..930a902 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTaskTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTaskTest.java
@@ -49,7 +49,6 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntityAdditionTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntityAdditionTest.java
new file mode 100644
index 0000000..d805e0c
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntityAdditionTest.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.extension.apiregions.api.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import javax.json.Json;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionState;
+import org.apache.sling.feature.ExtensionType;
+import org.junit.Test;
+
+public class ConfigurableEntityAdditionTest {
+
+    public static class CE extends ConfigurableEntityAddition {
+        // ConfigurableEntityAddition is abstract, therefore subclassing for testing
+
+        public CE() {
+            setDefaults();
+        }
+    }
+
+    @Test public void testClear() {
+        final CE entity = new CE();
+        entity.getAttributes().put("a", Json.createValue(5));
+        entity.getPropertyDescriptionAdditions().put("a", new PropertyDescriptionAddition());
+        entity.clear();
+        assertTrue(entity.getAttributes().isEmpty());
+        assertTrue(entity.getPropertyDescriptionAdditions().isEmpty());
+    }
+
+    @Test public void testFromJSONObject() throws IOException {
+        final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"properties\" : { \"a\" : {}, \"b\" : {}}}");
+
+        final CE entity = new CE();
+        entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
+        assertEquals(2, entity.getPropertyDescriptionAdditions().size());
+        assertNotNull(entity.getPropertyDescriptionAdditions().get("a"));
+        assertNotNull(entity.getPropertyDescriptionAdditions().get("b"));
+    }
+
+    @Test public void testToJSONObject() throws IOException {
+        final CE entity = new CE();
+        entity.getPropertyDescriptionAdditions().put("a", new PropertyDescriptionAddition());
+        entity.getPropertyDescriptionAdditions().put("b", new PropertyDescriptionAddition());
+
+        final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"properties\" : { \"a\" : {}, \"b\" : {}}}");
+
+        assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());
+    }
+
+    @Test(expected = IOException.class) 
+    public void testDuplicateCaseInsensitiveKeys() throws IOException {
+        final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"properties\" : { \"a\" : {}, \"A\" : {}}}");
+
+        final CE entity = new CE();
+        entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApiTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApiTest.java
index ce21bd3..d06584d 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApiTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApiTest.java
@@ -79,6 +79,8 @@
         entity.setRegion(Region.GLOBAL);
         entity.getFeatureToRegionCache().put(ArtifactId.parse("g:a:1"), Region.GLOBAL);
         entity.setMode(Mode.SILENT);
+        entity.getConfigurationDescriptionAdditions().put("pid", new ConfigurationDescriptionAddition());
+        entity.getFactoryConfigurationDescriptionAdditions().put("factory", new FactoryConfigurationDescriptionAddition());
         entity.clear();
         assertTrue(entity.getAttributes().isEmpty());
         assertTrue(entity.getConfigurationDescriptions().isEmpty());
@@ -89,6 +91,8 @@
         assertTrue(entity.getInternalFrameworkProperties().isEmpty());
         assertNull(entity.getRegion());
         assertTrue(entity.getFeatureToRegionCache().isEmpty());
+        assertTrue(entity.getConfigurationDescriptionAdditions().isEmpty());
+        assertTrue(entity.getFactoryConfigurationDescriptionAdditions().isEmpty());
         assertEquals(Mode.STRICT, entity.getMode());
     }
 
@@ -101,12 +105,17 @@
             "\"internal-factory-configurations\" : [\"ifactory\"],"+
             "\"internal-framework-properties\" : [\"iprop\"],"+
             "\"region\" : \"INTERNAL\","+
-            "\"region-cache\" : {\"g:a1:feature:1.0.0\" : \"INTERNAL\", \"g:a2:feature:1.7.3\" : \"GLOBAL\"}}");
+            "\"region-cache\" : {\"g:a1:feature:1.0.0\" : \"INTERNAL\", \"g:a2:feature:1.7.3\" : \"GLOBAL\"},"+
+            "\"configuration-additions\" : { \"pida\": {}},"+
+            "\"factory-configuration-additions\" : { \"factorya\": {}}"+
+            "}");
 
         final ConfigurationApi entity = new ConfigurationApi();
         entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
         assertEquals(1, entity.getConfigurationDescriptions().size());
         assertEquals(1, entity.getFactoryConfigurationDescriptions().size());
+        assertEquals(1, entity.getConfigurationDescriptionAdditions().size());
+        assertEquals(1, entity.getFactoryConfigurationDescriptionAdditions().size());
         assertEquals(1, entity.getFrameworkPropertyDescriptions().size());
         assertEquals(1, entity.getInternalConfigurations().size());
         assertEquals(1, entity.getInternalFactoryConfigurations().size());
@@ -114,6 +123,8 @@
         assertEquals(2, entity.getFeatureToRegionCache().size());
         assertTrue(entity.getConfigurationDescriptions().containsKey("pid"));
         assertTrue(entity.getFactoryConfigurationDescriptions().containsKey("factory"));
+        assertTrue(entity.getConfigurationDescriptionAdditions().containsKey("pida"));
+        assertTrue(entity.getFactoryConfigurationDescriptionAdditions().containsKey("factorya"));
         assertTrue(entity.getFrameworkPropertyDescriptions().containsKey("prop"));
         assertTrue(entity.getInternalConfigurations().contains("ipid"));
         assertTrue(entity.getInternalFactoryConfigurations().contains("ifactory"));
@@ -130,6 +141,8 @@
         entity.getConfigurationDescriptions().put("pid", new ConfigurationDescription());
         entity.getFactoryConfigurationDescriptions().put("factory", new FactoryConfigurationDescription());
         entity.getFrameworkPropertyDescriptions().put("prop", new FrameworkPropertyDescription());
+        entity.getConfigurationDescriptionAdditions().put("pida", new ConfigurationDescriptionAddition());
+        entity.getFactoryConfigurationDescriptionAdditions().put("factorya", new FactoryConfigurationDescriptionAddition());
         entity.getInternalConfigurations().add("ipid");
         entity.getInternalFactoryConfigurations().add("ifactory");
         entity.getInternalFrameworkProperties().add("iprop");
@@ -146,6 +159,8 @@
             "\"internal-framework-properties\" : [\"iprop\"],"+
             "\"region\" : \"INTERNAL\","+
             "\"region-cache\" : {\"g:a1:feature:1.0.0\" : \"INTERNAL\", \"g:a2:feature:1.7.3\" : \"GLOBAL\"}," +
+            "\"configuration-additions\" : { \"pida\": {}},"+
+            "\"factory-configuration-additions\" : { \"factorya\": {}},"+
             "\"mode\" : \"SILENT\"}");
 
         assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());        
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescriptionAdditionTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescriptionAdditionTest.java
new file mode 100644
index 0000000..0ecbf41
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescriptionAdditionTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.extension.apiregions.api.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import javax.json.Json;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionState;
+import org.apache.sling.feature.ExtensionType;
+import org.junit.Test;
+
+public class FactoryConfigurationDescriptionAdditionTest {
+
+    @Test public void testClear() {
+        final FactoryConfigurationDescriptionAddition entity = new FactoryConfigurationDescriptionAddition();
+        entity.getAttributes().put("a", Json.createValue(5));
+        entity.getPropertyDescriptionAdditions().put("a", new PropertyDescriptionAddition());
+        entity.getInternalNames().add("internal");
+        entity.clear();
+        assertTrue(entity.getAttributes().isEmpty());
+        assertTrue(entity.getPropertyDescriptionAdditions().isEmpty());
+        assertTrue(entity.getInternalNames().isEmpty());
+    }
+
+    @Test public void testFromJSONObject() throws IOException {
+        final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"internal-names\" : [ \"a\", \"b\"]}");
+
+        final FactoryConfigurationDescriptionAddition entity = new FactoryConfigurationDescriptionAddition();
+        entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
+        assertEquals(2, entity.getInternalNames().size());
+        assertTrue(entity.getInternalNames().contains("a"));
+        assertTrue(entity.getInternalNames().contains("b"));
+    }
+
+    @Test public void testToJSONObject() throws IOException {
+        final FactoryConfigurationDescription entity = new FactoryConfigurationDescription();
+        entity.getInternalNames().add("a");
+        entity.getInternalNames().add("b");
+
+        final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"internal-names\" : [ \"a\", \"b\"]}");
+
+        assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescriptionAdditionTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescriptionAdditionTest.java
new file mode 100644
index 0000000..fae16d7
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescriptionAdditionTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.extension.apiregions.api.config;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import javax.json.Json;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionState;
+import org.apache.sling.feature.ExtensionType;
+import org.junit.Test;
+
+public class PropertyDescriptionAdditionTest {
+
+    @Test public void testClear() {
+        final PropertyDescriptionAddition entity = new PropertyDescriptionAddition();
+        entity.getAttributes().put("a", Json.createValue(5));
+        entity.setIncludes(new String[] {"in"});
+        entity.clear();
+        assertTrue(entity.getAttributes().isEmpty());
+        assertNull(entity.getIncludes());
+    }
+
+    @Test public void testFromJSONObject() throws IOException {
+        final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"includes\" : [\"in\"]}");
+
+        final PropertyDescriptionAddition entity = new PropertyDescriptionAddition();
+        entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
+
+        assertArrayEquals(new String[] {"in"}, entity.getIncludes());
+   }
+
+    @Test public void testToJSONObject() throws IOException {
+        final PropertyDescriptionAddition entity = new PropertyDescriptionAddition();
+        entity.setIncludes(new String[] {"in"});
+
+        final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"includes\" : [\"in\"]}");
+
+        assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());
+
+        // test defaults and empty values
+        entity.setIncludes(null);
+
+        ext.setJSON("{}");
+
+        assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());
+    }
+}