This is an automated email from the ASF dual-hosted git repository.

cziegeler pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-extension-apiregions.git


The following commit(s) were added to refs/heads/master by this push:
     new 5f3bf4d  SLING-10117 : Support different configuration validation modes
5f3bf4d is described below

commit 5f3bf4d74a1b45845ae848edbfbfdc1e3ef25196
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Fri Feb 12 15:59:13 2021 +0100

    SLING-10117 : Support different configuration validation modes
---
 .../apiregions/api/config/ConfigurableEntity.java  |  32 ++
 .../apiregions/api/config/ConfigurationApi.java    |  34 ++
 .../apiregions/api/config/InternalConstants.java   |   2 +
 .../extension/apiregions/api/config/Mode.java      |  40 ++
 .../apiregions/api/config/PropertyDescription.java |  37 +-
 .../config/validation/ConfigurationValidator.java  |  25 +-
 .../api/config/validation/FeatureValidator.java    |   6 +-
 .../api/config/validation/PropertyValidator.java   | 264 ++++++------
 .../api/config/validation/package-info.java        |   2 +-
 .../api/config/ConfigurableEntityTest.java         |  15 +
 .../api/config/ConfigurationApiTest.java           |   8 +-
 .../api/config/PropertyDescriptionTest.java        |  15 +
 .../config/validation/PropertyValidatorTest.java   | 446 +++++++--------------
 13 files changed, 503 insertions(+), 423 deletions(-)

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 @@ public abstract class ConfigurableEntity extends 
DescribableEntity {
     @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 abstract class ConfigurableEntity extends 
DescribableEntity {
        public void clear() {
         super.clear();
                this.properties.clear();
+        this.setMode(null);
     }
 
        /**
@@ -67,6 +74,10 @@ public abstract class ConfigurableEntity extends 
DescribableEntity {
                     }
                 }
             }            
+                       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 @@ public abstract class ConfigurableEntity extends 
DescribableEntity {
     }
 
     /**
+     * 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 @@ public abstract class ConfigurableEntity extends 
DescribableEntity {
                        }
                        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 @@ public class ConfigurationApi extends AttributeableEntity {
     /** 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 @@ public class ConfigurationApi extends AttributeableEntity {
         this.internalFrameworkProperties.clear();
         this.setRegion(null);
         this.getFeatureToRegionCache().clear();
+        this.setMode(Mode.STRICT);
     }
 
        /**
@@ -221,6 +228,11 @@ public class ConfigurationApi extends AttributeableEntity {
                         
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 @@ public class ConfigurationApi extends AttributeableEntity {
     }
 
     /**
+     * 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 @@ public class ConfigurationApi extends AttributeableEntity {
             }
             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 @@ abstract class InternalConstants {
     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 @@ public class PropertyDescription extends DescribableEntity {
      */
     private Object defaultValue;
 
+    /** 
+     * The validation mode. 
+     * @since 1.2
+     */
+    private Mode mode;
+
     /**
      * Create a new description
      */
@@ -96,6 +102,7 @@ public class PropertyDescription extends DescribableEntity {
                this.setOptions(null);
                this.setRegex(null);
         this.setDefaultValue(null);
+        this.setMode(null);
     }
 
        /**
@@ -153,6 +160,10 @@ public class PropertyDescription extends DescribableEntity 
{
             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 @@ public class PropertyDescription extends DescribableEntity 
{
                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 class PropertyDescription extends DescribableEntity 
{
     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.Configuration;
 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 @@ public class ConfigurationValidator {
      * @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 @@ public class ConfigurationValidator {
     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 @@ public class FeatureValidator {
                 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 @@ public class FeatureValidator {
                 } 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 @@ public class FeatureValidator {
             } 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.Arrays;
 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 @@ public class PropertyValidator {
         * @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 @@ public class PropertyValidator {
                        } 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 @@ public class PropertyValidator {
                     }
                 }
                 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 @@ public class PropertyValidator {
                     }
                 }
                 if ( found ) {
-                    messages.add("Required excluded value " + exc + " found");
+                    setResult(context, "Required excluded value " + exc + " 
found");
                 }
             }
         }
@@ -123,8 +147,7 @@ public class PropertyValidator {
 
     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 @@ public class PropertyValidator {
                 }
             }
             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.
  */
 
[email protected]("1.1.0")
[email protected]("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 @@ public class ConfigurableEntityTest {
         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 @@ public class ConfigurableEntityTest {
         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 @@ public class ConfigurationApiTest {
         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 @@ public class ConfigurationApiTest {
         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 @@ public class ConfigurationApiTest {
         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 @@ public class ConfigurationApiTest {
         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 @@ public class ConfigurationApiTest {
             "\"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 @@ public class PropertyDescriptionTest {
         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 @@ public class PropertyDescriptionTest {
         assertFalse(entity.isRequired());
         assertEquals(PropertyType.STRING, entity.getType());
         assertNull(entity.getDefaultValue());
+        assertNull(entity.getMode());
     }
 
     @Test public void testFromJSONObject() throws IOException {
@@ -164,4 +166,17 @@ public class PropertyDescriptionTest {
             // 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.ArrayList;
 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 @@ public class PropertyValidatorTest {
     
     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());
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        // prop required - error
-        prop.setRequired(true);
-        result = validator.validate(null, prop);
-        assertEquals(1, result.getErrors().size());
+        // error - strict mode 
+        result = validator.validate(value, prop, Mode.STRICT);
+        assertEquals(errors, result.getErrors().size());
         assertFalse(result.isValid());
         assertFalse(result.isSkipped());
-    }
 
-    @Test public void testValidateBoolean() {
-        final PropertyDescription prop = new PropertyDescription();
-        prop.setType(PropertyType.BOOLEAN);
-
-        PropertyValidationResult result;
-        result = validator.validate(Boolean.TRUE, prop);
+        // error - mode lenient
+        result = validator.validate(value, prop, Mode.LENIENT);
+        assertEquals(errors, result.getWarnings().size());
         assertTrue(result.isValid());
         assertFalse(result.isSkipped());
 
-        result = validator.validate(Boolean.FALSE, prop);
+        // error - mode silent
+        result = validator.validate(value, prop, Mode.SILENT);
+        assertTrue(result.getWarnings().isEmpty());
         assertTrue(result.isValid());
         assertFalse(result.isSkipped());
 
-        result = validator.validate("TRUE", prop);
+        // error - mode definitive
+        result = validator.validate(value, prop, Mode.DEFINITIVE);
+        assertEquals(errors, result.getWarnings().size());
         assertTrue(result.isValid());
         assertFalse(result.isSkipped());
 
-        result = validator.validate("FALSE", prop);
+        // error - mode silent definitive 
+        result = validator.validate(value, prop, Mode.SILENT_DEFINITIVE);
+        assertTrue(result.getWarnings().isEmpty());
         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());
+    /**
+     * 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 testValidateByte() {
+    @Test public void testValidateWithNull() {
         final PropertyDescription prop = new PropertyDescription();
-        prop.setType(PropertyType.BYTE);
 
-        PropertyValidationResult result;
+        // prop not required - no error
+        validateValid(prop, null);
 
-        result = validator.validate((byte)1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        // prop required - error
+        prop.setRequired(true);
+        validateError(prop, null);
+    }
 
-        result = validator.validate("1", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+    @Test public void testValidateBoolean() {
+        final PropertyDescription prop = new PropertyDescription();
+        prop.setType(PropertyType.BOOLEAN);
 
-        result = validator.validate("yes", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
+        validateValid(prop, Boolean.TRUE);
+        validateValid(prop, Boolean.FALSE);
+        validateValid(prop, "TRUE");
+        validateValid(prop, "FALSE");
 
-        result = validator.validate(1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateError(prop, "yes");
+        validateError(prop, 1);
     }
 
-    @Test public void testValidateShort() {
+    @Test public void testValidateByte() {
         final PropertyDescription prop = new PropertyDescription();
-        prop.setType(PropertyType.SHORT);
+        prop.setType(PropertyType.BYTE);
 
-        PropertyValidationResult result;
+        validateValid(prop, (byte)1);
+        validateValid(prop, "1");
+        validateValid(prop, 1);
 
-        result = validator.validate((short)1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateError(prop, "yes");
+    }
 
-        result = validator.validate("1", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+    @Test public void testValidateShort() {
+        final PropertyDescription prop = new PropertyDescription();
+        prop.setType(PropertyType.SHORT);
 
-        result = validator.validate("yes", prop);
-        assertEquals(1, result.getErrors().size());
-        assertFalse(result.isSkipped());
+        validateValid(prop, (short)1);
+        validateValid(prop, "1");
+        validateValid(prop, 1);
 
-        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;
-
-        result = validator.validate(1.1, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("1.1", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateValid(prop, 1.1);
+        validateValid(prop, "1.1");
+        validateValid(prop, 1);
 
-        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;
-
-        result = validator.validate("[email protected]", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateValid(prop, "[email protected]");
 
-        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;
-
-        result = validator.validate(null, prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
-
-        result = validator.validate("secret", prop);
-        assertFalse(result.isValid());
+        validateValid(prop, null);
+        validateValid(prop, "$[secret:dbpassword]");
 
-        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;
-
-        result = validator.validate("/a/b/c", prop);
-        assertTrue(result.isValid());
-        assertFalse(result.isSkipped());
+        validateValid(prop, "/a/b/c");
 
-        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 @@ public class PropertyValidatorTest {
         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();
-
-        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();
+        description.getRange().setMin(5);
+        validateValid(description, 5);
+        validateValid(description, 6);
+
+        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();
-
-        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();
+        description.getRange().setMax(6);
+        validateValid(description, 5);
+        validateValid(description, 6);
+
+        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 @@ public class PropertyValidatorTest {
         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 @@ public class PropertyValidatorTest {
         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 @@ public class PropertyValidatorTest {
         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() {

Reply via email to