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());
+ }
}