SLING-9952 : Skip validation of properties if they contain a placeholder
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 6f25d4d..fd30a5f 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,7 @@
     /**
      * Clear the object and reset to defaults
      */
+    @Override
 	public void clear() {
         super.clear();
 		this.properties.clear();
@@ -49,6 +50,7 @@
 	 * @param jsonObj The JSON Object
 	 * @throws IOException If JSON parsing fails
 	 */
+    @Override
 	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
         super.fromJSONObject(jsonObj);
         try {
@@ -79,6 +81,7 @@
      * @return The json object builder
      * @throws IOException If generating the JSON fails
      */
+    @Override
 	JsonObjectBuilder createJson() throws IOException {
 		final JsonObjectBuilder objBuilder = super.createJson();
 
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 e29a629..6fc58c3 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
@@ -131,6 +131,7 @@
     /**
      * Clear the object and reset to defaults
      */
+    @Override
     public void clear() {
         super.clear();
         this.configurations.clear();
@@ -149,6 +150,7 @@
 	 * @param jsonObj The JSON Object
 	 * @throws IOException If JSON parsing fails
 	 */
+    @Override
     public void fromJSONObject(final JsonObject jsonObj) throws IOException {
         super.fromJSONObject(jsonObj);
         try {
@@ -281,6 +283,7 @@
      * @return The json object builder
      * @throws IOException If generating the JSON fails
      */
+    @Override
     JsonObjectBuilder createJson() throws IOException {
 		final JsonObjectBuilder objBuilder = super.createJson();
         if ( this.getRegion() != null ) {
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationDescription.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationDescription.java
index 0e2c7e9..d5b476d 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationDescription.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationDescription.java
@@ -21,4 +21,5 @@
  */
 public class ConfigurationDescription extends ConfigurableEntity {
 
+    // marker class to distinguish between different configurable entities
 }
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 b2617d4..88d1c07 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
@@ -40,6 +40,7 @@
 	/**
      * Clear the object and reset to defaults
      */
+    @Override
 	public void clear() {
 		super.clear();
 		this.setTitle(null);
@@ -54,6 +55,7 @@
 	 * @param jsonObj The JSON Object
 	 * @throws IOException If JSON parsing fails
 	 */
+    @Override
 	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
 		super.fromJSONObject(jsonObj);
         try {
@@ -119,6 +121,7 @@
      * @return The json object builder
      * @throws IOException If generating the JSON fails
      */
+    @Override
     JsonObjectBuilder createJson() throws IOException {
 		final JsonObjectBuilder objectBuilder = super.createJson();
 
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescription.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescription.java
index 9c8d7fa..da230cc 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescription.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescription.java
@@ -50,6 +50,7 @@
     /**
      * Clear the object and set the defaults
      */
+    @Override
     public void clear() {
         super.clear();
         this.setDefaults();
@@ -63,6 +64,7 @@
 	 * @param jsonObj The JSON Object
 	 * @throws IOException If JSON parsing fails
 	 */
+    @Override
     public void fromJSONObject(final JsonObject jsonObj) throws IOException {
         super.fromJSONObject(jsonObj);
         try {
@@ -113,6 +115,7 @@
      * @return The json object builder
      * @throws IOException If generating the JSON fails
      */
+    @Override
     JsonObjectBuilder createJson() throws IOException {
 		final JsonObjectBuilder objBuilder = super.createJson();
 		
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FrameworkPropertyDescription.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FrameworkPropertyDescription.java
index df9a3b0..0efc303 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FrameworkPropertyDescription.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FrameworkPropertyDescription.java
@@ -21,4 +21,6 @@
  */
 public class FrameworkPropertyDescription extends PropertyDescription {
     
+        // marker class to distinguish between different configurable entities
+
 }
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Option.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Option.java
index 41d2c23..4901c5e 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Option.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Option.java
@@ -33,6 +33,7 @@
     /**
      * Clear the object and reset to defaults
      */
+    @Override
     public void clear() {
         super.clear();
         this.setValue(null);
@@ -44,6 +45,7 @@
      * @param jsonObj The JSON Object
      * @throws IOException If JSON parsing fails
      */
+    @Override
     public void fromJSONObject(final JsonObject jsonObj) throws IOException {
         super.fromJSONObject(jsonObj);
         try {
@@ -75,6 +77,7 @@
      * @return The json object builder
      * @throws IOException If generating the JSON fails
      */
+    @Override
     JsonObjectBuilder createJson() throws IOException {
         final JsonObjectBuilder objectBuilder = super.createJson();
 
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 4323937..f07df67 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
@@ -76,6 +76,7 @@
     /**
      * Clear the object and reset to defaults
      */
+    @Override
 	public void clear() {
         super.clear();
         this.setDefaults();
@@ -93,6 +94,7 @@
 	 * @param jsonObj The JSON Object
 	 * @throws IOException If JSON parsing fails
 	 */
+    @Override
 	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
         super.fromJSONObject(jsonObj);
         try {
@@ -148,6 +150,7 @@
      * @return The json object builder
      * @throws IOException If generating the JSON fails
      */
+    @Override
     JsonObjectBuilder createJson() throws IOException {
 		final JsonObjectBuilder objectBuilder = super.createJson();
 
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Range.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Range.java
index f5daf55..326efb8 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Range.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Range.java
@@ -38,6 +38,7 @@
     /**
      * Clear the object and reset to defaults
      */
+    @Override
 	public void clear() {
         super.clear();
         this.setMax(null);
@@ -50,6 +51,7 @@
 	 * @param jsonObj The JSON Object
 	 * @throws IOException If JSON parsing fails
 	 */
+    @Override
 	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
         super.fromJSONObject(jsonObj);
         try {
@@ -98,6 +100,7 @@
      * @return The json object builder
      * @throws IOException If generating the JSON fails
      */
+    @Override
     JsonObjectBuilder createJson() throws IOException {
 		final JsonObjectBuilder objectBuilder = super.createJson();
 
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Region.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Region.java
index d4c14f4..65936e4 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Region.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Region.java
@@ -16,6 +16,9 @@
  */
 package org.apache.sling.feature.extension.apiregions.api.config;
 
+/**
+ * The configuration api region of the feature
+ */
 public enum Region {
     
     GLOBAL,
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/PropertyValidationResult.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/PropertyValidationResult.java
index 431ff59..1de03d4 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/PropertyValidationResult.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/validation/PropertyValidationResult.java
@@ -28,6 +28,8 @@
 
     private final List<String> warnings = new ArrayList<>();
 
+    private boolean skipped = false;
+
     /**
      * Is the property value valid?
      * @return {@code true} if the value is valid
@@ -52,4 +54,19 @@
     public List<String> getWarnings() {
         return this.warnings;
     }
+
+    /**
+     * Has the validation for this property be skipped?
+     * @return {@code true} if it has been skipped
+     */
+    public boolean isSkipped() {
+        return this.skipped;
+    }
+
+    /**
+     * Mark the property to be skipped during validation 
+     */
+    public void markSkipped() {
+        this.skipped = true;
+    }
 }
\ No newline at end of file
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 370e247..a9d5554 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
@@ -20,6 +20,7 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 
@@ -61,13 +62,13 @@
 			} else {
 				// single value
 				values = null;
-				validateValue(desc, value, result.getErrors());
+				validateValue(desc, value, result);
 			}
 
 			if ( values != null ) {
                 // array or collection
                 for(final Object val : values) {
-                    validateValue(desc, val, result.getErrors());
+                    validateValue(desc, val, result);
                 }
                 validateList(desc, values, result.getErrors());
             }
@@ -119,40 +120,58 @@
         }
     }
 
-	void validateValue(final PropertyDescription prop, final Object value, final List<String> messages) {
+    private static final List<String> PLACEHOLDERS = Arrays.asList("$[env:", "$[secret:", "$[prop:");
+
+	void validateValue(final PropertyDescription prop, final Object value, final PropertyValidationResult result) {
+        final List<String> messages = result.getErrors();
 		if ( value != null ) {
-			switch ( prop.getType() ) {
-				case BOOLEAN : validateBoolean(prop, value, messages);
-							   break;
-				case BYTE : validateByte(prop, value, messages);
-							break;
-				case CHARACTER : validateCharacter(prop, value, messages);
-							break;
-				case DOUBLE : validateDouble(prop, value, messages); 
-							break;
-				case FLOAT : validateFloat(prop, value, messages); 
-							break;
-				case INTEGER : validateInteger(prop, value, messages);
-							break;
-				case LONG : validateLong(prop, value, messages);
-							break;
-				case SHORT : validateShort(prop, value, messages);
-							break;
-				case STRING : // no special validation for string
-							break;
-				case EMAIL : validateEmail(prop, value, messages); 
-							break;
-				case PASSWORD : validatePassword(prop, value, messages);
-							break;
-                case URL : validateURL(prop, value, messages);
-                           break;
-                case PATH : validatePath(prop, value, messages);
-							break;
-				default : messages.add("Unable to validate value - unknown property type : " + prop.getType());
+            // check for placeholder
+            boolean checkValue = true;
+            if ( value instanceof String ) {
+                final String strVal = (String)value;
+                for(final String p : PLACEHOLDERS) {
+                    if ( strVal.contains(p) ) {
+                        checkValue = false;
+                        break;
+                    }
+                }
             }
-            validateRegex(prop, value, messages);
-            validateOptions(prop, value, messages);
-		} else {
+            if ( checkValue ) {
+                switch ( prop.getType() ) {
+                    case BOOLEAN : validateBoolean(prop, value, messages);
+                                break;
+                    case BYTE : validateByte(prop, value, messages);
+                                break;
+                    case CHARACTER : validateCharacter(prop, value, messages);
+                                break;
+                    case DOUBLE : validateDouble(prop, value, messages); 
+                                break;
+                    case FLOAT : validateFloat(prop, value, messages); 
+                                break;
+                    case INTEGER : validateInteger(prop, value, messages);
+                                break;
+                    case LONG : validateLong(prop, value, messages);
+                                break;
+                    case SHORT : validateShort(prop, value, messages);
+                                break;
+                    case STRING : // no special validation for string
+                                break;
+                    case EMAIL : validateEmail(prop, value, messages); 
+                                break;
+                    case PASSWORD : validatePassword(prop, value, messages);
+                                break;
+                    case URL : validateURL(prop, value, messages);
+                            break;
+                    case PATH : validatePath(prop, value, messages);
+                                break;
+                    default : messages.add("Unable to validate value - unknown property type : " + prop.getType());
+                }
+                validateRegex(prop, value, messages);
+                validateOptions(prop, value, messages);                
+            } else {
+                result.markSkipped();
+            }
+        } else {
 			messages.add("Null value provided for validation");
 		}
 	}
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 ea45d0d..fd8d1c0 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
@@ -35,15 +35,20 @@
     
     @Test public void testValidateWithNull() {
         final PropertyDescription prop = new PropertyDescription();
+        PropertyValidationResult result;
 
         // prop not required - no error
-        assertTrue(validator.validate(null, prop).getErrors().isEmpty());
-        assertTrue(validator.validate(null, prop).isValid());
+        result = validator.validate(null, prop);
+        assertTrue(result.getErrors().isEmpty());
+        assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         // prop required - error
         prop.setRequired(true);
-        assertEquals(1, validator.validate(null, prop).getErrors().size());
-        assertFalse(validator.validate(null, prop).isValid());
+        result = validator.validate(null, prop);
+        assertEquals(1, result.getErrors().size());
+        assertFalse(result.isValid());
+        assertFalse(result.isSkipped());
     }
 
     @Test public void testValidateBoolean() {
@@ -53,21 +58,27 @@
         PropertyValidationResult result;
         result = validator.validate(Boolean.TRUE, prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         result = validator.validate(Boolean.FALSE, prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         result = validator.validate("TRUE", prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         result = validator.validate("FALSE", prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         result = validator.validate("yes", prop);
         assertEquals(1, result.getErrors().size());
+        assertFalse(result.isSkipped());
 
         result = validator.validate(1, prop);
         assertEquals(1, result.getErrors().size());
+        assertFalse(result.isSkipped());
     }
 
     @Test public void testValidateByte() {
@@ -78,15 +89,19 @@
 
         result = validator.validate((byte)1, prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         result = validator.validate("1", prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         result = validator.validate("yes", prop);
         assertEquals(1, result.getErrors().size());
+        assertFalse(result.isSkipped());
 
         result = validator.validate(1, prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
     }
 
     @Test public void testValidateShort() {
@@ -97,15 +112,19 @@
 
         result = validator.validate((short)1, prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         result = validator.validate("1", prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         result = validator.validate("yes", prop);
         assertEquals(1, result.getErrors().size());
+        assertFalse(result.isSkipped());
 
         result = validator.validate(1, prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
     }
 
     @Test public void testValidateInteger() {
@@ -116,15 +135,19 @@
 
         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());
     }
 
     @Test public void testValidateLong() {
@@ -135,15 +158,19 @@
 
         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());
     }
 
     @Test public void testValidateFloat() {
@@ -154,15 +181,19 @@
 
         result = validator.validate(1.1, prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         result = validator.validate("1.1", prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         result = validator.validate("yes", prop);
         assertEquals(1, result.getErrors().size());
+        assertFalse(result.isSkipped());
 
         result = validator.validate(1, prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
     }
 
     @Test public void testValidateDouble() {
@@ -173,15 +204,19 @@
 
         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());
     }
 
     @Test public void testValidateChar() {
@@ -192,12 +227,15 @@
 
         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());
     }
 
     @Test public void testValidateUrl() {
@@ -208,9 +246,11 @@
 
         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());
     }
 
     @Test public void testValidateEmail() {
@@ -221,9 +261,11 @@
 
         result = validator.validate("a@b.com", prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         result = validator.validate("hello world", prop);
         assertEquals(1, result.getErrors().size());
+        assertFalse(result.isSkipped());
     }
 
     @Test public void testValidatePassword() {
@@ -234,10 +276,12 @@
 
         result = validator.validate(null, prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         prop.setVariable("secret");
         result = validator.validate(null, prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
     }
 
     @Test public void testValidatePath() {
@@ -248,9 +292,11 @@
 
         result = validator.validate("/a/b/c", prop);
         assertTrue(result.isValid());
+        assertFalse(result.isSkipped());
 
         result = validator.validate("hello world", prop);
         assertEquals(1, result.getErrors().size());
+        assertFalse(result.isSkipped());
     }
     
     @Test public void testValidateRange() {
@@ -360,37 +406,45 @@
         PropertyValidationResult result;
         result = validator.validate(values, prop);
         assertEquals(1, result.getErrors().size());
+        assertFalse(result.isSkipped());
 
         // cardinality 3 - no excludes/includes
         prop.setCardinality(3);
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertTrue(result.getErrors().isEmpty());
 
         values.add("d");
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertEquals(1, result.getErrors().size());
 
         // excludes
         prop.setExcludes(new String[] {"d", "e"});
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertEquals(2, result.getErrors().size()); // cardinality and exclude
 
         values.remove("d");
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertTrue(result.getErrors().isEmpty());
 
         // includes
         prop.setIncludes(new String[] {"b"});
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertTrue(result.getErrors().isEmpty());
 
         prop.setIncludes(new String[] {"x"});
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertEquals(1, result.getErrors().size());
 
         values.add("x");
         values.remove("a");
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertTrue(result.getErrors().isEmpty());
     }
 
@@ -402,37 +456,45 @@
         // default cardinality - no excludes/includes
         PropertyValidationResult result;
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertEquals(1, result.getErrors().size());
 
         // cardinality 3 - no excludes/includes
         prop.setCardinality(3);
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertTrue(result.getErrors().isEmpty());
 
         values = new String[] {"a", "b", "c", "d"};
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertEquals(1, result.getErrors().size());
 
         // excludes
         prop.setExcludes(new String[] {"d", "e"});
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertEquals(2, result.getErrors().size()); // cardinality and exclude
 
         values = new String[] {"a", "b", "c"};
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertTrue(result.getErrors().isEmpty());
 
         // includes
         prop.setIncludes(new String[] {"b"});
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertTrue(result.getErrors().isEmpty());
 
         prop.setIncludes(new String[] {"x"});
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertEquals(1, result.getErrors().size());
 
         values = new String[] {"b", "c", "x"};
         result = validator.validate(values, prop);
+        assertFalse(result.isSkipped());
         assertTrue(result.getErrors().isEmpty());
     }
 
@@ -445,4 +507,57 @@
         assertEquals(1, result.getWarnings().size());
         assertEquals("This is deprecated", result.getWarnings().get(0));
     }
+
+    @Test public void testPlaceholdersString() {
+        final PropertyDescription desc = new PropertyDescription();
+
+        PropertyValidationResult result = null;
+
+        result = validator.validate("$[env:variable]", desc);
+        assertTrue(result.isValid());
+        assertTrue(result.isSkipped());
+
+        result = validator.validate("$[prop:variable]", desc);
+        assertTrue(result.isValid());
+        assertTrue(result.isSkipped());
+
+        result = validator.validate("$[secret:variable]", desc);
+        assertTrue(result.isValid());
+        assertTrue(result.isSkipped());
+    }
+
+    @Test public void testPlaceholdersNumber() {
+        final PropertyDescription desc = new PropertyDescription();
+        desc.setType(PropertyType.INTEGER);
+
+        PropertyValidationResult result = null;
+
+        result = validator.validate("$[env:variable]", desc);
+        assertTrue(result.isValid());
+        assertTrue(result.isSkipped());
+
+        result = validator.validate("$[prop:variable]", desc);
+        assertTrue(result.isValid());
+        assertTrue(result.isSkipped());
+
+        result = validator.validate("$[secret:variable]", desc);
+        assertTrue(result.isValid());
+        assertTrue(result.isSkipped());
+    }
+
+    @Test public void testPlaceholdersArray() {
+        final PropertyDescription desc = new PropertyDescription();
+        desc.setType(PropertyType.INTEGER);
+        desc.setCardinality(-1);
+
+        PropertyValidationResult result = null;
+
+        result = validator.validate(new Object[] {5, "$[env:variable]"}, desc);
+        assertTrue(result.isValid());
+        assertTrue(result.isSkipped());
+
+        result = validator.validate(new Object[] {"hello", "$[env:variable]"}, desc);
+        assertFalse(result.isValid());
+        assertTrue(result.isSkipped());
+    }
 }