SLING-10117 : Support different configuration validation modes
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 a6f820d..08c7929 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
@@ -37,6 +37,12 @@
     @SuppressWarnings({"unchecked", "rawtypes"})
     private final Map<String, PropertyDescription> properties = (Map)Configurations.newConfiguration();
 
+    /** 
+     * The validation mode. 
+     * @since 1.2
+     */
+    private Mode mode;
+    
     /**
      * Clear the object and reset to defaults
      */
@@ -44,6 +50,7 @@
 	public void clear() {
         super.clear();
 		this.properties.clear();
+        this.setMode(null);
     }
 
 	/**
@@ -67,6 +74,10 @@
                     }
                 }
             }            
+			final String modeVal = this.getString(InternalConstants.KEY_MODE);
+			if ( modeVal != null ) {
+                this.setMode(Mode.valueOf(modeVal.toUpperCase()));				
+			}
         } catch (final JsonException | IllegalArgumentException e) {
             throw new IOException(e);
         }
@@ -81,6 +92,24 @@
     }
 
     /**
+     * Get the validation mode.
+     * @return The mode or {@code null}
+     * @since 1.2
+     */
+    public Mode getMode() {
+        return this.mode;
+    }
+
+    /**
+     * Set the validation mode
+     * @value The validation mode
+     * @since 1.2
+     */
+    public void setMode(final Mode value) {
+        this.mode = value;
+    }
+
+    /**
      * Convert this object into JSON
      *
      * @return The json object builder
@@ -97,6 +126,9 @@
 			}
 			objBuilder.add(InternalConstants.KEY_PROPERTIES, propBuilder);
 		}
+        if ( this.getMode() != null ) {
+            objBuilder.add(InternalConstants.KEY_MODE, this.getMode().name());
+        }
 
 		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 15c23ef..15e622e 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
@@ -133,6 +133,12 @@
     /** The cached region information for feature origins */
     private final Map<ArtifactId, Region> regionCache = new LinkedHashMap<>();
 
+    /** 
+     * The default validation mode. 
+     * @since 1.2
+     */
+    private Mode mode = Mode.STRICT;
+    
     /**
      * Clear the object and reset to defaults
      */
@@ -147,6 +153,7 @@
         this.internalFrameworkProperties.clear();
         this.setRegion(null);
         this.getFeatureToRegionCache().clear();
+        this.setMode(Mode.STRICT);
     }
 
 	/**
@@ -221,6 +228,11 @@
                         Region.valueOf(getString(innerEntry.getValue()).toUpperCase()));
                 }
             }
+            
+			final String modeVal = this.getString(InternalConstants.KEY_MODE);
+			if ( modeVal != null ) {
+                this.setMode(Mode.valueOf(modeVal.toUpperCase()));				
+			}
 
         } catch (final JsonException | IllegalArgumentException e) {
             throw new IOException(e);
@@ -313,6 +325,25 @@
     }
 
     /**
+     * Get the validation mode.
+     * The default is {@link Mode#STRICT}
+     * @return The mode
+     * @since 1.2
+     */
+    public Mode getMode() {
+        return this.mode;
+    }
+
+    /**
+     * Set the validation mode
+     * @value The validation mode
+     * @since 1.2
+     */
+    public void setMode(final Mode value) {
+        this.mode = value == null ? Mode.STRICT : value;
+    }
+
+    /**
      * Convert this object into JSON
      *
      * @return The json object builder
@@ -373,6 +404,9 @@
             }
             objBuilder.add(InternalConstants.KEY_REGION_CACHE, cacheBuilder);
         }
+        if ( this.getMode() != Mode.STRICT ) {
+            objBuilder.add(InternalConstants.KEY_MODE, this.getMode().name());
+        }
 
 		return objBuilder;
     }
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/InternalConstants.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/InternalConstants.java
index e126581..a99f32b 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
@@ -74,4 +74,6 @@
     static final String KEY_REGION_CACHE = "region-cache";
 
     static final String KEY_DEFAULT = "default";
+
+    static final String KEY_MODE = "mode";
 }
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Mode.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Mode.java
new file mode 100644
index 0000000..7608c3c
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Mode.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * The validation mode
+ * @since 1.2
+ */
+public enum Mode {
+
+    /** Default mode - If validation fails, issue an error. */
+    STRICT,
+
+    /** If validation fails, issue a warning (but use the invalid value). */
+    LENIENT,
+
+    /** If validation fails, do not report and use the invalid value. */
+    SILENT,
+
+    /** If validation fails, use the default value (if provided - otherwise remove value) and issue a warning. */
+    DEFINITIVE,
+
+    /** If validation fails, use the default value (if provided - otherwise remove value) and do not issue a warning. */
+    SILENT_DEFINITIVE
+
+}
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 56bb7e1..c315d4f 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
@@ -69,6 +69,12 @@
      */
     private Object defaultValue;
 
+    /** 
+     * The validation mode. 
+     * @since 1.2
+     */
+    private Mode mode;
+
     /**
      * Create a new description
      */
@@ -96,6 +102,7 @@
 		this.setOptions(null);
 		this.setRegex(null);
         this.setDefaultValue(null);
+        this.setMode(null);
     }
 
 	/**
@@ -153,6 +160,10 @@
             if ( dv != null ) {
                 this.setDefaultValue(Configurations.convertToObject(dv));
             }
+			final String modeVal = this.getString(InternalConstants.KEY_MODE);
+			if ( modeVal != null ) {
+                this.setMode(Mode.valueOf(modeVal.toUpperCase()));				
+			}
  		} catch (final JsonException | IllegalArgumentException e) {
             throw new IOException(e);
         }
@@ -207,7 +218,11 @@
 		if ( this.getDefaultValue() != null ) {
             objectBuilder.add(InternalConstants.KEY_DEFAULT, Configurations.convertToJsonValue(this.getDefaultValue()));
         }
-		return objectBuilder;
+        if ( this.getMode() != null ) {
+            objectBuilder.add(InternalConstants.KEY_MODE, this.getMode().name());
+        }
+
+        return objectBuilder;
 	}
 
     /**
@@ -392,4 +407,22 @@
     public void setDefaultValue(final Object val) {
         this.defaultValue = val;
     }
-}
+
+    /**
+     * Get the validation mode.
+     * @return The mode or {@code null}
+     * @since 1.2
+     */
+    public Mode getMode() {
+        return this.mode;
+    }
+
+    /**
+     * Set the validation mode
+     * @value The validation mode
+     * @since 1.2
+     */
+    public void setMode(final Mode value) {
+        this.mode = value;
+    }
+ }
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 f0315b3..44e16d4 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
@@ -26,6 +26,7 @@
 import org.apache.sling.feature.extension.apiregions.api.config.ConfigurableEntity;
 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.Mode;
 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;
@@ -54,18 +55,33 @@
      * @return The result
      */
     public ConfigurationValidationResult validate(final Configuration config, final ConfigurableEntity desc, final Region region) {
+        return this.validate(config, desc, region, Mode.STRICT);
+    }
+
+    /**
+     * Validate a configuration
+     * 
+     * @param config The OSGi configuration
+     * @param desc The configuration description 
+     * @param region The optional region for the configuration
+     * @param mode The optional validation mode. This is used if the configuration/property has no mode is set. Defaults to {@link Mode#STRICT}.
+     * @return The result
+     * @since 1.2
+     */
+    public ConfigurationValidationResult validate(final Configuration config, final ConfigurableEntity desc, 
+            final Region region, final Mode mode) {
         final ConfigurationValidationResult result = new ConfigurationValidationResult();
         if ( config.isFactoryConfiguration() ) {
             if ( !(desc instanceof FactoryConfigurationDescription) ) {
                 result.getErrors().add("Factory configuration cannot be validated against non factory configuration description");
             } else {
-                validateProperties(config, desc, result.getPropertyResults(), region);
+                validateProperties(config, desc, result.getPropertyResults(), region, mode);
             }
         } else {
             if ( !(desc instanceof ConfigurationDescription) ) {
                 result.getErrors().add("Configuration cannot be validated against factory configuration description");
             } else {
-                validateProperties(config, desc, result.getPropertyResults(), region);
+                validateProperties(config, desc, result.getPropertyResults(), region, mode);
             }
         }
 
@@ -85,12 +101,13 @@
     void validateProperties(final Configuration configuration,
             final ConfigurableEntity desc,  
             final Map<String, PropertyValidationResult> results,
-            final Region region) {
+            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());
+            final PropertyValidationResult result = propertyValidator.validate(value, propEntry.getValue(), mode);
             results.put(propEntry.getKey(), result);
         }
         // validate additional properties
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 8237869..919511d 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
@@ -96,7 +96,7 @@
                 if ( config.isFactoryConfiguration() ) {
                     final FactoryConfigurationDescription desc = api.getFactoryConfigurationDescriptions().get(config.getFactoryPid());
                     if ( desc != null ) {
-                        final ConfigurationValidationResult r = configurationValidator.validate(config, desc, regionInfo.region);
+                        final ConfigurationValidationResult r = configurationValidator.validate(config, desc, regionInfo.region, api.getMode());
                         result.getConfigurationResults().put(config.getPid(), r);
                         if ( regionInfo.region != Region.INTERNAL ) {
                             if ( desc.getOperations().isEmpty() ) {
@@ -121,7 +121,7 @@
                 } else {
                     final ConfigurationDescription desc = api.getConfigurationDescriptions().get(config.getPid());
                     if ( desc != null ) {
-                        final ConfigurationValidationResult r = configurationValidator.validate(config, desc, regionInfo.region);
+                        final ConfigurationValidationResult r = configurationValidator.validate(config, desc, regionInfo.region, api.getMode());
                         result.getConfigurationResults().put(config.getPid(), r);
                     } else if ( regionInfo.region!= Region.INTERNAL && api.getInternalConfigurations().contains(config.getPid())) {
                         final ConfigurationValidationResult cvr = new ConfigurationValidationResult();
@@ -144,7 +144,7 @@
             } else {
                 final FrameworkPropertyDescription fpd = api.getFrameworkPropertyDescriptions().get(frameworkProperty);
                 if ( fpd != null ) {
-                    final PropertyValidationResult pvr = propertyValidator.validate(feature.getFrameworkProperties().get(frameworkProperty), fpd);
+                    final PropertyValidationResult pvr = propertyValidator.validate(feature.getFrameworkProperties().get(frameworkProperty), fpd, api.getMode());
                     result.getFrameworkPropertyResults().put(frameworkProperty, pvr);
                 } else if ( regionInfo.region != Region.INTERNAL && api.getInternalFrameworkProperties().contains(frameworkProperty) ) {
                     final PropertyValidationResult pvr = new PropertyValidationResult();
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 ecfdb5a..b643d9d 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
@@ -24,6 +24,7 @@
 import java.util.Collection;
 import java.util.List;
 
+import org.apache.sling.feature.extension.apiregions.api.config.Mode;
 import org.apache.sling.feature.extension.apiregions.api.config.Option;
 import org.apache.sling.feature.extension.apiregions.api.config.PropertyDescription;
 import org.apache.sling.feature.extension.apiregions.api.config.PropertyType;
@@ -40,10 +41,25 @@
 	 * @return A property validation result
 	 */
 	public PropertyValidationResult validate(final Object value, final PropertyDescription desc) {
-		final PropertyValidationResult result = new PropertyValidationResult();
-		if ( value == null ) {
+        return this.validate(value, desc, Mode.STRICT);
+    }
+
+    /**
+	 * Validate the value against the property definition
+     * @param value The value to validate
+     * @param desc The property description
+     * @param mode Optional validation mode - this mode is used if the description does not define a mode. Defaults to {@link Mode#STRICT}.
+	 * @return A property validation result
+     * @since 1.2.0
+	 */
+	public PropertyValidationResult validate(final Object value, final PropertyDescription desc, final Mode mode) {
+        final Context context = new Context();
+        context.description = desc;
+        context.validationMode = desc.getMode() != null ? desc.getMode() : (mode != null ? mode : Mode.STRICT);
+
+        if ( value == null ) {
             if ( desc.isRequired() ) {
-                result.getErrors().add("No value provided");
+                setResult(context, "No value provided");
             }
 		} else {
 			final List<Object> values;
@@ -63,36 +79,44 @@
 			} else {
 				// single value
 				values = null;
-				validateValue(desc, value, result);
+				validateValue(context, value);
 			}
 
 			if ( values != null ) {
                 // array or collection
                 for(final Object val : values) {
-                    validateValue(desc, val, result);
+                    validateValue(context, val);
                 }
-                validateList(desc, values, result.getErrors());
+                validateList(context, values);
             }
             
             if ( desc.getDeprecated() != null ) {
-                result.getWarnings().add(desc.getDeprecated());
+                context.result.getWarnings().add(desc.getDeprecated());
             }
 		}
-		return result;
+		return context.result;
 	}
 
+    void setResult(final Context context, final String msg) {
+        if ( context.validationMode == Mode.STRICT ) {
+            context.result.getErrors().add(msg);
+        } else if ( context.validationMode == Mode.LENIENT || context.validationMode == Mode.DEFINITIVE ) {
+            context.result.getWarnings().add(msg);
+        }
+    }
+
     /**
      * Validate a multi value
      * @param prop The property description
      * @param values The values
      * @param messages The messages to record errors
      */
-    void validateList(final PropertyDescription prop, final List<Object> values, final List<String> messages) {
-        if ( prop.getCardinality() > 0 && values.size() > prop.getCardinality() ) {
-            messages.add("Array/collection contains too many elements, only " + prop.getCardinality() + " allowed");
+    void validateList(final Context context, final List<Object> values) {
+        if ( context.description.getCardinality() > 0 && values.size() > context.description.getCardinality() ) {
+            setResult(context, "Array/collection contains too many elements, only " + context.description.getCardinality() + " allowed");
         }
-        if ( prop.getIncludes() != null ) {
-            for(final String inc : prop.getIncludes()) {
+        if ( context.description.getIncludes() != null ) {
+            for(final String inc : context.description.getIncludes()) {
                 boolean found = false;
                 for(final Object val : values) {
                     if ( inc.equals(val.toString())) {
@@ -101,12 +125,12 @@
                     }
                 }
                 if ( !found ) {
-                    messages.add("Required included value " + inc + " not found");
+                    setResult(context, "Required included value " + inc + " not found");
                 }
             }
         }
-        if ( prop.getExcludes() != null ) {
-            for(final String exc : prop.getExcludes()) {
+        if ( context.description.getExcludes() != null ) {
+            for(final String exc : context.description.getExcludes()) {
                 boolean found = false;
                 for(final Object val : values) {
                     if ( exc.equals(val.toString())) {
@@ -115,7 +139,7 @@
                     }
                 }
                 if ( found ) {
-                    messages.add("Required excluded value " + exc + " found");
+                    setResult(context, "Required excluded value " + exc + " found");
                 }
             }
         }
@@ -123,8 +147,7 @@
 
     private static final List<String> PLACEHOLDERS = Arrays.asList("$[env:", "$[secret:", "$[prop:");
 
-	void validateValue(final PropertyDescription desc, final Object value, final PropertyValidationResult result) {
-        final List<String> messages = result.getErrors();
+	void validateValue(final Context context, final Object value) {
 		if ( value != null ) {
             // check for placeholder
             boolean hasPlaceholder = false;
@@ -138,285 +161,294 @@
                 }
             }
             if ( !hasPlaceholder ) {
-                switch ( desc.getType() ) {
-                    case BOOLEAN : validateBoolean(desc, value, messages);
+                switch ( context.description.getType() ) {
+                    case BOOLEAN : validateBoolean(context, value);
                                 break;
-                    case BYTE : validateByte(desc, value, messages);
+                    case BYTE : validateByte(context, value);
                                 break;
-                    case CHARACTER : validateCharacter(desc, value, messages);
+                    case CHARACTER : validateCharacter(context, value);
                                 break;
-                    case DOUBLE : validateDouble(desc, value, messages); 
+                    case DOUBLE : validateDouble(context, value); 
                                 break;
-                    case FLOAT : validateFloat(desc, value, messages); 
+                    case FLOAT : validateFloat(context, value); 
                                 break;
-                    case INTEGER : validateInteger(desc, value, messages);
+                    case INTEGER : validateInteger(context, value);
                                 break;
-                    case LONG : validateLong(desc, value, messages);
+                    case LONG : validateLong(context, value);
                                 break;
-                    case SHORT : validateShort(desc, value, messages);
+                    case SHORT : validateShort(context, value);
                                 break;
-                    case STRING : validateRequired(desc, value, messages);
+                    case STRING : validateRequired(context, value);
                                 break;
-                    case EMAIL : validateEmail(desc, value, messages); 
+                    case EMAIL : validateEmail(context, value); 
                                 break;
-                    case PASSWORD : validatePassword(desc, value, messages, false);
+                    case PASSWORD : validatePassword(context, value, false);
                                 break;
-                    case URL : validateURL(desc, value, messages);
+                    case URL : validateURL(context, value);
                             break;
-                    case PATH : validatePath(desc, value, messages);
+                    case PATH : validatePath(context, value);
                                 break;
-                    default : messages.add("Unable to validate value - unknown property type : " + desc.getType());
+                    default : context.result.getErrors().add("Unable to validate value - unknown property type : " + context.description.getType());
                 }
-                validateRegex(desc, value, messages);
-                validateOptions(desc, value, messages);                
+                validateRegex(context, value);
+                validateOptions(context, value);                
             } else {
                 // placeholder is present
-                if ( desc.getType() == PropertyType.PASSWORD ) {
-                    validatePassword(desc, value, messages, true);
-                } else if ( desc.getType() == PropertyType.STRING ) {
+                if ( context.description.getType() == PropertyType.PASSWORD ) {
+                    validatePassword(context, value, true);
+                } else if ( context.description.getType() == PropertyType.STRING ) {
                     // any string is valid, we only mark the result as skipped if a regex or options are set
-                    if ( desc.getRegex() != null || desc.getOptions() != null || desc.isRequired() ) {
-                        result.markSkipped();
+                    if ( context.description.getRegex() != null || context.description.getOptions() != null || context.description.isRequired() ) {
+                        context.result.markSkipped();
                     }
                 } else {
-                    result.markSkipped();
+                    context.result.markSkipped();
                 }
             }
         } else {
-			messages.add("Null value provided for validation");
+			setResult(context, "Null value provided for validation");
 		}
 	}
 	
-	void validateRequired(final PropertyDescription prop, final Object value, final List<String> messages) {
-        if ( prop.isRequired() ) {
+	void validateRequired(final Context context, final Object value) {
+        if ( context.description.isRequired() ) {
             final String val = value.toString();
             if ( val.isEmpty() ) {
-                messages.add("Value is required");
+                setResult(context, "Value is required");
             }
         }
     }
 
-    void validateBoolean(final PropertyDescription prop, final Object value, final List<String> messages) {
+    void validateBoolean(final Context context, final Object value) {
         if ( ! (value instanceof Boolean) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				if ( ! v.equalsIgnoreCase("true") && !v.equalsIgnoreCase("false") ) {
-					messages.add("Boolean value must either be true or false, but not " + value);
+                    setResult(context, "Boolean value must either be true or false, but not " + value);
 				}
 			} else {
-				messages.add("Boolean value must either be of type Boolean or String : " + value);
+				setResult(context, "Boolean value must either be of type Boolean or String : " + value);
 			}
 		}
 	}
 
-	void validateByte(final PropertyDescription prop, final Object value, final List<String> messages) {
+	void validateByte(final Context context, final Object value) {
         if ( ! (value instanceof Byte) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				try {
-					validateRange(prop, Byte.valueOf(v), messages);
+					validateRange(context, Byte.valueOf(v));
 				} catch ( final NumberFormatException nfe ) {
-                    messages.add("Value is not a valid Byte : " + value);
+                    setResult(context, "Value is not a valid Byte : " + value);
                 }
             } else if ( value instanceof Number ) {
-                validateRange(prop, ((Number)value).byteValue(), messages);            
+                validateRange(context, ((Number)value).byteValue());            
 			} else {
-				messages.add("Byte value must either be of type Byte or String : " + value);
+				setResult(context, "Byte value must either be of type Byte or String : " + value);
 			}
 		} else {
-			validateRange(prop, (Byte)value, messages);
+			validateRange(context, (Byte)value);
 		}
 	}
 
-	void validateShort(final PropertyDescription prop, final Object value, final List<String> messages) {
+	void validateShort(final Context context, final Object value) {
         if ( ! (value instanceof Short) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				try {
-					validateRange(prop, Short.valueOf(v), messages);
+					validateRange(context, Short.valueOf(v));
 				} catch ( final NumberFormatException nfe ) {
-                    messages.add("Value is not a valid Short : " + value);
+                    setResult(context, "Value is not a valid Short : " + value);
 				}
             } else if ( value instanceof Number ) {
-                validateRange(prop, ((Number)value).shortValue(), messages);            
+                validateRange(context, ((Number)value).shortValue());            
 			} else {
-				messages.add("Short value must either be of type Short or String : " + value);
+				setResult(context, "Short value must either be of type Short or String : " + value);
 			}
 		} else {
-			validateRange(prop, (Short)value, messages);
+			validateRange(context, (Short)value);
 		}
 	}
 
-	void validateInteger(final PropertyDescription prop, final Object value, final List<String> messages) {
+	void validateInteger(final Context context, final Object value) {
         if ( ! (value instanceof Integer) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				try {
-					validateRange(prop, Integer.valueOf(v), messages);
+					validateRange(context, Integer.valueOf(v));
 				} catch ( final NumberFormatException nfe ) {
-                    messages.add("Value is not a valid Integer : " + value);
+                    setResult(context, "Value is not a valid Integer : " + value);
 				}
             } else if ( value instanceof Number ) {
-                validateRange(prop, ((Number)value).intValue(), messages);            
+                validateRange(context, ((Number)value).intValue());            
 			} else {
-				messages.add("Integer value must either be of type Integer or String : " + value);
+				setResult(context, "Integer value must either be of type Integer or String : " + value);
 			}
 		} else {
-			validateRange(prop, (Integer)value, messages);
+			validateRange(context, (Integer)value);
 		}
 	}
 
-	void validateLong(final PropertyDescription prop, final Object value, final List<String> messages) {
+	void validateLong(final Context context, final Object value) {
         if ( ! (value instanceof Long) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				try {
-					validateRange(prop, Long.valueOf(v), messages);
+					validateRange(context, Long.valueOf(v));
 				} catch ( final NumberFormatException nfe ) {
-                    messages.add("Value is not a valid Long : " + value);
+                    setResult(context, "Value is not a valid Long : " + value);
 				}
             } else if ( value instanceof Number ) {
-                validateRange(prop, ((Number)value).longValue(), messages);            
+                validateRange(context, ((Number)value).longValue());            
 			} else {
-				messages.add("Long value must either be of type Long or String : " + value);
+				setResult(context, "Long value must either be of type Long or String : " + value);
 			}
 		} else {
-			validateRange(prop, (Long)value, messages);
+			validateRange(context, (Long)value);
 		}
 	}
 
-	void validateFloat(final PropertyDescription prop, final Object value, final List<String> messages) {
+	void validateFloat(final Context context, final Object value) {
         if ( ! (value instanceof Float) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				try {
-					validateRange(prop, Float.valueOf(v), messages);
+					validateRange(context, Float.valueOf(v));
 				} catch ( final NumberFormatException nfe ) {
-                    messages.add("Value is not a valid Float : " + value);
+                    setResult(context, "Value is not a valid Float : " + value);
 				}
             } else if ( value instanceof Number ) {
-                validateRange(prop, ((Number)value).floatValue(), messages);            
+                validateRange(context, ((Number)value).floatValue());            
 			} else {
-				messages.add("Float value must either be of type Float or String : " + value);
+				setResult(context, "Float value must either be of type Float or String : " + value);
 			}
 		} else {
-			validateRange(prop, (Float)value, messages);
+			validateRange(context, (Float)value);
 		}
 	}
 
-	void validateDouble(final PropertyDescription prop, final Object value, final List<String> messages) {
+	void validateDouble(final Context context, final Object value) {
         if ( ! (value instanceof Double) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				try {
-					validateRange(prop, Double.valueOf(v), messages);
+					validateRange(context, Double.valueOf(v));
 				} catch ( final NumberFormatException nfe ) {
-                    messages.add("Value is not a valid Double : " + value);
+                    setResult(context, "Value is not a valid Double : " + value);
 				}
             } else if ( value instanceof Number ) {
-                validateRange(prop, ((Number)value).doubleValue(), messages);            
+                validateRange(context, ((Number)value).doubleValue());            
 			} else {
-				messages.add("Double value must either be of type Double or String : " + value);
+				setResult(context, "Double value must either be of type Double or String : " + value);
 			}
 		} else {
-			validateRange(prop, (Double)value, messages);
+			validateRange(context, (Double)value);
 		}
 	}
 
-	void validateCharacter(final PropertyDescription prop, final Object value, final List<String> messages) {
+	void validateCharacter(final Context context, final Object value) {
         if ( ! (value instanceof Character) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				if ( v.length() > 1 ) {
-                    messages.add("Value is not a valid Character : " + value);
+                    setResult(context, "Value is not a valid Character : " + value);
 				}
 			} else {
-				messages.add("Character value must either be of type Character or String : " + value);
+				setResult(context, "Character value must either be of type Character or String : " + value);
 			}
 		}
 	}
 
-	void validateURL(final PropertyDescription prop, final Object value, final List<String> messages) {
+	void validateURL(final Context context, final Object value) {
 		final String val = value.toString();
 		try {
 			new URL(val);
 		} catch ( final MalformedURLException mue) {
-			messages.add("Value is not a valid URL : " + val);
+			setResult(context, "Value is not a valid URL : " + val);
 		}
 	}
 
-	void validateEmail(final PropertyDescription prop, final Object value, final List<String> messages) {
+	void validateEmail(final Context context, final Object value) {
 		final String val = value.toString();
 		// poor man's validation (should probably use InternetAddress)
 		if ( !val.contains("@") ) {
-			messages.add("Not a valid email address " + val);
+			setResult(context, "Not a valid email address " + val);
 		}
 	}
 
-	void validatePassword(final PropertyDescription desc, final Object value, final List<String> messages, final boolean hasPlaceholder) {
+	void validatePassword(final Context context, final Object value, final boolean hasPlaceholder) {
         if ( !hasPlaceholder ) {
-            messages.add("Value for a password must use a placeholder");
+            setResult(context, "Value for a password must use a placeholder");
         }
 	}
 
-	void validatePath(final PropertyDescription prop, final Object value, final List<String> messages) {
+	void validatePath(final Context context, final Object value) {
 		final String val = value.toString();
 		// poor man's validation 
 		if ( !val.startsWith("/") ) {
-			messages.add("Not a valid path " + val);
+			setResult(context, "Not a valid path " + val);
 		}
 	}
 
-    void validateRange(final PropertyDescription prop, final Number value, final List<String> messages) {
-	    if ( prop.getRange() != null ) {
-            if ( prop.getRange().getMin() != null ) {
+    void validateRange(final Context context, final Number value) {
+	    if ( context.description.getRange() != null ) {
+            if ( context.description.getRange().getMin() != null ) {
                 if ( value instanceof Float || value instanceof Double ) {
-                    final double min = prop.getRange().getMin().doubleValue();
+                    final double min = context.description.getRange().getMin().doubleValue();
                     if ( value.doubleValue() < min ) {
-                            messages.add("Value " + value + " is too low; should not be lower than " + prop.getRange().getMin());
+                            setResult(context, "Value " + value + " is too low; should not be lower than " + context.description.getRange().getMin());
                     }    
                 } else {
-                    final long min = prop.getRange().getMin().longValue();
+                    final long min = context.description.getRange().getMin().longValue();
                     if ( value.longValue() < min ) {
-                            messages.add("Value " + value + " is too low; should not be lower than " + prop.getRange().getMin());
+                        setResult(context, "Value " + value + " is too low; should not be lower than " + context.description.getRange().getMin());
                     }    
                 }
             }
-            if ( prop.getRange().getMax() != null ) {
+            if ( context.description.getRange().getMax() != null ) {
                 if ( value instanceof Float || value instanceof Double ) {
-                    final double max = prop.getRange().getMax().doubleValue();
+                    final double max = context.description.getRange().getMax().doubleValue();
                     if ( value.doubleValue() > max ) {
-                        messages.add("Value " + value + " is too high; should not be higher than " + prop.getRange().getMax());
+                        setResult(context, "Value " + value + " is too high; should not be higher than " + context.description.getRange().getMax());
                     }    
                 } else {
-                    final long max = prop.getRange().getMax().longValue();
+                    final long max = context.description.getRange().getMax().longValue();
                     if ( value.longValue() > max ) {
-                        messages.add("Value " + value + " is too high; should not be higher than " + prop.getRange().getMax());
+                        setResult(context, "Value " + value + " is too high; should not be higher than " + context.description.getRange().getMax());
                     }    
                 }
             }
 		}
 	}
 
-    void validateRegex(final PropertyDescription prop, final Object value, final List<String> messages) {
-        if ( prop.getRegexPattern() != null ) {
-            if ( !prop.getRegexPattern().matcher(value.toString()).matches() ) {
-                messages.add("Value " + value + " does not match regex " + prop.getRegex());
+    void validateRegex(final Context context, final Object value) {
+        if ( context.description.getRegexPattern() != null ) {
+            if ( !context.description.getRegexPattern().matcher(value.toString()).matches() ) {
+                setResult(context, "Value " + value + " does not match regex " + context.description.getRegex());
             }
         }
     }
 
-    void validateOptions(final PropertyDescription prop, final Object value, final List<String> messages) {
-        if ( prop.getOptions() != null ) {
+    void validateOptions(final Context context, final Object value) {
+        if ( context.description.getOptions() != null ) {
             boolean found = false;
-            for(final Option opt : prop.getOptions()) {
+            for(final Option opt : context.description.getOptions()) {
                 if ( opt.getValue().equals(value.toString() ) ) {
                     found = true; 
                 }
             }
             if ( !found ) {
-                messages.add("Value " + value + " does not match provided options");
+                setResult(context, "Value " + value + " does not match provided options");
             }
         }
     }
+
+    static final class Context {
+
+        public final PropertyValidationResult result = new PropertyValidationResult();
+
+        public PropertyDescription description;
+
+        public Mode validationMode;
+    }
 }
\ No newline at end of file
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 69a037c..0cc3da8 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.1.0")
+@org.osgi.annotation.versioning.Version("1.2.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 10851d5..b9ea7d7 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
@@ -43,12 +43,14 @@
         entity.setTitle("t");
         entity.setDescription("x");
         entity.getPropertyDescriptions().put("a", new PropertyDescription());
+        entity.setMode(Mode.SILENT);
         entity.clear();
         assertTrue(entity.getAttributes().isEmpty());
         assertNull(entity.getDeprecated());
         assertNull(entity.getTitle());
         assertNull(entity.getDescription());
         assertTrue(entity.getPropertyDescriptions().isEmpty());
+        assertNull(entity.getMode());
     }
 
     @Test public void testFromJSONObject() throws IOException {
@@ -81,4 +83,17 @@
         final CE entity = new CE();
         entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
     }
+
+    @Test public void testSerialisingMode() throws IOException {
+        final CE entity = new CE();
+        entity.setMode(Mode.SILENT);
+
+        final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"mode\" : \"SILENT\"}");
+
+        assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());
+        entity.clear();
+        entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
+        assertEquals(Mode.SILENT, entity.getMode());
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApiTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApiTest.java
index f6ca5f0..ff16509 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApiTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApiTest.java
@@ -77,6 +77,7 @@
         entity.getInternalFrameworkProperties().add("iprop");
         entity.setRegion(Region.GLOBAL);
         entity.getFeatureToRegionCache().put(ArtifactId.parse("g:a:1"), Region.GLOBAL);
+        entity.setMode(Mode.SILENT);
         entity.clear();
         assertTrue(entity.getAttributes().isEmpty());
         assertTrue(entity.getConfigurationDescriptions().isEmpty());
@@ -87,6 +88,7 @@
         assertTrue(entity.getInternalFrameworkProperties().isEmpty());
         assertNull(entity.getRegion());
         assertTrue(entity.getFeatureToRegionCache().isEmpty());
+        assertEquals(Mode.STRICT, entity.getMode());
     }
 
     @Test public void testFromJSONObject() throws IOException {
@@ -118,6 +120,7 @@
         assertEquals(Region.INTERNAL, entity.getRegion());
         assertEquals(Region.INTERNAL, entity.getFeatureToRegionCache().get(ArtifactId.parse("g:a1:feature:1.0.0")));
         assertEquals(Region.GLOBAL, entity.getFeatureToRegionCache().get(ArtifactId.parse("g:a2:feature:1.7.3")));
+        assertEquals(Mode.STRICT, entity.getMode());
     }
 
     @Test public void testToJSONObject() throws IOException {
@@ -132,7 +135,7 @@
         entity.setRegion(Region.INTERNAL);
         entity.getFeatureToRegionCache().put(ArtifactId.parse("g:a1:feature:1.0.0"), Region.INTERNAL);
         entity.getFeatureToRegionCache().put(ArtifactId.parse("g:a2:feature:1.7.3"), Region.GLOBAL);
-
+        entity.setMode(Mode.SILENT);
         final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
         ext.setJSON("{ \"a\" : 5, \"configurations\" : { \"pid\": {}}, " +
             "\"factory-configurations\" : { \"factory\" : {}}," +
@@ -141,7 +144,8 @@
             "\"internal-factory-configurations\" : [\"ifactory\"],"+
             "\"internal-framework-properties\" : [\"iprop\"],"+
             "\"region\" : \"INTERNAL\","+
-            "\"region-cache\" : {\"g:a1:feature:1.0.0\" : \"INTERNAL\", \"g:a2:feature:1.7.3\" : \"GLOBAL\"}}");
+            "\"region-cache\" : {\"g:a1:feature:1.0.0\" : \"INTERNAL\", \"g:a2:feature:1.7.3\" : \"GLOBAL\"}," +
+            "\"mode\" : \"SILENT\"}");
 
         assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());        
     }
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescriptionTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescriptionTest.java
index 4fa0583..2e90b3b 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescriptionTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescriptionTest.java
@@ -52,6 +52,7 @@
         entity.setVariable("var");
         entity.setType(PropertyType.BYTE);        
         entity.setDefaultValue("default");
+        entity.setMode(Mode.SILENT);
         entity.clear();
         assertTrue(entity.getAttributes().isEmpty());
         assertNull(entity.getDeprecated());
@@ -68,6 +69,7 @@
         assertFalse(entity.isRequired());
         assertEquals(PropertyType.STRING, entity.getType());
         assertNull(entity.getDefaultValue());
+        assertNull(entity.getMode());
     }
 
     @Test public void testFromJSONObject() throws IOException {
@@ -164,4 +166,17 @@
             // expected
         }
     }
+
+    @Test public void testSerialisingMode() throws IOException {
+        final PropertyDescription entity = new PropertyDescription();
+        entity.setMode(Mode.SILENT);
+
+        final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"mode\" : \"SILENT\"}");
+
+        assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());
+        entity.clear();
+        entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
+        assertEquals(Mode.SILENT, entity.getMode());
+    }
 }
\ No newline at end of file
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 dd544bd..b2803f4 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
@@ -24,6 +24,7 @@
 import java.util.Collections;
 import java.util.List;
 
+import org.apache.sling.feature.extension.apiregions.api.config.Mode;
 import org.apache.sling.feature.extension.apiregions.api.config.Option;
 import org.apache.sling.feature.extension.apiregions.api.config.PropertyDescription;
 import org.apache.sling.feature.extension.apiregions.api.config.PropertyType;
@@ -34,289 +35,205 @@
     
     private final PropertyValidator validator = new PropertyValidator();
     
-    @Test public void testValidateWithNull() {
-        final PropertyDescription prop = new PropertyDescription();
+    /**
+     * Helper method to validate an error based on the validation mode
+     */
+    private void validateError(final PropertyDescription prop, final Object value) {
+        validateError(prop, value, 1);
+    }
+    
+    /**
+     * Helper method to validate an error based on the validation mode
+     */
+    private void validateError(final PropertyDescription prop, final Object value, final int errors) {
         PropertyValidationResult result;
 
-        // prop not required - no error
-        result = validator.validate(null, prop);
-        assertTrue(result.getErrors().isEmpty());
+        // error - strict mode 
+        result = validator.validate(value, prop, Mode.STRICT);
+        assertEquals(errors, result.getErrors().size());
+        assertFalse(result.isValid());
+        assertFalse(result.isSkipped());
+
+        // error - mode lenient
+        result = validator.validate(value, prop, Mode.LENIENT);
+        assertEquals(errors, result.getWarnings().size());
         assertTrue(result.isValid());
         assertFalse(result.isSkipped());
 
+        // error - mode silent
+        result = validator.validate(value, prop, Mode.SILENT);
+        assertTrue(result.getWarnings().isEmpty());
+        assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
+
+        // error - mode definitive
+        result = validator.validate(value, prop, Mode.DEFINITIVE);
+        assertEquals(errors, result.getWarnings().size());
+        assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
+
+        // error - mode silent definitive 
+        result = validator.validate(value, prop, Mode.SILENT_DEFINITIVE);
+        assertTrue(result.getWarnings().isEmpty());
+        assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
+    }
+
+    /**
+     * Helper method to validate that a value is valid and not skipped
+     */
+    private void validateValid(final PropertyDescription prop, final Object value) {
+        final PropertyValidationResult result = validator.validate(value, prop);
+        assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
+        assertTrue(result.getErrors().isEmpty());
+    }
+
+    @Test public void testValidateWithNull() {
+        final PropertyDescription prop = new PropertyDescription();
+
+        // prop not required - no error
+        validateValid(prop, null);
+
         // prop required - error
         prop.setRequired(true);
-        result = validator.validate(null, prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isValid());
-        assertFalse(result.isSkipped());
+        validateError(prop, null);
     }
 
     @Test public void testValidateBoolean() {
         final PropertyDescription prop = new PropertyDescription();
         prop.setType(PropertyType.BOOLEAN);
 
-        PropertyValidationResult result;
-        result = validator.validate(Boolean.TRUE, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateValid(prop, Boolean.TRUE);
+        validateValid(prop, Boolean.FALSE);
+        validateValid(prop, "TRUE");
+        validateValid(prop, "FALSE");
 
-        result = validator.validate(Boolean.FALSE, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("TRUE", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("FALSE", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("yes", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate(1, prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
+        validateError(prop, "yes");
+        validateError(prop, 1);
     }
 
     @Test public void testValidateByte() {
         final PropertyDescription prop = new PropertyDescription();
         prop.setType(PropertyType.BYTE);
 
-        PropertyValidationResult result;
+        validateValid(prop, (byte)1);
+        validateValid(prop, "1");
+        validateValid(prop, 1);
 
-        result = validator.validate((byte)1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("1", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("yes", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate(1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateError(prop, "yes");
     }
 
     @Test public void testValidateShort() {
         final PropertyDescription prop = new PropertyDescription();
         prop.setType(PropertyType.SHORT);
 
-        PropertyValidationResult result;
+        validateValid(prop, (short)1);
+        validateValid(prop, "1");
+        validateValid(prop, 1);
 
-        result = validator.validate((short)1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("1", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("yes", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate(1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateError(prop, "yes");
     }
 
     @Test public void testValidateInteger() {
         final PropertyDescription prop = new PropertyDescription();
         prop.setType(PropertyType.INTEGER);
 
-        PropertyValidationResult result;
+        validateValid(prop, "1");
+        validateValid(prop, 1);
 
-        result = validator.validate(1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("1", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("yes", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate(1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateError(prop, "yes");
     }
 
     @Test public void testValidateLong() {
         final PropertyDescription prop = new PropertyDescription();
         prop.setType(PropertyType.LONG);
 
-        PropertyValidationResult result;
+        validateValid(prop, 1L);
+        validateValid(prop, "1");
+        validateValid(prop, 1);
 
-        result = validator.validate(1L, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("1", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("yes", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate(1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateError(prop, "yes");
     }
 
     @Test public void testValidateFloat() {
         final PropertyDescription prop = new PropertyDescription();
         prop.setType(PropertyType.FLOAT);
 
-        PropertyValidationResult result;
+        validateValid(prop, 1.1);
+        validateValid(prop, "1.1");
+        validateValid(prop, 1);
 
-        result = validator.validate(1.1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("1.1", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("yes", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate(1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateError(prop, "yes");
     }
 
     @Test public void testValidateDouble() {
         final PropertyDescription prop = new PropertyDescription();
         prop.setType(PropertyType.DOUBLE);
 
-        PropertyValidationResult result;
+        validateValid(prop, 1.1d);
+        validateValid(prop, "1.1");
+        validateValid(prop, 1);
 
-        result = validator.validate(1.1d, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("1.1", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("yes", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate(1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateError(prop, "yes");
     }
 
     @Test public void testValidateChar() {
         final PropertyDescription prop = new PropertyDescription();
         prop.setType(PropertyType.CHARACTER);
 
-        PropertyValidationResult result;
+        validateValid(prop, 'x');
+        validateValid(prop, "y");
 
-        result = validator.validate('x', prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("y", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("yes", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
+        validateError(prop, "yes");
     }
 
     @Test public void testValidateUrl() {
         final PropertyDescription prop = new PropertyDescription();
         prop.setType(PropertyType.URL);
 
-        PropertyValidationResult result;
+        validateValid(prop, "https://sling.apache.org/documentation");
 
-        result = validator.validate("https://sling.apache.org/documentation", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("hello world", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
+        validateError(prop, "hello world");
     }
 
     @Test public void testValidateEmail() {
         final PropertyDescription prop = new PropertyDescription();
         prop.setType(PropertyType.EMAIL);
 
-        PropertyValidationResult result;
+        validateValid(prop, "a@b.com");
 
-        result = validator.validate("a@b.com", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("hello world", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
+        validateError(prop, "hello world");
     }
 
     @Test public void testValidatePassword() {
         final PropertyDescription prop = new PropertyDescription();
         prop.setType(PropertyType.PASSWORD);
 
-        PropertyValidationResult result;
+        validateValid(prop, null);
+        validateValid(prop, "$[secret:dbpassword]");
 
-        result = validator.validate(null, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("secret", prop);
-        assertFalse(result.isValid());
-
-        result = validator.validate("$[secret:dbpassword]", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateError(prop, "secret");
     }
 
     @Test public void testValidatePath() {
         final PropertyDescription prop = new PropertyDescription();
         prop.setType(PropertyType.PATH);
 
-        PropertyValidationResult result;
+        validateValid(prop, "/a/b/c");
 
-        result = validator.validate("/a/b/c", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("hello world", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
+        validateError(prop, "hello world");
     }
     
     @Test public void testValidateString() {
         final PropertyDescription desc = new PropertyDescription();
-        PropertyValidationResult result;
 
-        result = validator.validate("hello world", desc);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("$[prop:KEY]", desc);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateValid(desc, "hello world");
+        validateValid(desc, "$[prop:KEY]");
 
         // skip if required
         desc.setRequired(true);
-        result = validator.validate("$[prop:KEY]", desc);
+        PropertyValidationResult result = validator.validate("$[prop:KEY]", desc);
         assertTrue(result.isValid());
         assertTrue(result.isSkipped());
         desc.setRequired(false);
@@ -336,96 +253,72 @@
         desc.setRegex(null);
 
         // empty string - not required
-        result = validator.validate("", desc);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateValid(desc, "");
 
         // empty string - required
         desc.setRequired(true);
-        result = validator.validate("", desc);
-        assertFalse(result.isValid());
-        assertFalse(result.isSkipped());
+        validateError(desc, "");
         desc.setRequired(false);
     }
 
     @Test public void testValidateRange() {
-        final List<String> messages = new ArrayList<>();
-        final PropertyDescription prop = new PropertyDescription();
-
+        final PropertyDescription description = new PropertyDescription();
+        description.setType(PropertyType.INTEGER);
+         
         // no range set
-        validator.validateRange(prop, 2, messages);
-        assertTrue(messages.isEmpty());
+        validateValid(description, 2);
 
         // empty range set
-        prop.setRange(new Range());
-        validator.validateRange(prop, 2, messages);
-        assertTrue(messages.isEmpty());
+        description.setRange(new Range());
+        validateValid(description, 2);
 
         // min set
-        prop.getRange().setMin(5);
-        validator.validateRange(prop, 5, messages);
-        assertTrue(messages.isEmpty());
-        validator.validateRange(prop, 6, messages);
-        assertTrue(messages.isEmpty());
-        validator.validateRange(prop, 4, messages);
-        assertEquals(1, messages.size());
-        messages.clear();
+        description.getRange().setMin(5);
+        validateValid(description, 5);
+        validateValid(description, 6);
 
-        validator.validateRange(prop, 5.0, messages);
-        assertTrue(messages.isEmpty());
-        validator.validateRange(prop, 6.0, messages);
-        assertTrue(messages.isEmpty());
-        validator.validateRange(prop, 4.0, messages);
-        assertEquals(1, messages.size());
-        messages.clear();
+        validateError(description, 4);
+
+        validateValid(description, 5.0);
+        validateValid(description, 6.0);
+
+        validateError(description, 4.0);
 
         // max set
-        prop.getRange().setMax(6);
-        validator.validateRange(prop, 5, messages);
-        assertTrue(messages.isEmpty());
-        validator.validateRange(prop, 6, messages);
-        assertTrue(messages.isEmpty());
-        validator.validateRange(prop, 7, messages);
-        assertEquals(1, messages.size());
-        messages.clear();
+        description.getRange().setMax(6);
+        validateValid(description, 5);
+        validateValid(description, 6);
 
-        validator.validateRange(prop, 5.0, messages);
-        assertTrue(messages.isEmpty());
-        validator.validateRange(prop, 6.0, messages);
-        assertTrue(messages.isEmpty());
-        validator.validateRange(prop, 7.0, messages);
-        assertEquals(1, messages.size());
-        messages.clear();
+        validateError(description, 7);
+
+        validateValid(description, 5.0);
+        validateValid(description, 6.0);
+
+        validateError(description, 7.0);
     }   
     
     @Test public void testValidateRegex() {
-        final List<String> messages = new ArrayList<>();
         final PropertyDescription prop = new PropertyDescription();
 
         // no regex
-        validator.validateRegex(prop, "hello world", messages);
-        validator.validateRegex(prop, "world", messages);
-        assertTrue(messages.isEmpty());
+        validateValid(prop, "hello world");
+        validateValid(prop, "world");
 
         // regex
         prop.setRegex("h(.*)");
-        validator.validateRegex(prop, "hello world", messages);
-        assertTrue(messages.isEmpty());
-        validator.validateRegex(prop, "world", messages);
-        assertEquals(1, messages.size());
-        messages.clear();
+        validateValid(prop, "hello world");
+
+        validateError(prop, "world");
     }
 
     @Test public void testValidateOptions() {
-        final List<String> messages = new ArrayList<>();
         final PropertyDescription prop = new PropertyDescription();
 
         // no options
-        validator.validateOptions(prop, "foo", messages);
-        validator.validateOptions(prop, "bar", messages);
-        assertTrue(messages.isEmpty());
+        validateValid(prop, "foo");
+        validateValid(prop, "bar");
 
-        // options
+        // options - with foo
         final List<Option> options = new ArrayList<>();
         final Option o1 = new Option();
         o1.setValue("foo");
@@ -434,13 +327,10 @@
         options.add(o1);
         options.add(o2);
         prop.setOptions(options);
-        validator.validateOptions(prop, "foo", messages);
-        assertTrue(messages.isEmpty());
-        validator.validateOptions(prop, "bar", messages);
-        assertEquals(1, messages.size());
-        messages.clear();
-        validator.validateOptions(prop, 7, messages);
-        assertTrue(messages.isEmpty());
+
+        validateValid(prop, "foo");
+        validateError(prop, "bar");
+        validateValid(prop, 7);
     }
     
     @Test public void testValidateList() {
@@ -452,49 +342,32 @@
         values.add("c");
 
         // default cardinality - no excludes/includes
-        PropertyValidationResult result;
-        result = validator.validate(values, prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
+        validateError(prop, values);
 
         // cardinality 3 - no excludes/includes
         prop.setCardinality(3);
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertTrue(result.getErrors().isEmpty());
+        validateValid(prop, values);
 
         values.add("d");
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertEquals(1, result.getErrors().size());
+        validateError(prop, values);
 
         // excludes
         prop.setExcludes(new String[] {"d", "e"});
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertEquals(2, result.getErrors().size()); // cardinality and exclude
+        validateError(prop, values, 2);
 
         values.remove("d");
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertTrue(result.getErrors().isEmpty());
+        validateValid(prop, values);
 
         // includes
         prop.setIncludes(new String[] {"b"});
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertTrue(result.getErrors().isEmpty());
+        validateValid(prop, values);
 
         prop.setIncludes(new String[] {"x"});
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertEquals(1, result.getErrors().size());
+        validateError(prop, values);
 
         values.add("x");
         values.remove("a");
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertTrue(result.getErrors().isEmpty());
+        validateValid(prop, values);
     }
 
     @Test public void testValidateArray() {
@@ -503,48 +376,31 @@
         String[] values = new String[] {"a", "b", "c"};
 
         // default cardinality - no excludes/includes
-        PropertyValidationResult result;
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertEquals(1, result.getErrors().size());
+        validateError(prop, values);
 
         // cardinality 3 - no excludes/includes
         prop.setCardinality(3);
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertTrue(result.getErrors().isEmpty());
+        validateValid(prop, values);
 
         values = new String[] {"a", "b", "c", "d"};
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertEquals(1, result.getErrors().size());
+        validateError(prop, values);
 
         // excludes
         prop.setExcludes(new String[] {"d", "e"});
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertEquals(2, result.getErrors().size()); // cardinality and exclude
+        validateError(prop, values, 2);
 
         values = new String[] {"a", "b", "c"};
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertTrue(result.getErrors().isEmpty());
+        validateValid(prop, values);
 
         // includes
         prop.setIncludes(new String[] {"b"});
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertTrue(result.getErrors().isEmpty());
+        validateValid(prop, values);
 
         prop.setIncludes(new String[] {"x"});
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertEquals(1, result.getErrors().size());
+        validateError(prop, values);
 
         values = new String[] {"b", "c", "x"};
-        result = validator.validate(values, prop);
-        assertFalse(result.isSkipped());
-        assertTrue(result.getErrors().isEmpty());
+        validateValid(prop, values);
     }
 
     @Test public void testDeprecation() {