Merge pull request #16 from angulito/SLING-10577-include-enforce-on-and-since-infor-for-config-validations

Add new enforce-on and since attributes for osgi configurations
diff --git a/docs/api-regions.md b/docs/api-regions.md
index c8324f0..106162d 100644
--- a/docs/api-regions.md
+++ b/docs/api-regions.md
@@ -148,7 +148,7 @@
      ]
 ```
 
-The deprecation informnation can just be the message, or it can also include information when the deprecated started (since) and by when the member is expected to be removed (for-removal). The removal information should be either the string `true` or a date in the format `YYYY-MM-DD`.
+The deprecation information can just be the message, or it can also include information when the deprecated started (since) and by when the member is expected to be removed (for-removal). The removal information should be either the string `true` or a date in the format `YYYY-MM-DD`.
 
 ## OSGi Configurations
 
@@ -189,6 +189,8 @@
 * `description` : A human readable description
 * `properties` : An object containing all properties that are allowed to be configured
 * `deprecated` : If this configuration should not be used anymore a human readable message.
+* `since` : Info about when the configuration restriction started. It will be appended at the end of every validation message.
+* `enforce-on` : Info about by when the configuration restriction is expected to be enforced (enforced-on). It will be appended at the end of every validation message.
 * `mode` : Validation mode for the configuration overriding the global one. This mode applies to all properties.
 * `region` : Optional property to restrict the configuration to the internal region if this is set to "INTERNAL". With this set, configurations for the internal region can be validated.
 * `allow-additional-properties` : Optional property. If set to true, additional properties not listed in the description are allowed.
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntity.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntity.java
index 9172515..a2bf0eb 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntity.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntity.java
@@ -39,6 +39,18 @@
 	private String deprecated;
 
 	/**
+	 * Optional since information.
+	 * @since 1.3.6
+	 */
+	private String since;
+
+	/**
+	 * Optional enforce on information.
+	 * @since 1.3.6
+	 */
+	private String enforceOn;
+
+	/**
      * Clear the object and reset to defaults
      */
     @Override
@@ -47,6 +59,8 @@
 		this.setTitle(null);
 		this.setDescription(null);
 		this.setDeprecated(null);
+		this.setSince(null);
+		this.setEnforceOn(null);
 	}
 	
 	/**
@@ -63,6 +77,8 @@
 			this.setTitle(this.getString(InternalConstants.KEY_TITLE));
 			this.setDescription(this.getString(InternalConstants.KEY_DESCRIPTION));
 			this.setDeprecated(this.getString(InternalConstants.KEY_DEPRECATED));
+			this.setSince(this.getString(InternalConstants.KEY_SINCE));
+			this.setEnforceOn(this.getString(InternalConstants.KEY_ENFORCE_ON));
         } catch (final JsonException | IllegalArgumentException e) {
             throw new IOException(e);
 		}
@@ -117,6 +133,39 @@
 	}
 
 	/**
+	 * Get the optional since information
+	 * @return The since information or {@code null}
+	 */
+	public String getSince() {
+		return since;
+	}
+
+	/**
+	 * Set the since information. This should a date in the format 'YYYY-MM-DD'.
+	 * @param since The new info
+	 */
+	public void setSince(final String since) {
+		this.since = since;
+	}
+
+
+	/**
+	 * Get the optional since information
+	 * @return The since information or {@code null}
+	 */
+	public String getEnforceOn() {
+		return enforceOn;
+	}
+
+	/**
+	 * Set the enforce on information.
+	 * @param enforceOn The new info
+	 */
+	public void setEnforceOn(final String enforceOn) {
+		this.enforceOn = enforceOn;
+	}
+
+	/**
      * Convert this object into JSON
      *
      * @return The json object builder
@@ -129,6 +178,8 @@
 		this.setString(objectBuilder, InternalConstants.KEY_TITLE, this.getTitle());
 		this.setString(objectBuilder, InternalConstants.KEY_DESCRIPTION, this.getDescription());
 		this.setString(objectBuilder, InternalConstants.KEY_DEPRECATED, this.getDeprecated());
+		this.setString(objectBuilder, InternalConstants.KEY_SINCE, this.getSince());
+		this.setString(objectBuilder, InternalConstants.KEY_ENFORCE_ON, this.getEnforceOn());
 
 		return objectBuilder;
 	}
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 7fe12df..b2b9cf7 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
@@ -26,6 +26,10 @@
 	static final String KEY_DESCRIPTION = "description";
 
 	static final String KEY_DEPRECATED = "deprecated";
+
+    static final String KEY_SINCE = "since";
+
+    static final String KEY_ENFORCE_ON = "enforce-on";
 	
 	static final String KEY_PROPERTIES = "properties";
 
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/package-info.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/package-info.java
index ac5f0b1..49fdfc7 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/package-info.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@org.osgi.annotation.versioning.Version("1.4.0")
+@org.osgi.annotation.versioning.Version("1.5.0")
 package org.apache.sling.feature.extension.apiregions.api.config;
 
 
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidator.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidator.java
index 2eb7080..6358e19 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidator.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidator.java
@@ -27,6 +27,7 @@
 import org.apache.sling.feature.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.DescribableEntity;
 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;
@@ -106,11 +107,11 @@
             } else {
                 if ( desc.getPropertyDescriptions().isEmpty()) {
                     if ( region == Region.GLOBAL && !desc.isAllowAdditionalProperties() ) {
-                        setResult(result, validationMode, "Factory configuration is not allowed");
+                        setResult(result, validationMode, desc, "Factory configuration is not allowed");
                     }
                 } else {
                     if ( region == Region.GLOBAL && desc.getRegion() == Region.INTERNAL ) {
-                        setResult(result, validationMode, "Factory configuration is not allowed");
+                        setResult(result, validationMode, desc, "Factory configuration is not allowed");
                     } else {
                         validateProperties(config, desc, result.getPropertyResults(), region, validationMode);
                     }
@@ -122,11 +123,11 @@
             } else {
                 if ( desc.getPropertyDescriptions().isEmpty()) {
                     if ( region == Region.GLOBAL && !desc.isAllowAdditionalProperties() ) {
-                        setResult(result, validationMode, "Configuration is not allowed");
+                        setResult(result, validationMode, desc, "Configuration is not allowed");
                     }
                 } else {
                     if ( region == Region.GLOBAL && desc.getRegion() == Region.INTERNAL ) {
-                        setResult(result, validationMode, "Configuration is not allowed");
+                        setResult(result, validationMode, desc, "Configuration is not allowed");
                     } else {
                         validateProperties(config, desc, result.getPropertyResults(), region, validationMode);
                     }
@@ -135,8 +136,9 @@
         }
 
         if ( desc.getDeprecated() != null ) {
-            result.getWarnings().add(desc.getDeprecated());
+            setResult(result, Mode.LENIENT, desc, desc.getDeprecated());
         }
+
         return result;
     }
 
@@ -175,25 +177,35 @@
 
                 if ( desc.getInternalPropertyNames().contains(propName ) ) {
                     if  ( propRegion != Region.INTERNAL ) {
-                        PropertyValidator.setResult(result, null, mode, "Property is not allowed");
+                        PropertyValidator.setResult(result, null, mode, desc, "Property is not allowed");
                     }
                 } else if ( Constants.SERVICE_RANKING.equalsIgnoreCase(propName) ) {
                     final Object value = properties.get(propName);
                     if ( !(value instanceof Integer) ) {
-                        PropertyValidator.setResult(result, 0, mode, "service.ranking must be of type Integer");
+                        PropertyValidator.setResult(result, 0, mode, desc, "service.ranking must be of type Integer");
                     }    
                 } else if ( !isAllowedProperty(propName) && propRegion != Region.INTERNAL && !desc.isAllowAdditionalProperties() ) {                    
-                    PropertyValidator.setResult(result, null, mode, "Property is not allowed");
+                    PropertyValidator.setResult(result, null, mode, desc, "Property is not allowed");
                 }
             }
         }
     }
 
-    static void setResult(final ConfigurationValidationResult result, final Mode validationMode, final String msg) {
+    static void setResult(final ConfigurationValidationResult result, final Mode validationMode,
+                          final DescribableEntity desc, final String msg) {
+        // set postfix to the message if since or enforce-on are set
+        String postfixMsg = "";
+        if ( desc != null && desc.getSince() != null ) {
+            postfixMsg = postfixMsg.concat(". Since : ").concat(desc.getSince());
+        }
+        if ( desc != null && desc.getEnforceOn() != null ) {
+            postfixMsg = postfixMsg.concat(". Enforced on : ").concat(desc.getEnforceOn());
+        }
+        String finalMsg = msg + postfixMsg;
         if ( validationMode == Mode.STRICT ) {
-            result.getErrors().add(msg);
+            result.getErrors().add(finalMsg);
         } else if ( validationMode == Mode.LENIENT || validationMode == Mode.DEFINITIVE ) {
-            result.getWarnings().add(msg);
+            result.getWarnings().add(finalMsg);
         }
         if ( validationMode == Mode.DEFINITIVE || validationMode == Mode.SILENT_DEFINITIVE ) {
             result.setUseDefaultValue(true);
@@ -212,4 +224,4 @@
     void setCache(Map<ArtifactId, Region> cache) {
         this.cache = cache;
     }
-}
\ No newline at end of file
+}
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 04f0f23..10a513a 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
@@ -132,22 +132,27 @@
                         result.getConfigurationResults().put(config.getPid(), r);
                         if ( regionInfo.region != Region.INTERNAL ) {
                             if ( desc.getOperations().isEmpty() ) {
-                                ConfigurationValidator.setResult(r, api.getMode(), "No operations allowed for factory configuration");
+                                ConfigurationValidator.setResult(r, api.getMode(), desc, "No operations allowed for " +
+                                        "factory configuration");
                             } else {
                                 if ( regionInfo.isUpdate && !desc.getOperations().contains(Operation.UPDATE)) {
-                                    ConfigurationValidator.setResult(r, api.getMode(), "Updating of factory configuration is not allowed");
+                                    ConfigurationValidator.setResult(r, api.getMode(), desc, "Updating of factory " +
+                                            "configuration is not allowed");
                                 } else if ( !regionInfo.isUpdate && !desc.getOperations().contains(Operation.CREATE)) {
-                                    ConfigurationValidator.setResult(r, api.getMode(), "Creation of factory configuration is not allowed");
+                                    ConfigurationValidator.setResult(r, api.getMode(), desc, "Creation of factory " +
+                                            "configuration is not allowed");
                                 }
                             }
                             if ( desc.getInternalNames().contains(config.getName())) {
-                                ConfigurationValidator.setResult(r, api.getMode(), "Factory configuration with name is not allowed");
+                                ConfigurationValidator.setResult(r, api.getMode(), desc, "Factory configuration with " +
+                                        "name is not allowed");
                             }
                         }                        
 
                     } else if ( regionInfo.region != Region.INTERNAL && api.getInternalFactoryConfigurations().contains(config.getFactoryPid())) {
                         final ConfigurationValidationResult cvr = new ConfigurationValidationResult();
-                        ConfigurationValidator.setResult(cvr, api.getMode(), "Factory configuration is not allowed");
+                        ConfigurationValidator.setResult(cvr, api.getMode(), desc, "Factory configuration is not " +
+                                "allowed");
                         result.getConfigurationResults().put(config.getPid(), cvr);
                     }
                 } else {
@@ -157,7 +162,7 @@
                         result.getConfigurationResults().put(config.getPid(), r);
                     } else if ( regionInfo.region!= Region.INTERNAL && api.getInternalConfigurations().contains(config.getPid())) {
                         final ConfigurationValidationResult cvr = new ConfigurationValidationResult();
-                        ConfigurationValidator.setResult(cvr, api.getMode(), "Configuration is not allowed");
+                        ConfigurationValidator.setResult(cvr, api.getMode(), desc, "Configuration is not allowed");
                         result.getConfigurationResults().put(config.getPid(), cvr);
                     } 
                 }    
@@ -180,7 +185,7 @@
                     result.getFrameworkPropertyResults().put(frameworkProperty, pvr);
                 } else if ( regionInfo.region != Region.INTERNAL && api.getInternalFrameworkProperties().contains(frameworkProperty) ) {
                     final PropertyValidationResult pvr = new PropertyValidationResult();
-                    PropertyValidator.setResult(pvr, null, api.getMode(), "Framework property is not allowed");
+                    PropertyValidator.setResult(pvr, null, api.getMode(), null, "Framework property is not allowed");
                     result.getFrameworkPropertyResults().put(frameworkProperty, pvr);
                 }
             } 
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 e881ba2..6a586de 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
@@ -23,16 +23,14 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 import java.util.regex.Pattern;
 
-import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.extension.apiregions.api.config.DescribableEntity;
 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.PlaceholderPolicy;
 import org.apache.sling.feature.extension.apiregions.api.config.PropertyDescription;
 import org.apache.sling.feature.extension.apiregions.api.config.PropertyType;
-import org.apache.sling.feature.extension.apiregions.api.config.Region;
 
 /**
  * Validate a configuration property or framework property
@@ -84,7 +82,7 @@
 
         if ( value == null ) {
             if ( desc.isRequired() ) {
-                setResult(context, "No value provided");
+                setResult(context, desc, "No value provided");
             }
 		} else {
 			final List<Object> values;
@@ -104,33 +102,43 @@
 			} else {
 				// single value
 				values = null;
-				validateValue(context, value);
+				validateValue(context, desc, value);
 			}
 
 			if ( values != null ) {
                 // array or collection
                 for(final Object val : values) {
-                    validateValue(context, val);
+                    validateValue(context, desc, val);
                 }
-                validateList(context, values);
+                validateList(context, desc, values);
             }
             
             if ( desc.getDeprecated() != null ) {
-                context.result.getWarnings().add(desc.getDeprecated());
+                setResult(context.result, null, Mode.LENIENT, desc, desc.getDeprecated());
             }
 		}
 		return context.result;
 	}
 
-    void setResult(final Context context, final String msg) {
-        setResult(context.result, context.description.getDefaultValue(), context.validationMode, msg);
+    void setResult(final Context context, final DescribableEntity desc, final String msg) {
+        setResult(context.result, context.description.getDefaultValue(), context.validationMode, desc, msg);
     }
 
-    static void setResult(final PropertyValidationResult result, final Object defaultValue, final Mode validationMode, final String msg) {
+    static void setResult(final PropertyValidationResult result, final Object defaultValue, final Mode validationMode,
+                          final DescribableEntity desc, final String msg) {
+        // set postfix to the message if since or enforce-on are set
+        String postfixMsg = "";
+        if ( desc != null && desc.getSince() != null ) {
+            postfixMsg = postfixMsg.concat(". Since : ").concat(desc.getSince());
+        }
+        if ( desc != null && desc.getEnforceOn() != null ) {
+            postfixMsg = postfixMsg.concat(". Enforced on : ").concat(desc.getEnforceOn());
+        }
+        String finalMsg = msg + postfixMsg;
         if ( validationMode == Mode.STRICT ) {
-            result.getErrors().add(msg);
+            result.getErrors().add(finalMsg);
         } else if ( validationMode == Mode.LENIENT || validationMode == Mode.DEFINITIVE ) {
-            result.getWarnings().add(msg);
+            result.getWarnings().add(finalMsg);
         }
         if ( validationMode == Mode.DEFINITIVE || validationMode == Mode.SILENT_DEFINITIVE ) {
             result.setUseDefaultValue(true);
@@ -140,13 +148,14 @@
     
     /**
      * Validate a multi value
-     * @param prop The property description
+     * @param context The current context
+     * @param desc describable entity
      * @param values The values
-     * @param messages The messages to record errors
      */
-    void validateList(final Context context, final List<Object> values) {
+    void validateList(final Context context, final DescribableEntity desc, 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");
+            setResult(context, desc, "Array/collection contains too many elements, only " + context.description.getCardinality() +
+                            " allowed");
         }
         if ( context.description.getIncludes() != null ) {
             for(final String inc : context.description.getIncludes()) {
@@ -158,7 +167,7 @@
                     }
                 }
                 if ( !found ) {
-                    setResult(context, "Required included value " + inc + " not found");
+                    setResult(context, desc, "Required included value " + inc + " not found");
                 }
             }
         }
@@ -172,7 +181,7 @@
                     }
                 }
                 if ( found ) {
-                    setResult(context, "Required excluded value " + exc + " found");
+                    setResult(context, desc, "Required excluded value " + exc + " found");
                 }
             }
         }
@@ -180,7 +189,7 @@
 
     private static final List<String> PLACEHOLDERS = Arrays.asList("$[env:", "$[secret:", "$[prop:");
 
-	void validateValue(final Context context, final Object value) {
+	void validateValue(final Context context, final DescribableEntity desc, final Object value) {
 		if ( value != null ) {
             // check for placeholder
             boolean hasPlaceholder = false;
@@ -195,45 +204,45 @@
             }
             if ( !hasPlaceholder ) {
                 switch ( context.description.getType() ) {
-                    case BOOLEAN : validateBoolean(context, value);
+                    case BOOLEAN : validateBoolean(context, desc, value);
                                 break;
-                    case BYTE : validateByte(context, value);
+                    case BYTE : validateByte(context, desc, value);
                                 break;
-                    case CHARACTER : validateCharacter(context, value);
+                    case CHARACTER : validateCharacter(context, desc, value);
                                 break;
-                    case DOUBLE : validateDouble(context, value); 
+                    case DOUBLE : validateDouble(context, desc, value);
                                 break;
-                    case FLOAT : validateFloat(context, value); 
+                    case FLOAT : validateFloat(context, desc, value);
                                 break;
-                    case INTEGER : validateInteger(context, value);
+                    case INTEGER : validateInteger(context, desc, value);
                                 break;
-                    case LONG : validateLong(context, value);
+                    case LONG : validateLong(context, desc, value);
                                 break;
-                    case SHORT : validateShort(context, value);
+                    case SHORT : validateShort(context, desc, value);
                                 break;
-                    case STRING : validateRequired(context, value);
+                    case STRING : validateRequired(context, desc, value);
                                 break;
-                    case EMAIL : validateEmail(context, value); 
+                    case EMAIL : validateEmail(context, desc, value);
                                 break;
-                    case PASSWORD : validatePassword(context, value, false);
+                    case PASSWORD : validatePassword(context, desc, value, false);
                                 break;
-                    case URL : validateURL(context, value);
+                    case URL : validateURL(context, desc, value);
                             break;
-                    case PATH : validatePath(context, value);
+                    case PATH : validatePath(context, desc, value);
                                 break;
                     default : context.result.getErrors().add("Unable to validate value - unknown property type : " + context.description.getType());
                 }
-                validateRegex(context, context.description.getRegexPattern(), value);
-                validateOptions(context, value);
+                validateRegex(context, desc, context.description.getRegexPattern(), value);
+                validateOptions(context, desc, value);
                 if ( context.description.getType() != PropertyType.PASSWORD ) {
-                    validatePlaceholderPolicy(context, value, false);              
+                    validatePlaceholderPolicy(context, desc, value, false);
                 }
             } else {
                 // placeholder is present
                 if ( context.description.getType() == PropertyType.PASSWORD ) {
-                    validatePassword(context, value, true);
+                    validatePassword(context, desc, value, true);
                 } else if ( context.description.getType() == PropertyType.STRING ) {
-                    validateRegex(context, context.description.getPlaceholderRegexPattern(), value);
+                    validateRegex(context, desc, context.description.getPlaceholderRegexPattern(), value);
 
                     // we mark the result as skipped if a regex or options are set or if a value is marked as required.
                     if ( context.description.getRegex() != null || context.description.getOptions() != null || context.description.isRequired() ) {
@@ -243,206 +252,207 @@
                     context.result.markSkipped();
                 }
                 if ( context.description.getType() != PropertyType.PASSWORD ) {
-                    validatePlaceholderPolicy(context, value, true);              
+                    validatePlaceholderPolicy(context, desc, value, true);
                 }
             }
         } else {
-			setResult(context, "Null value provided for validation");
+			setResult(context, desc, "Null value provided for validation");
 		}
 	}
 	
-	void validateRequired(final Context context, final Object value) {
+	void validateRequired(final Context context, final DescribableEntity desc, final Object value) {
         if ( context.description.isRequired() ) {
             final String val = value.toString();
             if ( val.isEmpty() ) {
-                setResult(context, "Value is required");
+                setResult(context, desc, "Value is required");
             }
         }
     }
 
-    void validateBoolean(final Context context, final Object value) {
+    void validateBoolean(final Context context, final DescribableEntity desc, final Object value) {
         if ( ! (value instanceof Boolean) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				if ( ! v.equalsIgnoreCase("true") && !v.equalsIgnoreCase("false") ) {
-                    setResult(context, "Boolean value must either be true or false, but not " + value);
+                    setResult(context, desc, "Boolean value must either be true or false, but not " + value);
 				}
 			} else {
-				setResult(context, "Boolean value must either be of type Boolean or String : " + value);
+				setResult(context, desc, "Boolean value must either be of type Boolean or String : " + value);
 			}
 		}
 	}
 
-	void validateByte(final Context context, final Object value) {
+	void validateByte(final Context context, final DescribableEntity desc, final Object value) {
         if ( ! (value instanceof Byte) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				try {
-					validateRange(context, Byte.valueOf(v));
+					validateRange(context, desc, Byte.valueOf(v));
 				} catch ( final NumberFormatException nfe ) {
-                    setResult(context, "Value is not a valid Byte : " + value);
+                    setResult(context, desc,"Value is not a valid Byte : " + value);
                 }
             } else if ( value instanceof Number ) {
-                validateRange(context, ((Number)value).byteValue());            
+                validateRange(context, desc, ((Number)value).byteValue());
 			} else {
-				setResult(context, "Byte value must either be of type Byte or String : " + value);
+				setResult(context, desc, "Byte value must either be of type Byte or String : " + value);
 			}
 		} else {
-			validateRange(context, (Byte)value);
+			validateRange(context, desc, (Byte)value);
 		}
 	}
 
-	void validateShort(final Context context, final Object value) {
+	void validateShort(final Context context, final DescribableEntity desc, final Object value) {
         if ( ! (value instanceof Short) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				try {
-					validateRange(context, Short.valueOf(v));
+					validateRange(context, desc, Short.valueOf(v));
 				} catch ( final NumberFormatException nfe ) {
-                    setResult(context, "Value is not a valid Short : " + value);
+                    setResult(context, desc, "Value is not a valid Short : " + value);
 				}
             } else if ( value instanceof Number ) {
-                validateRange(context, ((Number)value).shortValue());            
+                validateRange(context, desc, ((Number)value).shortValue());
 			} else {
-				setResult(context, "Short value must either be of type Short or String : " + value);
+				setResult(context, desc, "Short value must either be of type Short or String : " + value);
 			}
 		} else {
-			validateRange(context, (Short)value);
+			validateRange(context, desc, (Short)value);
 		}
 	}
 
-	void validateInteger(final Context context, final Object value) {
+	void validateInteger(final Context context, final DescribableEntity desc, final Object value) {
         if ( ! (value instanceof Integer) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				try {
-					validateRange(context, Integer.valueOf(v));
+					validateRange(context, desc, Integer.valueOf(v));
 				} catch ( final NumberFormatException nfe ) {
-                    setResult(context, "Value is not a valid Integer : " + value);
+                    setResult(context, desc, "Value is not a valid Integer : " + value);
 				}
             } else if ( value instanceof Number ) {
-                validateRange(context, ((Number)value).intValue());            
+                validateRange(context, desc, ((Number)value).intValue());
 			} else {
-				setResult(context, "Integer value must either be of type Integer or String : " + value);
+				setResult(context, desc, "Integer value must either be of type Integer or String : " + value);
 			}
 		} else {
-			validateRange(context, (Integer)value);
+			validateRange(context, desc, (Integer)value);
 		}
 	}
 
-	void validateLong(final Context context, final Object value) {
+	void validateLong(final Context context, final DescribableEntity desc, final Object value) {
         if ( ! (value instanceof Long) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				try {
-					validateRange(context, Long.valueOf(v));
+					validateRange(context, desc, Long.valueOf(v));
 				} catch ( final NumberFormatException nfe ) {
-                    setResult(context, "Value is not a valid Long : " + value);
+                    setResult(context, desc, "Value is not a valid Long : " + value);
 				}
             } else if ( value instanceof Number ) {
-                validateRange(context, ((Number)value).longValue());            
+                validateRange(context, desc, ((Number)value).longValue());
 			} else {
-				setResult(context, "Long value must either be of type Long or String : " + value);
+				setResult(context, desc, "Long value must either be of type Long or String : " + value);
 			}
 		} else {
-			validateRange(context, (Long)value);
+			validateRange(context, desc, (Long)value);
 		}
 	}
 
-	void validateFloat(final Context context, final Object value) {
+	void validateFloat(final Context context, final DescribableEntity desc, final Object value) {
         if ( ! (value instanceof Float) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				try {
-					validateRange(context, Float.valueOf(v));
+					validateRange(context, desc, Float.valueOf(v));
 				} catch ( final NumberFormatException nfe ) {
-                    setResult(context, "Value is not a valid Float : " + value);
+                    setResult(context, desc, "Value is not a valid Float : " + value);
 				}
             } else if ( value instanceof Number ) {
-                validateRange(context, ((Number)value).floatValue());            
+                validateRange(context, desc, ((Number)value).floatValue());
 			} else {
-				setResult(context, "Float value must either be of type Float or String : " + value);
+				setResult(context, desc, "Float value must either be of type Float or String : " + value);
 			}
 		} else {
-			validateRange(context, (Float)value);
+			validateRange(context, desc, (Float)value);
 		}
 	}
 
-	void validateDouble(final Context context, final Object value) {
+	void validateDouble(final Context context, final DescribableEntity desc, final Object value) {
         if ( ! (value instanceof Double) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				try {
-					validateRange(context, Double.valueOf(v));
+					validateRange(context, desc, Double.valueOf(v));
 				} catch ( final NumberFormatException nfe ) {
-                    setResult(context, "Value is not a valid Double : " + value);
+                    setResult(context, desc, "Value is not a valid Double : " + value);
 				}
             } else if ( value instanceof Number ) {
-                validateRange(context, ((Number)value).doubleValue());            
+                validateRange(context, desc, ((Number)value).doubleValue());
 			} else {
-				setResult(context, "Double value must either be of type Double or String : " + value);
+				setResult(context, desc, "Double value must either be of type Double or String : " + value);
 			}
 		} else {
-			validateRange(context, (Double)value);
+			validateRange(context, desc, (Double)value);
 		}
 	}
 
-	void validateCharacter(final Context context, final Object value) {
+	void validateCharacter(final Context context, final DescribableEntity desc, final Object value) {
         if ( ! (value instanceof Character) ) {
 			if ( value instanceof String ) {
 				final String v = (String)value;
 				if ( v.length() > 1 ) {
-                    setResult(context, "Value is not a valid Character : " + value);
+                    setResult(context, desc, "Value is not a valid Character : " + value);
 				}
 			} else {
-				setResult(context, "Character value must either be of type Character or String : " + value);
+				setResult(context, desc,"Character value must either be of type Character or String : " + value);
 			}
 		}
 	}
 
-	void validateURL(final Context context, final Object value) {
+	void validateURL(final Context context, final DescribableEntity desc, final Object value) {
 		final String val = value.toString();
 		try {
 			new URL(val);
 		} catch ( final MalformedURLException mue) {
-			setResult(context, "Value is not a valid URL : " + val);
+			setResult(context, desc, "Value is not a valid URL : " + val);
 		}
 	}
 
-	void validateEmail(final Context context, final Object value) {
+	void validateEmail(final Context context, final DescribableEntity desc, final Object value) {
 		final String val = value.toString();
 		// poor man's validation (should probably use InternetAddress)
 		if ( !val.contains("@") ) {
-			setResult(context, "Not a valid email address " + val);
+			setResult(context, desc, "Not a valid email address " + val);
 		}
 	}
 
-	void validatePassword(final Context context, final Object value, final boolean hasPlaceholder) {
+	void validatePassword(final Context context, final DescribableEntity desc, final Object value,
+                          final boolean hasPlaceholder) {
         if ( !this.isLiveValues() && !hasPlaceholder && context.description.getPlaceholderPolicy() != PlaceholderPolicy.DENY ) {
-            setResult(context, "Value for a password must use a placeholder");
+            setResult(context, desc, "Value for a password must use a placeholder");
         }
 	}
 
-	void validatePath(final Context context, final Object value) {
+	void validatePath(final Context context, final DescribableEntity desc, final Object value) {
 		final String val = value.toString();
 		// poor man's validation 
 		if ( !val.startsWith("/") ) {
-			setResult(context, "Not a valid path " + val);
+			setResult(context, desc, "Not a valid path " + val);
 		}
 	}
 
-    void validateRange(final Context context, final Number value) {
+    void validateRange(final Context context, final DescribableEntity desc, final Number value) {
 	    if ( context.description.getRange() != null ) {
             if ( context.description.getRange().getMin() != null ) {
                 if ( value instanceof Float || value instanceof Double ) {
                     final double min = context.description.getRange().getMin().doubleValue();
                     if ( value.doubleValue() < min ) {
-                            setResult(context, "Value " + value + " is too low; should not be lower than " + context.description.getRange().getMin());
+                            setResult(context, desc, "Value " + value + " is too low; should not be lower than " + context.description.getRange().getMin());
                     }    
                 } else {
                     final long min = context.description.getRange().getMin().longValue();
                     if ( value.longValue() < min ) {
-                        setResult(context, "Value " + value + " is too low; should not be lower than " + context.description.getRange().getMin());
+                        setResult(context, desc, "Value " + value + " is too low; should not be lower than " + context.description.getRange().getMin());
                     }    
                 }
             }
@@ -450,27 +460,27 @@
                 if ( value instanceof Float || value instanceof Double ) {
                     final double max = context.description.getRange().getMax().doubleValue();
                     if ( value.doubleValue() > max ) {
-                        setResult(context, "Value " + value + " is too high; should not be higher than " + context.description.getRange().getMax());
+                        setResult(context, desc, "Value " + value + " is too high; should not be higher than " + context.description.getRange().getMax());
                     }    
                 } else {
                     final long max = context.description.getRange().getMax().longValue();
                     if ( value.longValue() > max ) {
-                        setResult(context, "Value " + value + " is too high; should not be higher than " + context.description.getRange().getMax());
+                        setResult(context, desc, "Value " + value + " is too high; should not be higher than " + context.description.getRange().getMax());
                     }    
                 }
             }
 		}
 	}
 
-    void validateRegex(final Context context, final Pattern pattern, final Object value) {
+    void validateRegex(final Context context, final DescribableEntity desc, final Pattern pattern, final Object value) {
         if ( pattern != null ) {
             if ( !pattern.matcher(value.toString()).matches() ) {
-                setResult(context, "Value " + value + " does not match regex " + pattern.pattern());
+                setResult(context, desc, "Value " + value + " does not match regex " + pattern.pattern());
             }
         }
     }
 
-    void validateOptions(final Context context, final Object value) {
+    void validateOptions(final Context context, final DescribableEntity desc, final Object value) {
         if ( context.description.getOptions() != null ) {
             boolean found = false;
             for(final Option opt : context.description.getOptions()) {
@@ -479,19 +489,20 @@
                 }
             }
             if ( !found ) {
-                setResult(context, "Value " + value + " does not match provided options");
+                setResult(context, desc, "Value " + value + " does not match provided options");
             }
         }
     }
 
-    void validatePlaceholderPolicy(final Context context, final Object value, final boolean hasPlaceholder) {
+    void validatePlaceholderPolicy(final Context context, final DescribableEntity desc, final Object value,
+                                   final boolean hasPlaceholder) {
         // only check policy if no live values
         if ( !this.isLiveValues() ) {
             // for policy default and allow nothing needs to be validated
             if ( context.description.getPlaceholderPolicy() == PlaceholderPolicy.DENY && hasPlaceholder ) {
-                setResult(context, "Placeholder in value is not allowed");
+                setResult(context, desc, "Placeholder in value is not allowed");
             }  else if ( context.description.getPlaceholderPolicy() == PlaceholderPolicy.REQUIRE && !hasPlaceholder ) {
-                setResult(context, "Value must use a placeholder");
+                setResult(context, desc, "Value must use a placeholder");
             }
         } 
     }         
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntityTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntityTest.java
index f2ad633..9293a85 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntityTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntityTest.java
@@ -41,16 +41,21 @@
         entity.setDeprecated("d");
         entity.setTitle("t");
         entity.setDescription("x");
+        entity.setEnforceOn("e");
+        entity.setSince("s");
         entity.clear();
         assertTrue(entity.getAttributes().isEmpty());
         assertNull(entity.getDeprecated());
         assertNull(entity.getTitle());
         assertNull(entity.getDescription());
+        assertNull(entity.getEnforceOn());
+        assertNull(entity.getSince());
     }
 
     @Test public void testFromJSONObject() throws IOException {
         final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
-        ext.setJSON("{ \"a\" : 1, \"b\" : \"2\", \"title\" : \"t\", \"description\" : \"desc\", \"deprecated\" : \"depr\"}");
+        ext.setJSON("{ \"a\" : 1, \"b\" : \"2\", \"title\" : \"t\", \"description\" : \"desc\", \"deprecated\" : " +
+                "\"depr\", \"enforce-on\" : \"1970-04-01\", \"since\" : \"1970-01-01\"}");
 
         final DE entity = new DE();
         entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
@@ -59,7 +64,8 @@
         assertEquals(Json.createValue("2"), entity.getAttributes().get("b"));
         assertEquals("t", entity.getTitle());
         assertEquals("desc", entity.getDescription());
-        assertEquals("depr", entity.getDeprecated());
+        assertEquals("1970-04-01", entity.getEnforceOn());
+        assertEquals("1970-01-01", entity.getSince());
     }
 
     @Test public void testToJSONObject() throws IOException {
@@ -69,10 +75,13 @@
         entity.setTitle("t");
         entity.setDescription("desc");
         entity.setDeprecated("depr");
+        entity.setEnforceOn("1970-04-01");
+        entity.setSince("1970-01-01");
 
         final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
-        ext.setJSON("{ \"a\" : 1, \"b\" : \"2\", \"title\" : \"t\", \"description\" : \"desc\", \"deprecated\" : \"depr\"}");
+        ext.setJSON("{ \"a\" : 1, \"b\" : \"2\", \"title\" : \"t\", \"description\" : \"desc\", \"deprecated\" : " +
+                "\"depr\", \"enforce-on\" : \"1970-04-01\", \"since\" : \"1970-01-01\"}");
 
         assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());
     }
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidatorTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidatorTest.java
index 1cfff34..7628582 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidatorTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/validation/ConfigurationValidatorTest.java
@@ -69,6 +69,26 @@
         assertEquals("this is deprecated", result.getWarnings().get(0));
     }
 
+    @Test public void testMessageWithEnforceAndSinceInfo() {
+        final Configuration cfg = new Configuration("org.apache");
+        final ConfigurationDescription cd = new ConfigurationDescription();
+        final PropertyDescription prop = new PropertyDescription();
+        cd.getPropertyDescriptions().put("a", prop);
+
+        ConfigurationValidationResult result = validator.validate(cfg, cd, null);
+        assertTrue(result.isValid());
+        assertTrue(result.getWarnings().isEmpty());
+
+        cd.setDeprecated("this is deprecated");
+        cd.setSince("1970-01-01");
+        cd.setEnforceOn("1970-04-01");
+        result = validator.validate(cfg, cd, null);
+        assertTrue(result.isValid());
+        assertFalse(result.getWarnings().isEmpty());
+        assertEquals("this is deprecated. Since : 1970-01-01. Enforced on : 1970-04-01",
+                result.getWarnings().get(0));
+    }
+
     @Test public void testServiceRanking() {
         final Configuration cfg = new Configuration("org.apache");
         final ConfigurationDescription cd = new ConfigurationDescription();
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 29fc3cc..7411375 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
@@ -425,6 +425,19 @@
         assertEquals("This is deprecated", result.getWarnings().get(0));
     }
 
+
+    @Test public void testEnforceOnAndSinceInMsg() {
+        final PropertyDescription prop = new PropertyDescription();
+        prop.setDeprecated("Deprecated message");
+        prop.setEnforceOn("1970-04-01");
+        prop.setSince("1970-01-01");
+
+        final PropertyValidationResult result = validator.validate("foo", prop);
+        assertTrue(result.isValid());
+        assertEquals(1, result.getWarnings().size());
+        assertEquals("Deprecated message. Since : 1970-01-01. Enforced on : 1970-04-01", result.getWarnings().get(0));
+    }
+
     @Test public void testPlaceholdersString() {
         final PropertyDescription desc = new PropertyDescription();
         desc.setType(PropertyType.PATH);