Implement region merging and feature validation
diff --git a/pom.xml b/pom.xml
index 0a929c5..d8e03c0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,7 +56,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.feature</artifactId>
- <version>1.2.13-SNAPSHOT</version>
+ <version>1.2.14</version>
<scope>provided</scope>
</dependency>
<dependency>
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 53ea26c..a7e505d 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
@@ -27,6 +27,7 @@
import org.apache.sling.feature.extension.apiregions.api.config.ConfigurationDescription;
import org.apache.sling.feature.extension.apiregions.api.config.FactoryConfigurationDescription;
import org.apache.sling.feature.extension.apiregions.api.config.PropertyDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.Region;
import org.osgi.framework.Constants;
/**
@@ -46,30 +47,24 @@
/**
* Validate a configuration
- * @param The configuration description
* @param config The OSGi configuration
+ * @param desc The configuration description
+ * @param region The optional region for the configuration
* @return The result
*/
- public ConfigurationValidationResult validate(final ConfigurableEntity desc, final Configuration config) {
+ public ConfigurationValidationResult validate(final Configuration config, final ConfigurableEntity desc, final Region region) {
final ConfigurationValidationResult result = new ConfigurationValidationResult();
if ( config.isFactoryConfiguration() ) {
if ( !(desc instanceof FactoryConfigurationDescription) ) {
result.getGlobalErrors().add("Factory configuration cannot be validated against non factory configuration description");
} else {
- final FactoryConfigurationDescription fDesc = (FactoryConfigurationDescription)desc;
- if ( fDesc.getOperations().isEmpty() ) {
- result.getGlobalErrors().add("Factory configuration not allowed");
- } else if ( fDesc.getInternalNames().contains(config.getName())) {
- result.getGlobalErrors().add("Factory configuration with name " + config.getName() + " not allowed");
- } else {
- validateProperties(desc, config, result.getPropertyResults());
- }
+ validateProperties(desc, config, result.getPropertyResults(), region);
}
} else {
if ( !(desc instanceof ConfigurationDescription) ) {
result.getGlobalErrors().add("Configuration cannot be validated against factory configuration description");
} else {
- validateProperties(desc, config, result.getPropertyResults());
+ validateProperties(desc, config, result.getPropertyResults(), region);
}
}
@@ -81,11 +76,12 @@
void validateProperties(final ConfigurableEntity desc,
final Configuration configuration,
- final Map<String, PropertyValidationResult> results) {
+ final Map<String, PropertyValidationResult> results,
+ final Region region) {
final Dictionary<String, Object> properties = configuration.getConfigurationProperties();
for(final Map.Entry<String, PropertyDescription> propEntry : desc.getPropertyDescriptions().entrySet()) {
final Object value = properties.get(propEntry.getKey());
- final PropertyValidationResult result = propertyValidator.validate(propEntry.getValue(), value);
+ final PropertyValidationResult result = propertyValidator.validate(value, propEntry.getValue());
results.put(propEntry.getKey(), result);
}
final Enumeration<String> keyEnum = properties.keys();
@@ -99,9 +95,8 @@
if ( !(value instanceof Integer) ) {
result.getErrors().add("service.ranking must be of type Integer");
}
- } else if ( !ALLOWED_PROPERTIES.contains(propName) ) {
+ } else if ( !ALLOWED_PROPERTIES.contains(propName) && region != Region.INTERNAL ) {
result.getErrors().add("Property is not allowed");
-
}
}
}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidationResult.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidationResult.java
index 1e49dd0..09def1a 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidationResult.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidationResult.java
@@ -19,10 +19,17 @@
import java.util.HashMap;
import java.util.Map;
+/**
+ * Validation result for a feature
+ */
public class FeatureValidationResult {
private final Map<String, ConfigurationValidationResult> configurationResults = new HashMap<>();
+ /**
+ * Is the configuration of the feature valid?
+ * @return {@code true} if it is valid
+ */
public boolean isValid() {
boolean valid = true;
for(final ConfigurationValidationResult r : this.configurationResults.values()) {
@@ -34,6 +41,10 @@
return valid;
}
+ /**
+ * Get the confiugration validation results.
+ * @return The results keyed by configuration PIDs
+ */
public Map<String, ConfigurationValidationResult> getConfigurationResults() {
return this.configurationResults;
}
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 770bc60..95df5db 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidator.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidator.java
@@ -16,11 +16,17 @@
*/
package org.apache.sling.feature.extension.apiregions.api.config.validation;
+import java.util.List;
+
+import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Configuration;
import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.builder.FeatureProvider;
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.FactoryConfigurationDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.Operation;
+import org.apache.sling.feature.extension.apiregions.api.config.Region;
/**
* Validator to validate a feature
@@ -29,37 +35,123 @@
private final ConfigurationValidator configurationValidator = new ConfigurationValidator();
+ private volatile FeatureProvider featureProvider;
+
+ /**
+ * Get the current feature provider
+ * @return the feature provider or {@code null}
+ */
+ public FeatureProvider getFeatureProvider() {
+ return featureProvider;
+ }
+
+ /**
+ * Set the feature provider
+ * @param featureProvider the feature provider to set
+ */
+ public void setFeatureProvider(final FeatureProvider provider) {
+ this.featureProvider = provider;
+ }
+
/**
* Validate the feature against the configuration API
- * @param api The configuration API
* @param feature The feature
+ * @param api The configuration API
* @return A {@code FeatureValidationResult}
+ * @throws IllegalArgumentException If api is {@code null}
*/
- public FeatureValidationResult validate(final ConfigurationApi api, final Feature feature) {
+ public FeatureValidationResult validate(final Feature feature, final ConfigurationApi api) {
final FeatureValidationResult result = new FeatureValidationResult();
+ if ( api == null ) {
+ throw new IllegalArgumentException();
+ }
for(final Configuration config : feature.getConfigurations()) {
- if ( config.isFactoryConfiguration() ) {
- final FactoryConfigurationDescription desc = api.getFactoryConfigurationDescriptions().get(config.getFactoryPid());
- if ( desc != null ) {
- final ConfigurationValidationResult r = configurationValidator.validate(desc, config);
- result.getConfigurationResults().put(config.getPid(), r);
- } else if ( api.getInternalFactoryConfigurations().contains(config.getFactoryPid())) {
- final ConfigurationValidationResult cvr = new ConfigurationValidationResult();
- cvr.getGlobalErrors().add("Factory configuration is not allowed");
- result.getConfigurationResults().put(config.getPid(), cvr);
- }
+ final RegionInfo regionInfo = getRegionInfo(feature, config);
+
+ if ( regionInfo == null ) {
+ final ConfigurationValidationResult cvr = new ConfigurationValidationResult();
+ cvr.getGlobalErrors().add("Unable to properly validate configuration, region info cannot be determined");
+ result.getConfigurationResults().put(config.getPid(), cvr);
} else {
- final ConfigurationDescription desc = api.getConfigurationDescriptions().get(config.getPid());
- if ( desc != null ) {
- final ConfigurationValidationResult r = configurationValidator.validate(desc, config);
- result.getConfigurationResults().put(config.getPid(), r);
- } else if ( api.getInternalConfigurations().contains(config.getPid())) {
- final ConfigurationValidationResult cvr = new ConfigurationValidationResult();
- cvr.getGlobalErrors().add("Configuration is not allowed");
- result.getConfigurationResults().put(config.getPid(), cvr);
+ if ( config.isFactoryConfiguration() ) {
+ final FactoryConfigurationDescription desc = api.getFactoryConfigurationDescriptions().get(config.getFactoryPid());
+ if ( desc != null ) {
+ final ConfigurationValidationResult r = configurationValidator.validate(config, desc, regionInfo.region);
+ result.getConfigurationResults().put(config.getPid(), r);
+ if ( regionInfo.region != Region.INTERNAL ) {
+ if ( desc.getOperations().isEmpty() ) {
+ r.getGlobalErrors().add("No operations allowed for factory configuration");
+ } else {
+ if ( regionInfo.isUpdate && !desc.getOperations().contains(Operation.UPDATE)) {
+ r.getGlobalErrors().add("Updating of factory configuration is not allowed");
+ } else if ( !regionInfo.isUpdate && !desc.getOperations().contains(Operation.CREATE)) {
+ r.getGlobalErrors().add("Creation of factory configuration is not allowed");
+ }
+ }
+ if ( desc.getInternalNames().contains(config.getName())) {
+ r.getGlobalErrors().add("Factory configuration with name is not allowed");
+ }
+ }
+
+ } else if ( regionInfo.region != Region.INTERNAL && api.getInternalFactoryConfigurations().contains(config.getFactoryPid())) {
+ final ConfigurationValidationResult cvr = new ConfigurationValidationResult();
+ cvr.getGlobalErrors().add("Factory configuration is not allowed");
+ result.getConfigurationResults().put(config.getPid(), cvr);
+ }
+ } else {
+ final ConfigurationDescription desc = api.getConfigurationDescriptions().get(config.getPid());
+ if ( desc != null ) {
+ final ConfigurationValidationResult r = configurationValidator.validate(config, desc, regionInfo.region);
+ result.getConfigurationResults().put(config.getPid(), r);
+ } else if ( regionInfo.region!= Region.INTERNAL && api.getInternalConfigurations().contains(config.getPid())) {
+ final ConfigurationValidationResult cvr = new ConfigurationValidationResult();
+ cvr.getGlobalErrors().add("Configuration is not allowed");
+ result.getConfigurationResults().put(config.getPid(), cvr);
+ }
+ }
+ }
+ // make sure a result exists
+ result.getConfigurationResults().computeIfAbsent(config.getPid(), id -> new ConfigurationValidationResult());
+ }
+ return result;
+ }
+
+ static final class RegionInfo {
+
+ public Region region;
+
+ public boolean isUpdate;
+ }
+
+ RegionInfo getRegionInfo(final Feature feature, final Configuration cfg) {
+ final FeatureProvider provider = this.getFeatureProvider();
+ final RegionInfo result = new RegionInfo();
+
+ final List<ArtifactId> list = cfg.getFeatureOrigins();
+ if ( !list.isEmpty() ) {
+ boolean global = false;
+ for(final ArtifactId id : list) {
+ final Feature f = provider == null ? null : provider.provide(id);
+ if ( f == null ) {
+ return null;
+ }
+ final ConfigurationApi api = ConfigurationApi.getConfigurationApi(f);
+ if ( api == null || api.getRegion() != Region.INTERNAL ) {
+ global = true;
+ break;
}
}
+ result.region = global ? Region.GLOBAL : Region.INTERNAL;
+ result.isUpdate = list.size() > 1;
+ } else {
+ final ConfigurationApi api = ConfigurationApi.getConfigurationApi(feature);
+ if ( api == null || api.getRegion() == null || api.getRegion() == Region.GLOBAL ) {
+ result.region = Region.GLOBAL;
+ } else {
+ result.region = Region.INTERNAL;
+ }
+ result.isUpdate = false;
}
return result;
}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/PropertyValidator.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/PropertyValidator.java
index 3bd72d8..82f7a1c 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/PropertyValidator.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/PropertyValidator.java
@@ -35,7 +35,7 @@
* Validate the value against the property definition
* @return A property validation result
*/
- public PropertyValidationResult validate(final PropertyDescription prop, final Object value) {
+ public PropertyValidationResult validate(final Object value, final PropertyDescription prop) {
final PropertyValidationResult result = new PropertyValidationResult();
if ( value == null ) {
if ( prop.isRequired() ) {
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 5856910..7c95589 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
@@ -112,4 +112,86 @@
api = ConfigurationApi.getConfigurationApi(result);
assertEquals(Region.GLOBAL, api.getRegion());
}
- }
\ No newline at end of file
+
+ @Test public void testRegionMerge() {
+ // always return prototype
+ 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();
+ ConfigurationApi.setConfigurationApi(featureA, apiA);
+
+ final Feature featureB = new Feature(ArtifactId.parse("g:b:1"));
+ final ConfigurationApi apiB = new ConfigurationApi();
+ ConfigurationApi.setConfigurationApi(featureB, apiB);
+
+ // no region
+ final ArtifactId id = ArtifactId.parse("g:m:1");
+ Feature result = FeatureBuilder.assemble(id, context, featureA, featureB);
+ ConfigurationApi api = ConfigurationApi.getConfigurationApi(result);
+ assertNotNull(api);
+ assertNull(api.getRegion());
+
+ // only A has region
+ apiA.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(featureA, apiA);
+ result = FeatureBuilder.assemble(id, context, featureA, featureB);
+ api = ConfigurationApi.getConfigurationApi(result);
+ assertEquals(Region.GLOBAL, api.getRegion());
+
+ apiA.setRegion(Region.GLOBAL);
+ ConfigurationApi.setConfigurationApi(featureA, apiA);
+ result = FeatureBuilder.assemble(id, context, featureA, featureB);
+ api = ConfigurationApi.getConfigurationApi(result);
+ assertEquals(Region.GLOBAL, api.getRegion());
+
+ // only B has region
+ apiA.setRegion(null);
+ ConfigurationApi.setConfigurationApi(featureA, apiA);
+ apiB.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(featureB, apiB);
+ result = FeatureBuilder.assemble(id, context, featureA, featureB);
+ api = ConfigurationApi.getConfigurationApi(result);
+ assertEquals(Region.GLOBAL, api.getRegion());
+
+ apiB.setRegion(Region.GLOBAL);
+ ConfigurationApi.setConfigurationApi(featureB, apiB);
+ result = FeatureBuilder.assemble(id, context, featureA, featureB);
+ api = ConfigurationApi.getConfigurationApi(result);
+ assertEquals(Region.GLOBAL, api.getRegion());
+
+ // both have region
+ apiA.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(featureA, apiA);
+ apiB.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(featureB, apiB);
+ result = FeatureBuilder.assemble(id, context, featureA, featureB);
+ api = ConfigurationApi.getConfigurationApi(result);
+ assertEquals(Region.INTERNAL, api.getRegion());
+
+ apiA.setRegion(Region.GLOBAL);
+ ConfigurationApi.setConfigurationApi(featureA, apiA);
+ apiB.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(featureB, apiB);
+ result = FeatureBuilder.assemble(id, context, featureA, featureB);
+ api = ConfigurationApi.getConfigurationApi(result);
+ assertEquals(Region.GLOBAL, api.getRegion());
+
+ apiA.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(featureA, apiA);
+ apiB.setRegion(Region.GLOBAL);
+ ConfigurationApi.setConfigurationApi(featureB, apiB);
+ result = FeatureBuilder.assemble(id, context, featureA, featureB);
+ api = ConfigurationApi.getConfigurationApi(result);
+ assertEquals(Region.GLOBAL, api.getRegion());
+
+ apiA.setRegion(Region.GLOBAL);
+ ConfigurationApi.setConfigurationApi(featureA, apiA);
+ apiB.setRegion(Region.GLOBAL);
+ ConfigurationApi.setConfigurationApi(featureB, apiB);
+ result = FeatureBuilder.assemble(id, context, featureA, featureB);
+ api = ConfigurationApi.getConfigurationApi(result);
+ assertEquals(Region.GLOBAL, api.getRegion());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidatorTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidatorTest.java
index 8620daf..46c1f57 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidatorTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidatorTest.java
@@ -25,6 +25,7 @@
import org.apache.sling.feature.extension.apiregions.api.config.FactoryConfigurationDescription;
import org.apache.sling.feature.extension.apiregions.api.config.PropertyDescription;
import org.apache.sling.feature.extension.apiregions.api.config.PropertyType;
+import org.apache.sling.feature.extension.apiregions.api.config.Region;
import org.junit.Test;
import org.osgi.framework.Constants;
@@ -36,7 +37,7 @@
final Configuration cfg = new Configuration("org.apache");
final FactoryConfigurationDescription fcd = new FactoryConfigurationDescription();
- final ConfigurationValidationResult result = validator.validate(fcd, cfg);
+ final ConfigurationValidationResult result = validator.validate(cfg, fcd, null);
assertFalse(result.isValid());
assertEquals(1, result.getGlobalErrors().size());
}
@@ -45,7 +46,7 @@
final Configuration cfg = new Configuration("org.apache~foo");
final ConfigurationDescription fcd = new ConfigurationDescription();
- final ConfigurationValidationResult result = validator.validate(fcd, cfg);
+ final ConfigurationValidationResult result = validator.validate(cfg, fcd, null);
assertFalse(result.isValid());
assertEquals(1, result.getGlobalErrors().size());
}
@@ -54,12 +55,12 @@
final Configuration cfg = new Configuration("org.apache");
final ConfigurationDescription cd = new ConfigurationDescription();
- ConfigurationValidationResult result = validator.validate(cd, cfg);
+ ConfigurationValidationResult result = validator.validate(cfg, cd, null);
assertTrue(result.isValid());
assertTrue(result.getWarnings().isEmpty());
cd.setDeprecated("this is deprecated");
- result = validator.validate(cd, cfg);
+ result = validator.validate(cfg, cd, null);
assertTrue(result.isValid());
assertFalse(result.getWarnings().isEmpty());
assertEquals("this is deprecated", result.getWarnings().get(0));
@@ -70,11 +71,11 @@
final ConfigurationDescription cd = new ConfigurationDescription();
cfg.getProperties().put(Constants.SERVICE_RANKING, 5);
- ConfigurationValidationResult result = validator.validate(cd, cfg);
+ ConfigurationValidationResult result = validator.validate(cfg, cd, null);
assertTrue(result.isValid());
cfg.getProperties().put(Constants.SERVICE_RANKING, "5");
- result = validator.validate(cd, cfg);
+ result = validator.validate(cfg, cd, null);
assertFalse(result.isValid());
}
@@ -84,7 +85,7 @@
cfg.getProperties().put(Constants.SERVICE_DESCRIPTION, "desc");
cfg.getProperties().put(Constants.SERVICE_VENDOR, "vendor");
- ConfigurationValidationResult result = validator.validate(cd, cfg);
+ ConfigurationValidationResult result = validator.validate(cfg, cd, null);
assertTrue(result.isValid());
}
@@ -96,17 +97,22 @@
final PropertyDescription prop = new PropertyDescription();
cd.getPropertyDescriptions().put("a", prop);
- ConfigurationValidationResult result = validator.validate(cd, cfg);
+ ConfigurationValidationResult result = validator.validate(cfg, cd, Region.GLOBAL);
assertTrue(result.isValid());
assertEquals(1, result.getPropertyResults().size());
assertTrue(result.getPropertyResults().get("a").isValid());
cfg.getProperties().put("b", "vendor");
- result = validator.validate(cd, cfg);
+ result = validator.validate(cfg, cd, Region.GLOBAL);
assertFalse(result.isValid());
assertEquals(2, result.getPropertyResults().size());
assertTrue(result.getPropertyResults().get("a").isValid());
assertFalse(result.getPropertyResults().get("b").isValid());
+
+ // allowed if internal
+ result = validator.validate(cfg, cd, Region.INTERNAL);
+ assertTrue(result.isValid());
+ assertEquals(2, result.getPropertyResults().size());
}
@Test public void testInvalidProperty() {
@@ -121,7 +127,7 @@
propB.setType(PropertyType.INTEGER);
cd.getPropertyDescriptions().put("b", propB);
- ConfigurationValidationResult result = validator.validate(cd, cfg);
+ ConfigurationValidationResult result = validator.validate(cfg, cd, null);
assertFalse(result.isValid());
assertEquals(2, result.getPropertyResults().size());
assertTrue(result.getPropertyResults().get("a").isValid());
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
new file mode 100644
index 0000000..fb4fa0c
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/FeatureValidatorTest.java
@@ -0,0 +1,436 @@
+/*
+ * 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.validation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.builder.FeatureProvider;
+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.FactoryConfigurationDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.Operation;
+import org.apache.sling.feature.extension.apiregions.api.config.PropertyDescription;
+import org.apache.sling.feature.extension.apiregions.api.config.Region;
+import org.junit.Before;
+import org.junit.Test;
+
+public class FeatureValidatorTest {
+
+ private static final String PID = "org.apache.sling";
+
+ private static final String FACTORY_PID = "org.apache.sling.factory";
+
+ private final FeatureValidator validator = new FeatureValidator();
+
+ @Before public void setup() {
+ this.validator.setFeatureProvider(null);
+ }
+
+ private Feature createFeature(final String id) {
+ final Feature f= new Feature(ArtifactId.parse(id));
+ final Configuration c = new Configuration(PID);
+ c.getProperties().put("prop", "a");
+ f.getConfigurations().add(c);
+
+ final Configuration fc = new Configuration(FACTORY_PID.concat("~print"));
+ fc.getProperties().put("key", "value");
+ f.getConfigurations().add(fc);
+
+ return f;
+ }
+
+ private ConfigurationApi createApi() {
+ final ConfigurationApi api = new ConfigurationApi();
+
+ final ConfigurationDescription cd = new ConfigurationDescription();
+ cd.getPropertyDescriptions().put("prop", new PropertyDescription());
+
+ api.getConfigurationDescriptions().put(PID, cd);
+
+ final FactoryConfigurationDescription fd = new FactoryConfigurationDescription();
+ fd.getPropertyDescriptions().put("key", new PropertyDescription());
+
+ api.getFactoryConfigurationDescriptions().put(FACTORY_PID, fd);
+
+ return api;
+ }
+
+ @Test public void testGetRegionInfoNoOrigin() {
+ final Feature f1 = createFeature("g:a:1");
+ final Configuration cfg = f1.getConfigurations().getConfiguration(PID);
+
+ // no api set
+ FeatureValidator.RegionInfo info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.GLOBAL, info.region);
+ assertFalse(info.isUpdate);
+
+ // empty region in api
+ final ConfigurationApi api = createApi();
+ ConfigurationApi.setConfigurationApi(f1, api);
+ info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.GLOBAL, info.region);
+ assertFalse(info.isUpdate);
+
+ // global region in api
+ api.setRegion(Region.GLOBAL);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.GLOBAL, info.region);
+ assertFalse(info.isUpdate);
+
+ // internal region in api
+ api.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.INTERNAL, info.region);
+ assertFalse(info.isUpdate);
+ }
+
+ @Test public void testGetRegionInfoSingleOrigin() {
+ final Feature f1 = createFeature("g:a:1");
+ final Configuration cfg = f1.getConfigurations().getConfiguration(PID);
+
+ final Feature f2 = createFeature("g:b:1");
+ cfg.setFeatureOrigins(Collections.singletonList(f2.getId()));
+
+ // set feature provider to always provide f2
+ this.validator.setFeatureProvider(id -> f2);
+ // no api in origin
+ FeatureValidator.RegionInfo info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.GLOBAL, info.region);
+ assertFalse(info.isUpdate);
+
+ // no region in api
+ final ConfigurationApi api2 = new ConfigurationApi();
+ ConfigurationApi.setConfigurationApi(f2, api2);
+ info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.GLOBAL, info.region);
+ assertFalse(info.isUpdate);
+
+ // global in api
+ api2.setRegion(Region.GLOBAL);
+ ConfigurationApi.setConfigurationApi(f2, api2);
+ info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.GLOBAL, info.region);
+ assertFalse(info.isUpdate);
+
+ // internal in api
+ api2.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(f2, api2);
+ info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.INTERNAL, info.region);
+ assertFalse(info.isUpdate);
+
+ // unknown id
+ this.validator.setFeatureProvider(id -> null);
+ cfg.setFeatureOrigins(Collections.singletonList(ArtifactId.parse("g:xy:1")));
+ info = validator.getRegionInfo(f1, cfg);
+ assertNull(info);
+ }
+
+ @Test public void testGetRegionInfoMultipleOrigins() {
+ final Feature f1 = createFeature("g:a:1");
+ final Configuration cfg = f1.getConfigurations().getConfiguration(PID);
+
+ final Feature f2 = createFeature("g:b:1");
+ final Feature f3 = createFeature("g:c:1");
+ cfg.setFeatureOrigins(Arrays.asList(f2.getId(), f3.getId()));
+
+ final FeatureProvider provider = new FeatureProvider() {
+
+ @Override
+ public Feature provide(final ArtifactId id) {
+ if ( f1.getId().equals(id) ) {
+ return f1;
+ } else if ( f2.getId().equals(id)) {
+ return f2;
+ } else if ( f3.getId().equals(id)) {
+ return f3;
+ }
+ return null;
+ }
+
+ };
+
+ this.validator.setFeatureProvider(provider);
+
+ // no api in origins
+ FeatureValidator.RegionInfo info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.GLOBAL, info.region);
+ assertTrue(info.isUpdate);
+
+ // global-internal
+ final ConfigurationApi api2 = new ConfigurationApi();
+ final ConfigurationApi api3 = new ConfigurationApi();
+ api2.setRegion(Region.GLOBAL);
+ api3.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(f2, api2);
+ ConfigurationApi.setConfigurationApi(f3, api3);
+
+ info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.GLOBAL, info.region);
+ assertTrue(info.isUpdate);
+
+ // global-global
+ api2.setRegion(Region.GLOBAL);
+ api3.setRegion(Region.GLOBAL);
+ ConfigurationApi.setConfigurationApi(f2, api2);
+ ConfigurationApi.setConfigurationApi(f3, api3);
+
+ info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.GLOBAL, info.region);
+ assertTrue(info.isUpdate);
+
+ // internal-internal
+ api2.setRegion(Region.INTERNAL);
+ api3.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(f2, api2);
+ ConfigurationApi.setConfigurationApi(f3, api3);
+
+ info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.INTERNAL, info.region);
+ assertTrue(info.isUpdate);
+
+ // internal-global
+ api2.setRegion(Region.INTERNAL);
+ api3.setRegion(Region.GLOBAL);
+ ConfigurationApi.setConfigurationApi(f2, api2);
+ ConfigurationApi.setConfigurationApi(f3, api3);
+
+ info = validator.getRegionInfo(f1, cfg);
+ assertEquals(Region.GLOBAL, info.region);
+ assertTrue(info.isUpdate);
+ }
+ @Test public void testSingleValidation() {
+ final Feature f1 = createFeature("g:a:1");
+ final ConfigurationApi api = createApi();
+ ConfigurationApi.setConfigurationApi(f1, api);
+
+ FeatureValidationResult result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+
+ // add property
+ f1.getConfigurations().getConfiguration(PID).getProperties().put("b", "x");
+ result = validator.validate(f1, api);
+ assertFalse(result.isValid());
+ }
+
+ @Test public void testInternalConfiguration() {
+ final Feature f1 = createFeature("g:a:1");
+ final ConfigurationApi api = new ConfigurationApi();
+ ConfigurationApi.setConfigurationApi(f1, api);
+
+ // global region
+ FeatureValidationResult result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+
+ // mark configurations as internal
+ api.getInternalConfigurations().add(PID);
+ api.getInternalFactoryConfigurations().add(FACTORY_PID);
+ ConfigurationApi.setConfigurationApi(f1, api);
+
+ // global region
+ result = validator.validate(f1, api);
+ assertFalse(result.isValid());
+ assertFalse(result.getConfigurationResults().get(PID).isValid());
+ assertFalse(result.getConfigurationResults().get(FACTORY_PID.concat("~print")).isValid());
+
+ // internal region
+ api.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+ }
+
+ @Test public void testInternalFactoryNames() {
+ final Feature f1 = createFeature("g:a:1");
+
+ final Configuration fa = new Configuration(FACTORY_PID.concat("~a"));
+ fa.getProperties().put("key", "value");
+ f1.getConfigurations().add(fa);
+
+ final Configuration fb = new Configuration(FACTORY_PID.concat("~b"));
+ fb.getProperties().put("key", "value");
+ f1.getConfigurations().add(fb);
+
+ final ConfigurationApi api = createApi();
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getInternalNames().add("a");
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getInternalNames().add("b");
+ ConfigurationApi.setConfigurationApi(f1, api);
+
+ FeatureValidationResult result = validator.validate(f1, api);
+ assertFalse(result.isValid());
+ assertFalse(result.getConfigurationResults().get(FACTORY_PID.concat("~a")).isValid());
+ assertFalse(result.getConfigurationResults().get(FACTORY_PID.concat("~b")).isValid());
+ assertTrue(result.getConfigurationResults().get(FACTORY_PID.concat("~print")).isValid());
+
+ // internal region
+ api.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+ }
+
+ @Test public void testFactoryConfigurationOperationsWithCreate() {
+ final Feature f1 = createFeature("g:a:1");
+ final ConfigurationApi api = createApi();
+
+ // no operation -> fail
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().clear();
+ ConfigurationApi.setConfigurationApi(f1, api);
+ FeatureValidationResult result = validator.validate(f1, api);
+ assertFalse(result.isValid());
+ assertFalse(result.getConfigurationResults().get(FACTORY_PID.concat("~print")).isValid());
+
+ // only update -> fail
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().add(Operation.UPDATE);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertFalse(result.isValid());
+ assertFalse(result.getConfigurationResults().get(FACTORY_PID.concat("~print")).isValid());
+
+ // only create -> success
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().clear();
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().add(Operation.CREATE);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+
+ // update, create -> success
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().clear();
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().add(Operation.CREATE);
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().add(Operation.UPDATE);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+
+ // internal region -> always success
+ api.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().clear();
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().add(Operation.UPDATE);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().clear();
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().add(Operation.CREATE);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().clear();
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+ }
+
+ @Test public void testFactoryConfigurationOperationsWithUpdate() {
+ final Feature f1 = createFeature("g:a:1");
+ final ConfigurationApi api = createApi();
+
+ final Configuration cfg = f1.getConfigurations().getConfiguration(FACTORY_PID.concat("~print"));
+
+ final Feature f2 = createFeature("g:b:1");
+ final Feature f3 = createFeature("g:c:1");
+ cfg.setFeatureOrigins(Arrays.asList(f2.getId(), f3.getId()));
+
+ final FeatureProvider provider = new FeatureProvider() {
+
+ @Override
+ public Feature provide(final ArtifactId id) {
+ if ( f1.getId().equals(id) ) {
+ return f1;
+ } else if ( f2.getId().equals(id)) {
+ return f2;
+ } else if ( f3.getId().equals(id)) {
+ return f3;
+ }
+ return null;
+ }
+
+ };
+
+ this.validator.setFeatureProvider(provider);
+
+ // no operation -> fail
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().clear();
+ ConfigurationApi.setConfigurationApi(f1, api);
+ FeatureValidationResult result = validator.validate(f1, api);
+ assertFalse(result.isValid());
+ assertFalse(result.getConfigurationResults().get(FACTORY_PID.concat("~print")).isValid());
+
+ // only update -> success
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().add(Operation.UPDATE);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+
+ // only create -> fail
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().clear();
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().add(Operation.CREATE);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertFalse(result.isValid());
+ assertFalse(result.getConfigurationResults().get(FACTORY_PID.concat("~print")).isValid());
+
+ // update, create -> success
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().clear();
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().add(Operation.CREATE);
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().add(Operation.UPDATE);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+
+ // internal region -> always success
+ api.setRegion(Region.INTERNAL);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ ConfigurationApi.setConfigurationApi(f2, api);
+ ConfigurationApi.setConfigurationApi(f3, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().clear();
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().add(Operation.UPDATE);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().clear();
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().add(Operation.CREATE);
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+
+ api.getFactoryConfigurationDescriptions().get(FACTORY_PID).getOperations().clear();
+ ConfigurationApi.setConfigurationApi(f1, api);
+ result = validator.validate(f1, api);
+ assertTrue(result.isValid());
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/PropertyValidatorTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/PropertyValidatorTest.java
index 64cbddb..acbffa5 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/PropertyValidatorTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/PropertyValidatorTest.java
@@ -37,13 +37,13 @@
final PropertyDescription prop = new PropertyDescription();
// prop not required - no error
- assertTrue(validator.validate(prop, null).getErrors().isEmpty());
- assertTrue(validator.validate(prop, null).isValid());
+ assertTrue(validator.validate(null, prop).getErrors().isEmpty());
+ assertTrue(validator.validate(null, prop).isValid());
// prop required - error
prop.setRequired(true);
- assertEquals(1, validator.validate(prop, null).getErrors().size());
- assertFalse(validator.validate(prop, null).isValid());
+ assertEquals(1, validator.validate(null, prop).getErrors().size());
+ assertFalse(validator.validate(null, prop).isValid());
}
@Test public void testValidateBoolean() {
@@ -405,7 +405,7 @@
final PropertyDescription prop = new PropertyDescription();
prop.setDeprecated("This is deprecated");
- final PropertyValidationResult result = validator.validate(prop, "foo");
+ final PropertyValidationResult result = validator.validate("foo", prop);
assertTrue(result.isValid());
assertEquals(1, result.getWarnings().size());
assertEquals("This is deprecated", result.getWarnings().get(0));