You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by kw...@apache.org on 2014/11/27 15:52:30 UTC

svn commit: r1642166 - in /sling/trunk/contrib/extensions/validation: api/src/main/java/org/apache/sling/validation/api/ core/src/main/java/org/apache/sling/validation/impl/ core/src/main/java/org/apache/sling/validation/impl/util/ core/src/test/java/o...

Author: kwin
Date: Thu Nov 27 14:52:29 2014
New Revision: 1642166

URL: http://svn.apache.org/r1642166
Log:
SLING-4011, allow pattern matching on properties and child resources

also some refactoring of the ValidationServiceImplTest

Modified:
    sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ChildResource.java
    sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ResourceProperty.java
    sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ChildResourceImpl.java
    sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/Constants.java
    sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ResourcePropertyImpl.java
    sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java
    sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java
    sling/trunk/contrib/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java

Modified: sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ChildResource.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ChildResource.java?rev=1642166&r1=1642165&r2=1642166&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ChildResource.java (original)
+++ sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ChildResource.java Thu Nov 27 14:52:29 2014
@@ -18,7 +18,9 @@
  */
 package org.apache.sling.validation.api;
 
+import java.util.List;
 import java.util.Set;
+import java.util.regex.Pattern;
 
 /**
  * Defines the validation rules for a child resource, allowing {@link ValidationModel}s to be applied to {@link
@@ -27,11 +29,19 @@ import java.util.Set;
 public interface ChildResource {
 
     /**
-     * Return this resource's name.
+     * Return this resource's name. This must match the name of the child resource which is validated through this section of the validation model.
+     * Either this method or {@link getNamePattern} must not return {@code null}
      *
-     * @return the name
+     * @return the name (if one is set) or {@code null)
      */
     String getName();
+    
+    /**
+     * Returns this resource's name pattern. Either this method or {@link getName} must not return {@code null}
+     *
+     * @return the name pattern (if one is set) or {@code null)
+     */
+    Pattern getNamePattern();
 
     /**
      * Returns the properties this child resource is expected to have.
@@ -39,4 +49,10 @@ public interface ChildResource {
      * @return the properties set
      */
     Set<ResourceProperty> getProperties();
+    
+    /**
+     * Returns the child resources of this part of the Validation Model
+     * @return child resources. Never {@code null}.
+     */
+    List<ChildResource> getChildren();
 }

Modified: sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ResourceProperty.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ResourceProperty.java?rev=1642166&r1=1642165&r2=1642166&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ResourceProperty.java (original)
+++ sling/trunk/contrib/extensions/validation/api/src/main/java/org/apache/sling/validation/api/ResourceProperty.java Thu Nov 27 14:52:29 2014
@@ -19,6 +19,7 @@
 package org.apache.sling.validation.api;
 
 import java.util.List;
+import java.util.regex.Pattern;
 
 /**
  * Describes a {@link org.apache.sling.api.resource.Resource} property.
@@ -27,10 +28,18 @@ public interface ResourceProperty {
 
     /**
      * Returns the name of this property.
+     * Either this method or {@link getNamePattern} must not return {@code null}
      *
-     * @return the name
+     * @return the name or {@code null}
      */
     String getName();
+    
+    /**
+     * Returns the name pattern for this property. Either this method or {@link getName} must not return {@code null}
+     *
+     * @return the name pattern (if one is set) or {@code null)
+     */
+    Pattern getNamePattern();
 
     /**
      * Returns {@code true} if this property is expected to be a multiple property (e.g. array of values).

Modified: sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ChildResourceImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ChildResourceImpl.java?rev=1642166&r1=1642165&r2=1642166&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ChildResourceImpl.java (original)
+++ sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ChildResourceImpl.java Thu Nov 27 14:52:29 2014
@@ -1,9 +1,13 @@
 package org.apache.sling.validation.impl;
 
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
 
 import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.validation.api.ChildResource;
 import org.apache.sling.validation.api.ResourceProperty;
 import org.apache.sling.validation.api.Validator;
@@ -14,16 +18,37 @@ import org.apache.sling.validation.impl.
  */
 public class ChildResourceImpl implements ChildResource {
 
-    private String name;
-    private Set<ResourceProperty> properties;
+    private final String name;
+    private final Pattern namePattern;
+    private final Set<ResourceProperty> properties;
+    private final List<ChildResource> children;
 
-    public ChildResourceImpl(Resource modelResource, Resource childResource, Map<String, Validator<?>> validatorsMap) {
+    public ChildResourceImpl(Resource modelResource, Resource childResource, Map<String, Validator<?>> validatorsMap, List<ChildResource> children) {
         String root = modelResource.getPath();
         if (!childResource.getPath().startsWith(root)) {
             throw new IllegalArgumentException("Expected resource " + childResource.getPath() + " to be under root path " + root);
         }
-        name = childResource.getPath().replaceFirst(root + "/", "").replaceAll(Constants.CHILDREN + "/", "");
+        // if pattern is set, always use that
+        ValueMap childrenProperties = childResource.adaptTo(ValueMap.class);
+        if (childrenProperties == null) {
+            throw new IllegalStateException("Could not adapt resource " + childResource.getPath() + " to ValueMap");
+        }
+        if (childrenProperties.containsKey(Constants.NAME_REGEX)) {
+            name = null;
+            String regex = childrenProperties.get(Constants.NAME_REGEX, String.class);
+            try {
+                namePattern = Pattern.compile(regex);
+            } catch (PatternSyntaxException e) {
+                throw new IllegalArgumentException("Given regular expression is invalid: " + regex);
+            }
+        } else {
+            // otherwise fall back to the name
+            name = childResource.getName();
+            namePattern = null;
+        }
+        
         properties = JCRBuilder.buildProperties(validatorsMap, childResource.getChild(Constants.PROPERTIES));
+        this.children = children;
     }
 
     @Override
@@ -35,4 +60,13 @@ public class ChildResourceImpl implement
     public Set<ResourceProperty> getProperties() {
         return properties;
     }
+
+    @Override
+    public Pattern getNamePattern() {
+        return namePattern;
+    }
+    
+    public List<ChildResource> getChildren() {
+        return children;
+    }
 }

Modified: sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/Constants.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/Constants.java?rev=1642166&r1=1642165&r2=1642166&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/Constants.java (original)
+++ sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/Constants.java Thu Nov 27 14:52:29 2014
@@ -33,4 +33,5 @@ public final class Constants {
     public static final String VALIDATORS = "validators";
     public static final String VALIDATOR_ARGUMENTS = "validatorArguments";
     public static final String CHILDREN = "children";
+    public static final String NAME_REGEX = "name-regex";
 }

Modified: sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ResourcePropertyImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ResourcePropertyImpl.java?rev=1642166&r1=1642165&r2=1642166&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ResourcePropertyImpl.java (original)
+++ sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ResourcePropertyImpl.java Thu Nov 27 14:52:29 2014
@@ -19,6 +19,7 @@
 package org.apache.sling.validation.impl;
 
 import java.util.List;
+import java.util.regex.Pattern;
 
 import org.apache.sling.validation.api.ParameterizedValidator;
 import org.apache.sling.validation.api.ResourceProperty;
@@ -28,13 +29,16 @@ public class ResourcePropertyImpl implem
     private String name;
     private boolean isMultiple;
     private List<ParameterizedValidator> validators;
-
-    public ResourcePropertyImpl(String name, List<ParameterizedValidator> validators) {
-        this(name, false, validators);
-    }
-
-    public ResourcePropertyImpl(String name, boolean isMultiple, List<ParameterizedValidator> validators) {
-        this.name = name;
+    private Pattern namePattern;
+    
+    public ResourcePropertyImpl(String name, String nameRegex, boolean isMultiple, List<ParameterizedValidator> validators) {
+        if (nameRegex != null) {
+            this.name = null;
+            this.namePattern = Pattern.compile(nameRegex);
+        } else {
+            this.name = name;
+            this.namePattern = null;
+        }
         this.isMultiple = isMultiple;
         this.validators = validators;
     }
@@ -45,6 +49,11 @@ public class ResourcePropertyImpl implem
     }
 
     @Override
+    public Pattern getNamePattern() {
+        return namePattern;
+    }
+
+    @Override
     public boolean isMultiple() {
         return isMultiple;
     }
@@ -53,4 +62,6 @@ public class ResourcePropertyImpl implem
     public List<ParameterizedValidator> getValidators() {
         return validators;
     }
+
+    
 }

Modified: sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java?rev=1642166&r1=1642165&r2=1642166&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java (original)
+++ sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java Thu Nov 27 14:52:29 2014
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Matcher;
 
 import javax.jcr.query.Query;
 
@@ -119,18 +120,50 @@ public class ValidationServiceImpl imple
         ValidationResultImpl result = new ValidationResultImpl();
 
         // validate direct properties of the resource
-        validateValueMap(resource.adaptTo(ValueMap.class), model.getResourceProperties(), result, "");
+        validateValueMap(resource.adaptTo(ValueMap.class), "", model.getResourceProperties(), result );
 
         // validate children resources, if any
-        for (ChildResource childResource : model.getChildren()) {
-            Resource expectedResource = resource.getChild(childResource.getName());
-            if (expectedResource != null) {
-                validateValueMap(expectedResource.adaptTo(ValueMap.class), childResource.getProperties(), result, childResource.getName() + "/");
+        validateChildren(resource, "", model.getChildren(), result);
+        return result;
+    }
+
+    /**
+     * Validates a child resource with the help of the given {@code ChildResource} entry from the validation model
+     * @param resource
+     * @param relativePath relativePath of the resource (must be empty or end with "/")
+     * @param result
+     * @param childResources
+     */
+    private void validateChildren(Resource resource, String relativePath, List<ChildResource> childResources, ValidationResultImpl result) {
+        // validate children resources, if any
+        for (ChildResource childResource : childResources) {
+            // if a pattern is set we validate all children matching that pattern
+            if (childResource.getNamePattern() != null) {
+                boolean foundMatch = false;
+                for (Resource child : resource.getChildren()) {
+                    Matcher matcher = childResource.getNamePattern().matcher(child.getName());
+                    if (matcher.matches()) {
+                       validateChildResource(child, relativePath, childResource, result);
+                       foundMatch = true;
+                    }
+                }
+                if (!foundMatch) {
+                    result.addFailureMessage(relativePath + childResource.getNamePattern().pattern(), "Missing required child resource.");
+                }
             } else {
-                result.addFailureMessage(childResource.getName(), "Missing required child resource.");
-            }
+                Resource expectedResource = resource.getChild(childResource.getName());
+                if (expectedResource != null) {
+                    validateChildResource(expectedResource, relativePath, childResource, result);
+                } else {
+                    result.addFailureMessage(relativePath + childResource.getName(), "Missing required child resource.");
+                }
+            } 
         }
-        return result;
+    }
+    
+    private void validateChildResource(Resource resource, String relativePath, ChildResource childResource, ValidationResultImpl result) {
+        validateValueMap(resource.adaptTo(ValueMap.class), relativePath + resource.getName() + "/", childResource.getProperties(), result);
+        validateChildren(resource, relativePath + resource.getName() + "/", childResource.getChildren(), result);
     }
 
     @Override
@@ -139,7 +172,7 @@ public class ValidationServiceImpl imple
             throw new IllegalArgumentException("ValidationResult.validate - cannot accept null parameters");
         }
         ValidationResultImpl result = new ValidationResultImpl();
-        validateValueMap(valueMap, model.getResourceProperties(), result, "");
+        validateValueMap(valueMap,  "", model.getResourceProperties(), result);
         return result;
     }
 
@@ -202,27 +235,44 @@ public class ValidationServiceImpl imple
         }
     }
 
-    private void validateValueMap(ValueMap valueMap, Set<ResourceProperty> resourceProperties,
-                                            ValidationResultImpl result, String relativePathName) {
+    private void validateValueMap(ValueMap valueMap, String relativePath, Set<ResourceProperty> resourceProperties,
+            ValidationResultImpl result) {
         if (valueMap == null) {
             throw new IllegalArgumentException("ValueMap may not be null");
         }
         for (ResourceProperty resourceProperty : resourceProperties) {
-            String property = resourceProperty.getName();
-            Object fieldValues = valueMap.get(property);
-            if (fieldValues == null) {
-                result.addFailureMessage(relativePathName + property, "Missing required property.");
-                return;
-            }
-            List<ParameterizedValidator> validators = resourceProperty.getValidators();
-            if (resourceProperty.isMultiple()) {
-                if (!fieldValues.getClass().isArray()) {
-                    result.addFailureMessage(relativePathName + property, "Expected multiple-valued property.");
-                    return;
+            if (resourceProperty.getNamePattern() != null) {
+                boolean foundMatch = false;
+                for (String key : valueMap.keySet()) {
+                    if (resourceProperty.getNamePattern().matcher(key).matches()) {
+                        foundMatch = true;
+                        validateValueMap(key, valueMap, relativePath, resourceProperty, result);
+                    }
+                }
+                if (!foundMatch) {
+                    result.addFailureMessage(relativePath + resourceProperty.getNamePattern(), "Missing required property.");
                 }
+            } else {
+                validateValueMap(resourceProperty.getName(), valueMap, relativePath, resourceProperty, result);
+            }
+        }
+    }
+    
+    
+    private void validateValueMap(String property, ValueMap valueMap, String relativePath, ResourceProperty resourceProperty, ValidationResultImpl result) {
+        Object fieldValues = valueMap.get(property);
+        if (fieldValues == null) {
+            result.addFailureMessage(relativePath + property, "Missing required property.");
+            return;
+        }
+        List<ParameterizedValidator> validators = resourceProperty.getValidators();
+        if (resourceProperty.isMultiple()) {
+            if (!fieldValues.getClass().isArray()) {
+                result.addFailureMessage(relativePath + property, "Expected multiple-valued property.");
+                return;
             }
-            validatePropertyValue(result, property, relativePathName, valueMap, validators);
         }
+        validatePropertyValue(result, property, relativePath, valueMap, validators);
     }
 
     /**

Modified: sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java?rev=1642166&r1=1642165&r2=1642166&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java (original)
+++ sling/trunk/contrib/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java Thu Nov 27 14:52:29 2014
@@ -59,6 +59,7 @@ public class JCRBuilder {
                 String fieldName = property.getName();
                 ValueMap propertyValueMap = property.adaptTo(ValueMap.class);
                 Boolean propertyMultiple = PropertiesUtil.toBoolean(propertyValueMap.get(Constants.PROPERTY_MULTIPLE), false);
+                String nameRegex = PropertiesUtil.toString(propertyValueMap.get(Constants.NAME_REGEX), null);
                 Resource validators = property.getChild(Constants.VALIDATORS);
                 List<ParameterizedValidator> parameterizedValidators = new ArrayList<ParameterizedValidator>();
                 if (validators != null) {
@@ -86,7 +87,7 @@ public class JCRBuilder {
                         parameterizedValidators.add(new ParameterizedValidatorImpl(v, new ValueMapDecorator(validatorArgumentsMap)));
                     }
                 }
-                ResourceProperty f = new ResourcePropertyImpl(fieldName, propertyMultiple, parameterizedValidators);
+                ResourceProperty f = new ResourcePropertyImpl(fieldName, nameRegex, propertyMultiple, parameterizedValidators);
                 properties.add(f);
             }
         }
@@ -109,9 +110,8 @@ public class JCRBuilder {
         Resource childrenResource = rootResource.getChild(Constants.CHILDREN);
         if (childrenResource != null) {
             for (Resource child : childrenResource.getChildren()) {
-                ChildResource childResource = new ChildResourceImpl(modelResource, child, validatorsMap);
+                ChildResource childResource = new ChildResourceImpl(modelResource, child, validatorsMap, buildChildren(modelResource, child, validatorsMap));
                 children.add(childResource);
-                children.addAll(buildChildren(modelResource, child, validatorsMap));
             }
         }
         return children;

Modified: sling/trunk/contrib/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java?rev=1642166&r1=1642165&r2=1642166&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java (original)
+++ sling/trunk/contrib/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java Thu Nov 27 14:52:29 2014
@@ -32,6 +32,7 @@ import java.util.Map;
 
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
@@ -78,9 +79,9 @@ public class ValidationServiceImplTest {
         });
         ResourceResolver rr = rrf.getAdministrativeResourceResolver(null);
         if (rr != null) {
-            appsValidatorsRoot = ResourceUtil.getOrCreateResource(rr, APPS + "/" + VALIDATION_MODELS_RELATIVE_PATH, (Map) null,
+            appsValidatorsRoot = ResourceUtil.getOrCreateResource(rr, APPS + "/" + VALIDATION_MODELS_RELATIVE_PATH, (Map<String, Object>) null,
                     "sling:Folder", true);
-            libsValidatorsRoot = ResourceUtil.getOrCreateResource(rr, LIBS + "/" + VALIDATION_MODELS_RELATIVE_PATH, (Map) null,
+            libsValidatorsRoot = ResourceUtil.getOrCreateResource(rr, LIBS + "/" + VALIDATION_MODELS_RELATIVE_PATH, (Map<String, Object>) null,
                     "sling:Folder", true);
             rr.close();
         }
@@ -113,20 +114,17 @@ public class ValidationServiceImplTest {
     public void testGetValidationModel() throws Exception {
         validationService.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new RegexValidator());
         
-        List<TestProperty> properties = new ArrayList<TestProperty>();
-        TestProperty property = new TestProperty();
-        property.name = "field1";
-        property.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", null);
-        properties.add(property);
+        TestProperty property = new TestProperty("field1");
+        property.addValidator("org.apache.sling.validation.impl.validators.RegexValidator");
         ResourceResolver rr = rrf.getAdministrativeResourceResolver(null);
         Resource model1 = null, model2 = null;
         try {
             if (rr != null) {
                 model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test",
-                        new String[]{"/apps/validation"}, properties);
+                        new String[]{"/apps/validation"}, property);
                 model2 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel2", "sling/validation/test",
                         new String[]{"/apps/validation/1",
-                        "/apps/validation/2"}, properties);
+                        "/apps/validation/2"}, property);
             }
 
             // BEST MATCHING PATH = /apps/validation/1; assume the applicable paths contain /apps/validation/2
@@ -155,22 +153,19 @@ public class ValidationServiceImplTest {
     public void testGetValidationModelWithOverlay() throws Exception {
         validationService.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new RegexValidator());
 
-        List<TestProperty> fields = new ArrayList<TestProperty>();
-        TestProperty field = new TestProperty();
-        field.name = "field1";
-        field.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", null);
-        fields.add(field);
+        TestProperty field = new TestProperty("field1");
+        field.addValidator("org.apache.sling.validation.impl.validators.RegexValidator");
         ResourceResolver rr = rrf.getAdministrativeResourceResolver(null);
         Resource model1 = null, model2 = null, model3 = null;
         try {
             if (rr != null) {
                 model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test",
-                        new String[]{"/apps/validation/1"}, fields);
+                        new String[]{"/apps/validation/1"}, field);
                 model2 = createValidationModelResource(rr, appsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test",
                         new String[]{"/apps/validation/1",
-                                "/apps/validation/2"}, fields);
+                                "/apps/validation/2"}, field);
                 model3 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel2", "sling/validation/test",
-                        new String[]{"/apps/validation/3"}, fields);
+                        new String[]{"/apps/validation/3"}, field);
             }
 
             // BEST MATCHING PATH = /apps/validation/1; assume the applicable paths contain /apps/validation/2
@@ -200,19 +195,16 @@ public class ValidationServiceImplTest {
     @Test()
     public void testValueMapWithWrongDataType() throws Exception {
         validationService.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new RegexValidator());
-
-        List<TestProperty> properties = new ArrayList<TestProperty>();
-        TestProperty property = new TestProperty();
-        property.name = "field1";
         validationService.validators.put("org.apache.sling.validation.impl.util.examplevalidators.DateValidator",new DateValidator());
-        property.validators.put("org.apache.sling.validation.impl.util.examplevalidators.DateValidator", null);
-        properties.add(property);
+
+        TestProperty property = new TestProperty("field1");
+        property.addValidator("org.apache.sling.validation.impl.util.examplevalidators.DateValidator");
         ResourceResolver rr = rrf.getAdministrativeResourceResolver(null);
         Resource model1 = null;
         try {
             if (rr != null) {
                 model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test",
-                        new String[]{"/apps/validation"}, properties);
+                        new String[]{"/apps/validation"}, property);
             }
             ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource");
             HashMap<String, Object> hashMap = new HashMap<String, Object>() {{
@@ -239,21 +231,17 @@ public class ValidationServiceImplTest {
     public void testValueMapWithCorrectDataType() throws Exception {
         validationService.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new RegexValidator());
 
-        List<TestProperty> fields = new ArrayList<TestProperty>();
-        TestProperty field = new TestProperty();
-        field.name = "field1";
-        field.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new String[] {"regex=^\\p{L}+$"});
-        fields.add(field);
-        field.name = "field2";
+        TestProperty field1 = new TestProperty("field1");
+        field1.addValidator("org.apache.sling.validation.impl.validators.RegexValidator", "regex=^\\p{L}+$");
+        TestProperty field2 = new TestProperty("field2");
         final String TEST_REGEX = "^test$";
-        field.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new String[] {"regex="+TEST_REGEX});
-        fields.add(field);
+        field2.addValidator("org.apache.sling.validation.impl.validators.RegexValidator", "regex="+TEST_REGEX);
         ResourceResolver rr = rrf.getAdministrativeResourceResolver(null);
         Resource model1 = null;
         try {
             if (rr != null) {
                 model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test",
-                        new String[]{"/apps/validation"}, fields);
+                        new String[]{"/apps/validation"}, field1, field2);
             }
             ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource");
             HashMap<String, Object> hashMap = new HashMap<String, Object>() {{
@@ -277,68 +265,179 @@ public class ValidationServiceImplTest {
             }
         }
     }
+    
 
     @Test
     public void testResourceWithMissingChildProperty() throws Exception {
         validationService.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new RegexValidator());
 
-        List<TestProperty> fields = new ArrayList<TestProperty>();
-        TestProperty property = new TestProperty();
-        property.name = "field1";
-        property.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new String[] {RegexValidator.REGEX_PARAM + "=" + "\\d"});
-        fields.add(property);
+        TestProperty property = new TestProperty("field1");
+        property.addValidator("org.apache.sling.validation.impl.validators.RegexValidator", RegexValidator.REGEX_PARAM + "=" + "\\d");
         ResourceResolver rr = rrf.getAdministrativeResourceResolver(null);
         Resource model1 = null;
         Resource testResource = null;
         try {
             if (rr != null) {
                 model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test",
-                        new String[]{"/apps/validation"}, fields);
-                Resource modelChildren = rr.create(model1, "children", new HashMap<String, Object>(){{
+                        new String[]{"/apps/validation"}, property);
+                
+                Resource child = createValidationModelChildResource(model1, "child1", null, new TestProperty("hello"));
+                createValidationModelChildResource(child, "grandChild1", null, new TestProperty("hello"));
+
+                testResource = ResourceUtil.getOrCreateResource(rr, "/apps/validation/1/resource", JcrConstants.NT_UNSTRUCTURED,
+                        JcrConstants.NT_UNSTRUCTURED, true);
+                ModifiableValueMap mvm = testResource.adaptTo(ModifiableValueMap.class);
+                mvm.put("field1", "1");
+                rr.commit();
+                
+                Resource childResource = rr.create(testResource, "child1", new HashMap<String, Object>(){{
                     put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
                 }});
-                Resource child = rr.create(modelChildren, "child1", new HashMap<String, Object>(){{
+                rr.commit();
+               
+                
+                Resource resourceChild = rr.create(testResource, "child1", new HashMap<String, Object>(){{
                     put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
                 }});
-                Resource childProperties = rr.create(child, "properties", new HashMap<String, Object>(){{
+                mvm = resourceChild.adaptTo(ModifiableValueMap.class);
+                mvm.put("hello", "1");
+                rr.commit();
+
+                // /apps/validation/1/resource/child1/grandChild1 will miss its mandatory "hello" property
+                Resource resourceGrandChild = rr.create(resourceChild, "grandChild1", new HashMap<String, Object>(){{
                     put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
                 }});
-                Resource childProperty = rr.create(childProperties, "hello", new HashMap<String, Object>(){{
+                rr.commit();
+                
+                mvm = resourceGrandChild.adaptTo(ModifiableValueMap.class);
+                mvm.put("field1", "1");
+                rr.commit();
+            }
+            ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource");
+            ValidationResult vr = validationService.validate(testResource, vm);
+            assertFalse(vr.isValid());
+            assertThat(vr.getFailureMessages(), Matchers.hasKey("child1/grandChild1/hello"));
+            assertThat(vr.getFailureMessages().keySet(), Matchers.hasSize(1));
+        } finally {
+            if (rr != null) {
+                if (model1 != null) {
+                    rr.delete(model1);
+                }
+                if (testResource != null) {
+                    rr.delete(testResource);
+                }
+                rr.commit();
+                rr.close();
+            }
+        }
+    }
+    
+    @Test
+    public void testResourceWithNestedChildren() throws Exception {
+        validationService.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new RegexValidator());
+
+        TestProperty property = new TestProperty("field1");
+        property.addValidator("org.apache.sling.validation.impl.validators.RegexValidator", RegexValidator.REGEX_PARAM + "=" + "\\d");
+        ResourceResolver rr = rrf.getAdministrativeResourceResolver(null);
+        Resource model1 = null;
+        Resource testResource = null;
+        try {
+            if (rr != null) {
+                model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test",
+                        new String[]{"/apps/validation"}, property);
+                
+                Resource child = createValidationModelChildResource(model1, "child1", null, new TestProperty("hello"));
+                createValidationModelChildResource(child, "grandChild1", null, new TestProperty("hello"));
+
+                testResource = ResourceUtil.getOrCreateResource(rr, "/apps/validation/1/resource", JcrConstants.NT_UNSTRUCTURED,
+                        JcrConstants.NT_UNSTRUCTURED, true);
+                Resource childResource = rr.create(testResource, "child1", new HashMap<String, Object>(){{
                     put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
-                    put(Constants.PROPERTY_TYPE, "string");
                 }});
-                Resource grandChildren = rr.create(child, "children", new HashMap<String, Object>(){{
+
+                ModifiableValueMap mvm = testResource.adaptTo(ModifiableValueMap.class);
+                mvm.put("field1", "1");
+
+                Resource resourceChild = rr.create(testResource, "child1", new HashMap<String, Object>(){{
                     put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
                 }});
-                Resource grandChild = rr.create(grandChildren, "grandChild1", new HashMap<String, Object>(){{
+                mvm = resourceChild.adaptTo(ModifiableValueMap.class);
+                mvm.put("hello", "test");
+
+                Resource resourceGrandChild = rr.create(resourceChild, "grandChild1", new HashMap<String, Object>(){{
                     put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
                 }});
+                mvm = resourceGrandChild.adaptTo(ModifiableValueMap.class);
+                mvm.put("hello", "test");
+                rr.commit();
+            }
+            ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource");
+            ValidationResult vr = validationService.validate(testResource, vm);
+            assertTrue(vr.isValid());
+        } finally {
+            if (rr != null) {
+                if (model1 != null) {
+                    rr.delete(model1);
+                }
+                if (testResource != null) {
+                    rr.delete(testResource);
+                }
+                rr.commit();
+                rr.close();
+            }
+        }
+    }
+    
+    @Test
+    public void testResourceWithNestedChildrenAndPatternMatching() throws Exception {
+        validationService.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new RegexValidator());
 
+        TestProperty property = new TestProperty("field1");
+        property.addValidator("org.apache.sling.validation.impl.validators.RegexValidator", RegexValidator.REGEX_PARAM + "=" + "\\d");
+        ResourceResolver rr = rrf.getAdministrativeResourceResolver(null);
+        Resource model1 = null;
+        Resource testResource = null;
+        try {
+            if (rr != null) {
+                model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test",
+                        new String[]{"/apps/validation"}, property);
+                Resource child = createValidationModelChildResource(model1, "child1", "child.*", new TestProperty("hello"));
+                createValidationModelChildResource(child, "grandChild", "grandChild.*", new TestProperty("hello"));
+                rr.commit();
+                
                 testResource = ResourceUtil.getOrCreateResource(rr, "/apps/validation/1/resource", JcrConstants.NT_UNSTRUCTURED,
                         JcrConstants.NT_UNSTRUCTURED, true);
+                ModifiableValueMap mvm = testResource.adaptTo(ModifiableValueMap.class);
+                mvm.put("field1", "1");
+                
                 Resource childResource = rr.create(testResource, "child1", new HashMap<String, Object>(){{
                     put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
                 }});
-                rr.commit();
 
-                ModifiableValueMap mvm = testResource.adaptTo(ModifiableValueMap.class);
-                mvm.put("field1", "1");
-                rr.commit();
+                mvm = childResource.adaptTo(ModifiableValueMap.class);
+                mvm.put("hello", "test");
 
-                // /apps/validation/1/resource/child1 will miss its mandatory "hello" property
-                Resource resourceChild = rr.create(testResource, "child1", new HashMap<String, Object>(){{
+                Resource resourceChild = rr.create(testResource, "child2", new HashMap<String, Object>(){{
                     put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
                 }});
+                mvm = resourceChild.adaptTo(ModifiableValueMap.class);
+                mvm.put("hello2", "test");
 
                 Resource resourceGrandChild = rr.create(resourceChild, "grandChild1", new HashMap<String, Object>(){{
                     put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
                 }});
+                mvm = resourceGrandChild.adaptTo(ModifiableValueMap.class);
+                mvm.put("hello", "test");
                 rr.commit();
             }
             ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource");
             ValidationResult vr = validationService.validate(testResource, vm);
             assertFalse(vr.isValid());
-            assertThat(vr.getFailureMessages(), Matchers.hasKey("child1/hello"));
+            // check for correct error message
+            Map<String, List<String>> expectedFailureMessages = new HashMap<String, List<String>>();
+            expectedFailureMessages.put("child2/hello", Arrays.asList("Missing required property."));
+            expectedFailureMessages.put("child1/grandChild.*", Arrays.asList("Missing required child resource."));
+            Assert.assertThat(vr.getFailureMessages().entrySet(), Matchers.equalTo(expectedFailureMessages.entrySet()));
         } finally {
             if (rr != null) {
                 if (model1 != null) {
@@ -354,21 +453,65 @@ public class ValidationServiceImplTest {
     }
     
     @Test
+    public void testResourceWithPropertyPatternMatching() throws Exception {
+        validationService.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new RegexValidator());
+
+        TestProperty property = new TestProperty("field1");
+        final String TEST_REGEX = "^testvalue.*$";
+        property.addValidator("org.apache.sling.validation.impl.validators.RegexValidator", "regex="+TEST_REGEX);
+        property.setNameRegex("property[1-4]");
+        
+        TestProperty otherProperty = new TestProperty("field2");
+        otherProperty.addValidator("org.apache.sling.validation.impl.validators.RegexValidator", "regex="+TEST_REGEX);
+        otherProperty.setNameRegex("otherproperty[1-4]");
+        ResourceResolver rr = rrf.getAdministrativeResourceResolver(null);
+        Resource model1 = null;
+        try {
+            if (rr != null) {
+                model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test",
+                        new String[]{"/apps/validation"}, property, otherProperty);
+            }
+            ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource");
+            HashMap<String, Object> hashMap = new HashMap<String, Object>() {{
+                put("property1", "testvalue1"); 
+                put("property2", "test1value1"); // does not match validator pattern
+                put("property3", "testvalue1");
+                put("property4", "1testvalue1"); // does not match validator pattern
+                put("property5", "invalid");     // does not match property name pattern
+            }};
+            ValueMap map = new ValueMapDecorator(hashMap);
+            ValidationResult vr = validationService.validate(map, vm);
+            assertFalse(vr.isValid());
+            // check for correct error message
+            Map<String, List<String>> expectedFailureMessages = new HashMap<String, List<String>>();
+            expectedFailureMessages.put("property2", Arrays.asList("Property does not match the pattern " + TEST_REGEX));
+            expectedFailureMessages.put("property4", Arrays.asList("Property does not match the pattern " + TEST_REGEX));
+            expectedFailureMessages.put("otherproperty[1-4]", Arrays.asList("Missing required property."));
+            Assert.assertThat(vr.getFailureMessages().entrySet(), Matchers.equalTo(expectedFailureMessages.entrySet()));
+        } finally {
+            if (model1 != null) {
+                rr.delete(model1);
+            }
+            if (rr != null) {
+                rr.commit();
+                rr.close();
+            }
+        }
+    }
+    
+    @Test
     public void testResourceWithMultivalueProperties() throws Exception {
         validationService.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new RegexValidator());
 
-        List<TestProperty> fields = new ArrayList<TestProperty>();
-        TestProperty property = new TestProperty();
-        property.name = "field1";
+        TestProperty property = new TestProperty("field1");
         final String TEST_REGEX = "^testvalue.*$";
-        property.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new String[] {"regex="+TEST_REGEX});
-        fields.add(property);
+        property.addValidator("org.apache.sling.validation.impl.validators.RegexValidator", "regex="+TEST_REGEX);
         ResourceResolver rr = rrf.getAdministrativeResourceResolver(null);
         Resource model1 = null;
         try {
             if (rr != null) {
                 model1 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test",
-                        new String[]{"/apps/validation"}, fields);
+                        new String[]{"/apps/validation"}, property);
             }
             ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource");
             HashMap<String, Object> hashMap = new HashMap<String, Object>() {{
@@ -393,7 +536,7 @@ public class ValidationServiceImplTest {
     }
 
     private Resource createValidationModelResource(ResourceResolver rr, String root, String name, String validatedResourceType,
-                                               String[] applicableResourcePaths, List<TestProperty> properties) throws Exception {
+                                               String[] applicableResourcePaths, TestProperty... properties) throws Exception {
         Map<String, Object> modelProperties = new HashMap<String, Object>();
         modelProperties.put(Constants.VALIDATED_RESOURCE_TYPE, validatedResourceType);
         modelProperties.put(Constants.APPLICABLE_PATHS, applicableResourcePaths);
@@ -401,34 +544,59 @@ public class ValidationServiceImplTest {
         modelProperties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
         Resource model = ResourceUtil.getOrCreateResource(rr, root + "/" + name, modelProperties, JcrResourceConstants.NT_SLING_FOLDER, true);
         if (model != null) {
-            Resource propertiesResource = ResourceUtil.getOrCreateResource(rr, model.getPath() + "/" + Constants
-                    .PROPERTIES, JcrConstants.NT_UNSTRUCTURED, null, true);
-            if (propertiesResource != null) {
-                for (TestProperty property : properties) {
-                    Map<String, Object> modelPropertyJCRProperties = new HashMap<String, Object>();
-                    modelPropertyJCRProperties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
-                    Resource propertyResource = ResourceUtil.getOrCreateResource(rr, propertiesResource.getPath() + "/" + property.name,
-                            modelPropertyJCRProperties, null, true);
-                    if (propertyResource != null) {
-                        Resource validators = ResourceUtil.getOrCreateResource(rr,
-                                propertyResource.getPath() + "/" + Constants.VALIDATORS,
-                                JcrConstants.NT_UNSTRUCTURED, null, true);
-                        if (validators != null) {
-                            for (Map.Entry<String, String[]> v : property.validators.entrySet()) {
-                                Map<String, Object> validatorProperties = new HashMap<String, Object>();
-                                validatorProperties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
-                                if (v.getValue() != null) {
-                                    validatorProperties.put(Constants.VALIDATOR_ARGUMENTS, v.getValue());
-                                }
-                                 ResourceUtil.getOrCreateResource(rr, validators.getPath() + "/" + v.getKey(), validatorProperties, null,
-                                        true);
+            createValidationModelProperties(model, properties);
+        }
+        return model;
+    }
+    
+    private void createValidationModelProperties(Resource model, TestProperty... properties) throws PersistenceException {
+        ResourceResolver rr = model.getResourceResolver();
+        Resource propertiesResource = ResourceUtil.getOrCreateResource(rr, model.getPath() + "/" + Constants
+                .PROPERTIES, JcrConstants.NT_UNSTRUCTURED, null, true);
+        if (propertiesResource != null) {
+            for (TestProperty property : properties) {
+                Map<String, Object> modelPropertyJCRProperties = new HashMap<String, Object>();
+                modelPropertyJCRProperties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+                Resource propertyResource = ResourceUtil.getOrCreateResource(rr, propertiesResource.getPath() + "/" + property.name,
+                        modelPropertyJCRProperties, null, true);
+                if (propertyResource != null) {
+                    if (property.nameRegex != null) {
+                        ModifiableValueMap values = propertyResource.adaptTo(ModifiableValueMap.class);
+                        values.put(Constants.NAME_REGEX, property.nameRegex);
+                    }
+                    Resource validators = ResourceUtil.getOrCreateResource(rr,
+                            propertyResource.getPath() + "/" + Constants.VALIDATORS,
+                            JcrConstants.NT_UNSTRUCTURED, null, true);
+                    if (validators != null) {
+                        for (Map.Entry<String, String[]> v : property.validators.entrySet()) {
+                            Map<String, Object> validatorProperties = new HashMap<String, Object>();
+                            validatorProperties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+                            if (v.getValue() != null) {
+                                validatorProperties.put(Constants.VALIDATOR_ARGUMENTS, v.getValue());
                             }
+                             ResourceUtil.getOrCreateResource(rr, validators.getPath() + "/" + v.getKey(), validatorProperties, null,
+                                    true);
                         }
                     }
                 }
             }
         }
-        return model;
+    }
+    
+    private Resource createValidationModelChildResource(Resource parentResource, String name, String nameRegex, TestProperty... properties) throws PersistenceException {
+        ResourceResolver rr = parentResource.getResourceResolver();
+        Resource modelChildren = rr.create(parentResource, Constants.CHILDREN, new HashMap<String, Object>(){{
+            put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+        }});
+        Resource child = rr.create(modelChildren, name, new HashMap<String, Object>(){{
+            put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+        }});
+        if (nameRegex != null) {
+            ModifiableValueMap mvm = child.adaptTo(ModifiableValueMap.class);
+            mvm.put(Constants.NAME_REGEX, nameRegex);
+        }
+        createValidationModelProperties(child, properties);
+        return child;
     }
 
     private boolean arrayContainsString(String[] array, String string) {
@@ -445,16 +613,25 @@ public class ValidationServiceImplTest {
     }
 
     private class TestProperty {
-        String name;
-        Map<String, String[]> validators;
+        final String name;
+        String nameRegex;
+        final Map<String, String[]> validators;
 
-        TestProperty() {
+        TestProperty(String name) {
             validators = new HashMap<String, String[]>();
+            this.name = name;
+            this.nameRegex = null;
+        }
+        
+        TestProperty setNameRegex(String nameRegex) {
+            this.nameRegex = nameRegex;
+            return this;
+        }
+        
+        TestProperty addValidator(String name, String... parameters) {
+            validators.put(name, parameters);
+            return this;
         }
-    }
-
-    private class TestChild {
-
     }
 
 }