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));