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 2017/03/02 13:27:51 UTC

svn commit: r1785131 [1/2] - in /sling/trunk/bundles/extensions/validation: api/src/main/java/org/apache/sling/validation/model/ api/src/main/java/org/apache/sling/validation/model/spi/ core/ core/src/main/java/org/apache/sling/validation/impl/ core/sr...

Author: kwin
Date: Thu Mar  2 13:27:50 2017
New Revision: 1785131

URL: http://svn.apache.org/viewvc?rev=1785131&view=rev
Log:
SLING-6588 refactor caching by moving it back to the model provider

some more refactoring allowed to get rid of dependencies towards commons.osgi and commons.thread

Added:
    sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/util/ChildResourceNameRegexMatcher.java   (with props)
    sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/util/ResourcePropertyNameMatcher.java   (with props)
    sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/util/ResourcePropertyNameRegexMatcher.java   (with props)
Removed:
    sling/trunk/bundles/extensions/validation/api/src/main/java/org/apache/sling/validation/model/spi/ValidationModelCache.java
    sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationModelCacheImpl.java
Modified:
    sling/trunk/bundles/extensions/validation/api/src/main/java/org/apache/sling/validation/model/ValidationModel.java
    sling/trunk/bundles/extensions/validation/api/src/main/java/org/apache/sling/validation/model/spi/ValidationModelProvider.java
    sling/trunk/bundles/extensions/validation/core/pom.xml
    sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationModelRetrieverImpl.java
    sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/MergedValidationModel.java
    sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/ValidationModelBuilder.java
    sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/ValidationModelImpl.java
    sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/resourcemodel/ResourceValidationModelProviderImpl.java
    sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationModelRetrieverImplTest.java
    sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java
    sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/model/MergedValidationModelTest.java
    sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/resourcemodel/ResourceValidationModelProviderImplTest.java

Modified: sling/trunk/bundles/extensions/validation/api/src/main/java/org/apache/sling/validation/model/ValidationModel.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/validation/api/src/main/java/org/apache/sling/validation/model/ValidationModel.java?rev=1785131&r1=1785130&r2=1785131&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/validation/api/src/main/java/org/apache/sling/validation/model/ValidationModel.java (original)
+++ sling/trunk/bundles/extensions/validation/api/src/main/java/org/apache/sling/validation/model/ValidationModel.java Thu Mar  2 13:27:50 2017
@@ -60,5 +60,11 @@ public interface ValidationModel {
      * @return the children list (can be empty if there are no children), never {@code null}
      */
     @Nonnull Collection<ChildResource> getChildren();
+    
+    /**
+     * 
+     * @return a string indicating the original source of this validation model, e.g. a resource path
+     */
+    @Nonnull String getSource();
 
 }

Modified: sling/trunk/bundles/extensions/validation/api/src/main/java/org/apache/sling/validation/model/spi/ValidationModelProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/validation/api/src/main/java/org/apache/sling/validation/model/spi/ValidationModelProvider.java?rev=1785131&r1=1785130&r2=1785131&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/validation/api/src/main/java/org/apache/sling/validation/model/spi/ValidationModelProvider.java (original)
+++ sling/trunk/bundles/extensions/validation/api/src/main/java/org/apache/sling/validation/model/spi/ValidationModelProvider.java Thu Mar  2 13:27:50 2017
@@ -18,20 +18,22 @@
  */
 package org.apache.sling.validation.model.spi;
 
-import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 import javax.annotation.Nonnull;
 
+import org.apache.sling.validation.ValidationService;
 import org.apache.sling.validation.model.ValidationModel;
 import org.apache.sling.validation.spi.Validator;
 import org.osgi.annotation.versioning.ProviderType;
 
 
 /**
- * All providers of {@link ValidationModel}s must implement this interface. In addition, if the model might become
- * invalid after some time, it is also the obligation of the provider implementation to invalidate the cache via the
- * {@link ValidationModelCache} OSGi service.
+ * All providers of {@link ValidationModel}s must implement this interface. Caching of validation models should be implemented in the provider itself, 
+ * because the providers are asked potentially multiple times for each {@link ValidationService#getValidationModel(org.apache.sling.api.resource.Resource, boolean)} or 
+ *  {@link ValidationService#getValidationModel(String, String, boolean)} call.
+ * 
  */
 @ProviderType
 public interface ValidationModelProvider {
@@ -39,16 +41,17 @@ public interface ValidationModelProvider
     /**
      * Retrieves the models responsible for validating the given resourceType.
      * 
-     * @param relativeResourceType
+     * @param relativeResourceType the relative resource (relative to one of the resource resolver's search paths)
      * @param validatorsMap
      *            all known validators in a map (key=id of validator). Only one of those should be used in the
      *            returned validation models.
-     * @return a Collection of {@link ValidationModel}s. Never {@code null}, but might be empty collection in case no
-     *         model for the given resource type could be found.
+     * @return a List of {@link ValidationModel}s. Never {@code null}, but might be empty collection in case no
+     *         model for the given resource type could be found. The order which model gets active is mostly determined by {@link ValidationModel#getApplicablePaths()} (longest path match wins) 
+     *         but in case there are multiple models having the same applicable path, the order being returned here is considered (i.e. the first one is taken).
      * @throws IllegalStateException
      *             in case a validation model was found but it is invalid
      */
-    @Nonnull Collection<ValidationModel> getModels(@Nonnull String relativeResourceType,
+    @Nonnull List<ValidationModel> getModels(@Nonnull String relativeResourceType,
             @Nonnull Map<String, Validator<?>> validatorsMap) throws IllegalStateException;
 
 }

Modified: sling/trunk/bundles/extensions/validation/core/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/validation/core/pom.xml?rev=1785131&r1=1785130&r2=1785131&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/validation/core/pom.xml (original)
+++ sling/trunk/bundles/extensions/validation/core/pom.xml Thu Mar  2 13:27:50 2017
@@ -139,18 +139,6 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.commons.threads</artifactId>
-            <version>3.1.0</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.commons.osgi</artifactId>
-            <version>2.3.0</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.jcr.resource</artifactId>
             <version>2.2.8</version>
             <scope>provided</scope>

Modified: sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationModelRetrieverImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationModelRetrieverImpl.java?rev=1785131&r1=1785130&r2=1785131&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationModelRetrieverImpl.java (original)
+++ sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationModelRetrieverImpl.java Thu Mar  2 13:27:50 2017
@@ -20,6 +20,7 @@ package org.apache.sling.validation.impl
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -29,56 +30,50 @@ import javax.annotation.Nonnull;
 import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.commons.osgi.RankedServices;
 import org.apache.sling.validation.impl.model.MergedValidationModel;
 import org.apache.sling.validation.impl.util.Trie;
 import org.apache.sling.validation.model.ValidationModel;
 import org.apache.sling.validation.model.spi.ValidationModelProvider;
 import org.apache.sling.validation.spi.Validator;
 import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.FieldOption;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.osgi.service.component.annotations.ReferencePolicy;
 import org.osgi.service.component.annotations.ReferencePolicyOption;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventConstants;
-import org.osgi.service.event.EventHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/**
- * Retrieves the most appropriate model (the one with the longest matching applicablePath) from any of the
- * {@link ValidationModelProvider}s. Also implements a cache of all previously retrieved models.
- *
- */
-@Component(property={EventConstants.EVENT_TOPIC+"="+ValidationModelRetrieverImpl.CACHE_INVALIDATION_EVENT_TOPIC})
-public class ValidationModelRetrieverImpl implements ValidationModelRetriever, EventHandler {
+/** Retrieves the most appropriate model (the one with the longest matching applicablePath) from any of the
+ * {@link ValidationModelProvider}s. Also implements a cache of all previously retrieved models. */
+@Component
+public class ValidationModelRetrieverImpl implements ValidationModelRetriever {
 
     public static final String CACHE_INVALIDATION_EVENT_TOPIC = "org/apache/sling/validation/cache/INVALIDATE";
 
-    /**
-     * Map of known validation models (key=validated resourceType, value={@link Trie} of {@link ValidationModel}s sorted by their
-     * allowed paths)
+    /** 
+     * Map of validation providers (key=service properties), Declarative Services 1.3 takes care that the list is ordered according to {@link ServiceReference#compareTo(Object)}.
+     * Highest ranked service is the last one in the list.
+     * 
+     * @see OSGi R6 Comp, 112.3.8.1
      */
-    protected Map<String, Trie<ValidationModel>> validationModelsCache = new ConcurrentHashMap<String, Trie<ValidationModel>>();
+    @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MULTIPLE, policyOption = ReferencePolicyOption.GREEDY, fieldOption = FieldOption.REPLACE)
+    protected volatile List<ValidationModelProvider> modelProviders;
 
-    /** Map of validation providers (key=service properties) */
-    private RankedServices<ValidationModelProvider> modelProviders = new RankedServices<ValidationModelProvider>();
+    /** List of all known validators (key=classname of validator) */
+    @Nonnull
+    Map<String, Validator<?>> validators = new ConcurrentHashMap<>();
 
-    /**
-     * List of all known validators (key=classname of validator)
-     */
-    @Nonnull Map<String, Validator<?>> validators = new ConcurrentHashMap<>();
-    
-    @Nonnull Map<String, ServiceReference<Validator<?>>> validatorServiceReferences = new ConcurrentHashMap<>();
+    @Nonnull
+    Map<String, ServiceReference<Validator<?>>> validatorServiceReferences = new ConcurrentHashMap<>();
 
     @Reference
     ResourceResolverFactory resourceResolverFactory;
 
     private static final Logger LOG = LoggerFactory.getLogger(ValidationModelRetrieverImpl.class);
 
-
     /*
      * (non-Javadoc)
      * 
@@ -121,10 +116,8 @@ public class ValidationModelRetrieverImp
 
     private @CheckForNull ValidationModel getModel(@Nonnull String resourceType, String resourcePath) {
         ValidationModel model = null;
-        Trie<ValidationModel> modelsForResourceType = validationModelsCache.get(resourceType);
-        if (modelsForResourceType == null) {
-            modelsForResourceType = fillTrieForResourceType(resourceType);
-        }
+        Trie<ValidationModel> modelsForResourceType = fillTrieForResourceType(resourceType);
+
         model = modelsForResourceType.getElementForLongestMatchingKey(resourcePath).getValue();
         if (model == null && !modelsForResourceType.isEmpty()) {
             LOG.warn("Although model for resource type {} is available, it is not allowed for path {}", resourceType,
@@ -133,41 +126,23 @@ public class ValidationModelRetrieverImp
         return model;
     }
 
-    private synchronized @Nonnull Trie<ValidationModel> fillTrieForResourceType(@Nonnull String resourceType) {
-        Trie<ValidationModel> modelsForResourceType = validationModelsCache.get(resourceType);
-        // use double-checked locking (http://en.wikipedia.org/wiki/Double-checked_locking)
-        if (modelsForResourceType == null) {
-            // create a new (empty) trie
-            modelsForResourceType = new Trie<ValidationModel>();
-            validationModelsCache.put(resourceType, modelsForResourceType);
-
-            // fill trie with data from model providers (all models for the given resource type, independent of resource
-            // path)
-            for (ValidationModelProvider modelProvider : modelProviders) {
-                for (ValidationModel model : modelProvider.getModels(resourceType, validators)) {
-                    for (String applicablePath : model.getApplicablePaths()) {
-                        modelsForResourceType.insert(applicablePath, model);
-                    }
+    private @Nonnull Trie<ValidationModel> fillTrieForResourceType(@Nonnull String resourceType) {
+        // create a new (empty) trie
+        Trie<ValidationModel> modelsForResourceType = new Trie<ValidationModel>();
+
+        // fill trie with data from model providers (all models for the given resource type, independent of resource path)
+        // lowest ranked model provider inserts first (i.e. higher ranked should overwrite)
+        for (ValidationModelProvider modelProvider : modelProviders) {
+            for (ValidationModel model : modelProvider.getModels(resourceType, validators)) {
+                for (String applicablePath : model.getApplicablePaths()) {
+                    modelsForResourceType.insert(applicablePath, model);
                 }
             }
         }
         return modelsForResourceType;
     }
 
-    @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MULTIPLE, policyOption=ReferencePolicyOption.GREEDY)
-    protected synchronized void addModelProvider(ValidationModelProvider modelProvider, Map<String, Object> props) {
-        modelProviders.bind(modelProvider, props);
-        LOG.debug("Invalidating models cache because new model provider '{}' available", modelProvider);
-        validationModelsCache.clear();
-    }
-
-    protected void removeModelProvider(ValidationModelProvider modelProvider, Map<String, Object> props) {
-        modelProviders.unbind(modelProvider, props);
-        LOG.debug("Invalidating models cache because model provider '{}' is no longer available", modelProvider);
-        validationModelsCache.clear();
-    }
-
-    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policyOption=ReferencePolicyOption.GREEDY)
+    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policyOption = ReferencePolicyOption.GREEDY)
     protected void addValidator(Validator<?> validator, Map<String, Object> properties, ServiceReference<Validator<?>> serviceReference) {
         String validatorId = getValidatorIdFromServiceProperties(properties, validator, serviceReference);
         if (validators.containsKey(validatorId)) {
@@ -177,12 +152,14 @@ public class ValidationModelRetrieverImp
             }
             if (serviceReference.compareTo(existingServiceReference) == 1) {
                 LOG.info("Overwriting already existing validator {} from bundle {} with validator {} from bundle {},"
-                        + " because it has the same id {} and a higher service ranking", 
-                        validators.get(validatorId), existingServiceReference.getBundle().getBundleId(), validator, serviceReference.getBundle().getBundleId(), validatorId);
+                        + " because it has the same id '{}' and a higher service ranking",
+                        validators.get(validatorId), existingServiceReference.getBundle().getBundleId(), validator,
+                        serviceReference.getBundle().getBundleId(), validatorId);
                 validators.put(validatorId, validator);
                 validatorServiceReferences.put(validatorId, serviceReference);
             } else {
-                LOG.info("A Validator for the same id '{}' is already registered with class '{}' from bundle {} and has a higher service ranking", 
+                LOG.info(
+                        "A Validator for the same id '{}' is already registered with class '{}' from bundle {} and has a higher service ranking",
                         validatorId, validators.get(validatorId), existingServiceReference.getBundle().getBundleId());
             }
         } else {
@@ -191,36 +168,27 @@ public class ValidationModelRetrieverImp
         }
     }
 
-    protected void removeValidator(Validator<?> validator, Map<String, Object> properties, ServiceReference<Validator<?>> serviceReference) {
-        String validatorId = getValidatorIdFromServiceProperties(properties, validator, serviceReference);
-        // check if this validator is really bound (might not be the case if another validator with a higher service ranking and the same id is bound)
-        boolean removed = validators.remove(validatorId, validator);
-        if (removed) {
-            validatorServiceReferences.remove(validatorId);
-            LOG.debug("Invalidating models cache because validator {} with id '{}' is no longer available", validator, validatorId);
-            validationModelsCache.clear();
-        } else {
-            LOG.debug("Removing validator {} with id '{}' has no effect, as another validator with a higher service ranking was bound previously", validator, validatorId);
-        }
+    // no need for an unbind method for validators, as those are static, i.e. component is deactivated first
+    @Activate
+    protected void activate() {
+        LOG.info("Starting service...");
     }
 
-    private String getValidatorIdFromServiceProperties(Map<String, Object> properties, Validator<?> validator, ServiceReference<Validator<?>> serviceReference) {
+    private String getValidatorIdFromServiceProperties(Map<String, Object> properties, Validator<?> validator,
+            ServiceReference<Validator<?>> serviceReference) {
         Object object = properties.get(Validator.PROPERTY_VALIDATOR_ID);
         if (object == null) {
-            throw new IllegalArgumentException("Validator '" + validator.getClass().getName() + "' provided from bundle " + serviceReference.getBundle().getBundleId() +
+            throw new IllegalArgumentException("Validator '" + validator.getClass().getName() + "' provided from bundle "
+                    + serviceReference.getBundle().getBundleId() +
                     " is lacking the mandatory service property " + Validator.PROPERTY_VALIDATOR_ID);
         }
         if (!(object instanceof String)) {
-            throw new IllegalArgumentException("Validator '" + validator.getClass().getName() + "' provided from bundle " + serviceReference.getBundle().getBundleId() +
-                    " is providing the mandatory service property "+ Validator.PROPERTY_VALIDATOR_ID + " with the wrong type "+ object.getClass() +" (must be of type String)"); 
+            throw new IllegalArgumentException("Validator '" + validator.getClass().getName() + "' provided from bundle "
+                    + serviceReference.getBundle().getBundleId() +
+                    " is providing the mandatory service property " + Validator.PROPERTY_VALIDATOR_ID + " with the wrong type "
+                    + object.getClass() + " (must be of type String)");
         }
-        return (String)object;
-    }
-
-    @Override
-    public void handleEvent(Event event) {
-        validationModelsCache.clear();
-        LOG.debug("Models cache invalidated");
+        return (String) object;
     }
 
 }

Modified: sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/MergedValidationModel.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/MergedValidationModel.java?rev=1785131&r1=1785130&r2=1785131&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/MergedValidationModel.java (original)
+++ sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/MergedValidationModel.java Thu Mar  2 13:27:50 2017
@@ -42,6 +42,7 @@ public class MergedValidationModel imple
     private final ValidationModel baseModel;
     private final Map<String, ResourceProperty> resourcePropertiesMap;
     private final Map<String, ChildResource> childResourceMap;
+    private final String source;
     
     public MergedValidationModel(ValidationModel baseModel, ValidationModel... modelsToMerge) {
         this.baseModel = baseModel;
@@ -55,7 +56,7 @@ public class MergedValidationModel imple
         for (ChildResource childResource : baseModel.getChildren()) {
             childResourceMap.put(childResource.getName(), childResource);
         }
-        
+        StringBuilder sourceStringBuilder = new StringBuilder(baseModel.getSource());
         for (ValidationModel modelToMerge : modelsToMerge) {
             for (ResourceProperty resourceProperty : modelToMerge.getResourceProperties()) {
                 // only if name is not already used, the resource property should be considered
@@ -76,7 +77,9 @@ public class MergedValidationModel imple
                     throw new IllegalArgumentException(msg);
                 }
             }
+            sourceStringBuilder.append(" + ").append(modelToMerge.getSource());
         }
+        source = sourceStringBuilder.toString();
     }
     
     /**
@@ -118,4 +121,10 @@ public class MergedValidationModel imple
         return childResourceMap.values();
     }
 
+    @Override
+    @Nonnull
+    public String getSource() {
+        return source;
+    }
+
 }

Modified: sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/ValidationModelBuilder.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/ValidationModelBuilder.java?rev=1785131&r1=1785130&r2=1785131&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/ValidationModelBuilder.java (original)
+++ sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/ValidationModelBuilder.java Thu Mar  2 13:27:50 2017
@@ -20,7 +20,9 @@ package org.apache.sling.validation.impl
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import javax.annotation.Nonnull;
 
@@ -40,8 +42,8 @@ public class ValidationModelBuilder {
     
     public ValidationModelBuilder() {
         resourceProperties = new ArrayList<ResourceProperty>();
-        children = new ArrayList<ChildResource>();
-        applicablePaths = new ArrayList<String>();
+        children = new ArrayList<>();
+        applicablePaths = new ArrayList<>();
     }
     
     public @Nonnull ValidationModelBuilder resourceProperty(@Nonnull ResourceProperty resourceProperty) {
@@ -49,11 +51,21 @@ public class ValidationModelBuilder {
         return this;
     }
     
+    public @Nonnull ValidationModelBuilder resourceProperties(@Nonnull List<ResourceProperty> resourceProperties) {
+        this.resourceProperties.addAll(resourceProperties);
+        return this;
+    }
+    
     public @Nonnull ValidationModelBuilder childResource(@Nonnull ChildResource childResource) {
         children.add(childResource);
         return this;
     }
     
+    public @Nonnull ValidationModelBuilder childResources(@Nonnull List<ChildResource> childResources) {
+        children.addAll(childResources);
+        return this;
+    }
+    
     public @Nonnull ValidationModelBuilder setApplicablePath(@Nonnull String applicablePath) {
         applicablePaths.clear();
         applicablePaths.add(applicablePath);
@@ -65,7 +77,14 @@ public class ValidationModelBuilder {
         return this;
     }
     
-    public @Nonnull ValidationModel build(@Nonnull String validatedResourceType) {
-        return new ValidationModelImpl(resourceProperties, validatedResourceType, applicablePaths.toArray(new String[0]), children);
+    public @Nonnull ValidationModelBuilder addApplicablePaths(@Nonnull String[] applicablePaths) {
+        for (String applicablePath : applicablePaths) {
+            this.applicablePaths.add(applicablePath);
+        }
+        return this;
+    }
+    
+    public @Nonnull ValidationModel build(@Nonnull String validatedResourceType, @Nonnull String source) {
+        return new ValidationModelImpl(resourceProperties, validatedResourceType, applicablePaths.toArray(new String[0]), children, source);
     }
 }

Modified: sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/ValidationModelImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/ValidationModelImpl.java?rev=1785131&r1=1785130&r2=1785131&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/ValidationModelImpl.java (original)
+++ sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/model/ValidationModelImpl.java Thu Mar  2 13:27:50 2017
@@ -34,12 +34,24 @@ public class ValidationModelImpl impleme
     private final @Nonnull String validatedResourceType;
     private final @Nonnull String[] applicablePaths;
     private final @Nonnull List<ChildResource> children;
+    private final @Nonnull String source;
 
-    // TODO: only call from ValidationModelBuilder
-    public ValidationModelImpl(@Nonnull List<ResourceProperty> resourceProperties, @Nonnull String validatedResourceType,
-                              String[] applicablePaths, @Nonnull List<ChildResource> children) {
+    /**
+     * Only used from {@link ValidationModelBuilder}
+     * @param resourceProperties
+     * @param validatedResourceType
+     * @param applicablePaths
+     * @param children
+     * @param source a string identifying the model's source (e.g. a resource path)
+     */
+    ValidationModelImpl(@Nonnull List<ResourceProperty> resourceProperties, @Nonnull String validatedResourceType,
+                              String[] applicablePaths, @Nonnull List<ChildResource> children, @Nonnull String source) {
         this.resourceProperties = resourceProperties;
         this.validatedResourceType = validatedResourceType;
+        
+        if (resourceProperties.isEmpty() && children.isEmpty()) {
+            throw new IllegalStateException("Neither children nor properties set in validation model for " + validatedResourceType + "'");
+        }
         // if this property was not set or is an empty array...
         if (applicablePaths == null || applicablePaths.length == 0) {
             // ...set this to the empty string (which matches all paths)
@@ -47,12 +59,13 @@ public class ValidationModelImpl impleme
         } else {
             for (String applicablePath : applicablePaths) {
                 if (StringUtils.isBlank(applicablePath)) {
-                    throw new IllegalArgumentException("applicablePaths may not contain empty values!");
+                    throw new IllegalStateException("applicablePaths may not contain empty values in validation model for " + validatedResourceType + "'");
                 }
             }
             this.applicablePaths = applicablePaths;
         }
         this.children = children;
+        this.source = source;
     }
 
     @Override
@@ -76,9 +89,14 @@ public class ValidationModelImpl impleme
     }
 
     @Override
+    public @Nonnull String getSource() {
+        return source;
+    }
+
+    @Override
     public String toString() {
-        return "ResourceValidationModel [resourceProperties=" + resourceProperties + ", validatedResourceType="
-                + validatedResourceType + ", applicablePaths=" + Arrays.toString(applicablePaths) + ", children=" + children + "]";
+        return "ValidationModelImpl [resourceProperties=" + resourceProperties + ", validatedResourceType=" + validatedResourceType
+                + ", applicablePaths=" + Arrays.toString(applicablePaths) + ", children=" + children + ", source="+ source +"]";
     }
 
     @Override
@@ -88,6 +106,7 @@ public class ValidationModelImpl impleme
         result = prime * result + Arrays.hashCode(applicablePaths);
         result = prime * result + ((children == null) ? 0 : children.hashCode());
         result = prime * result + ((resourceProperties == null) ? 0 : resourceProperties.hashCode());
+        result = prime * result + ((source == null) ? 0 : source.hashCode());
         result = prime * result + ((validatedResourceType == null) ? 0 : validatedResourceType.hashCode());
         return result;
     }
@@ -107,8 +126,12 @@ public class ValidationModelImpl impleme
             return false;
         if (!resourceProperties.equals(other.resourceProperties))
             return false;
+        if (!source.equals(other.source))
+            return false;
         if (!validatedResourceType.equals(other.validatedResourceType))
             return false;
         return true;
     }
+
+    
 }

Modified: sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/resourcemodel/ResourceValidationModelProviderImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/resourcemodel/ResourceValidationModelProviderImpl.java?rev=1785131&r1=1785130&r2=1785131&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/resourcemodel/ResourceValidationModelProviderImpl.java (original)
+++ sling/trunk/bundles/extensions/validation/core/src/main/java/org/apache/sling/validation/impl/resourcemodel/ResourceValidationModelProviderImpl.java Thu Mar  2 13:27:50 2017
@@ -19,13 +19,16 @@
 package org.apache.sling.validation.impl.resourcemodel;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Dictionary;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import javax.annotation.Nonnull;
 
@@ -35,22 +38,16 @@ import org.apache.sling.api.resource.Res
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.commons.osgi.PropertiesUtil;
-import org.apache.sling.commons.threads.ModifiableThreadPoolConfig;
-import org.apache.sling.commons.threads.ThreadPool;
-import org.apache.sling.commons.threads.ThreadPoolManager;
-import org.apache.sling.commons.threads.ThreadPoolConfig.ThreadPoolPolicy;
 import org.apache.sling.serviceusermapping.ServiceUserMapped;
 import org.apache.sling.validation.impl.model.ChildResourceImpl;
 import org.apache.sling.validation.impl.model.ParameterizedValidatorImpl;
 import org.apache.sling.validation.impl.model.ResourcePropertyImpl;
-import org.apache.sling.validation.impl.model.ValidationModelImpl;
+import org.apache.sling.validation.impl.model.ValidationModelBuilder;
 import org.apache.sling.validation.impl.util.Trie;
 import org.apache.sling.validation.model.ChildResource;
 import org.apache.sling.validation.model.ParameterizedValidator;
 import org.apache.sling.validation.model.ResourceProperty;
 import org.apache.sling.validation.model.ValidationModel;
-import org.apache.sling.validation.model.spi.ValidationModelCache;
 import org.apache.sling.validation.model.spi.ValidationModelProvider;
 import org.apache.sling.validation.spi.Validator;
 import org.osgi.framework.ServiceRegistration;
@@ -69,10 +66,12 @@ import org.slf4j.LoggerFactory;
 @Component(service = ValidationModelProvider.class)
 public class ResourceValidationModelProviderImpl implements ValidationModelProvider, EventHandler {
 
-    static final String MODEL_XPATH_QUERY = "/jcr:root%s/*[@sling:resourceType=\""+ResourceValidationModelProviderImpl.VALIDATION_MODEL_RESOURCE_TYPE+"\" and @"+ResourceValidationModelProviderImpl.VALIDATED_RESOURCE_TYPE+"=\"%s\"]";
+    static final String MODEL_XPATH_QUERY = "/jcr:root%s/*[@sling:resourceType=\""
+            + ResourceValidationModelProviderImpl.VALIDATION_MODEL_RESOURCE_TYPE + "\" and @"
+            + ResourceValidationModelProviderImpl.VALIDATED_RESOURCE_TYPE + "=\"%s\"]";
     static final String[] TOPICS = { SlingConstants.TOPIC_RESOURCE_REMOVED, SlingConstants.TOPIC_RESOURCE_CHANGED,
             SlingConstants.TOPIC_RESOURCE_ADDED };
-    
+
     public static final String NAME_REGEX = "nameRegex";
     public static final String CHILDREN = "children";
     public static final String VALIDATOR_ARGUMENTS = "validatorArguments";
@@ -88,29 +87,18 @@ public class ResourceValidationModelProv
     @Reference
     ResourceResolverFactory rrf = null;
 
-    @Reference
-    private ValidationModelCache cache;
-
     private static final Logger LOG = LoggerFactory.getLogger(ResourceValidationModelProviderImpl.class);
 
-    @Reference
-    private ThreadPoolManager tpm = null;
-
-    private ThreadPool threadPool;
-
     private ServiceRegistration<EventHandler> eventHandlerRegistration;
 
     @Reference
     private ServiceUserMapped serviceUserMapped;
 
+    /** key = resource type of validation models value = a list of all validation models for the resource type given in the key */
+    final Map<String, List<ValidationModel>> validationModelCacheByResourceType = new ConcurrentHashMap<>();
+
     @Activate
     protected void activate(ComponentContext componentContext) throws LoginException {
-        ModifiableThreadPoolConfig threadPoolConfig = new ModifiableThreadPoolConfig();
-        threadPoolConfig.setMinPoolSize(1);
-        threadPoolConfig.setMaxPoolSize(1);
-        threadPoolConfig.setQueueSize(2); // make sure at most 2 invalidation requests queue up
-        threadPoolConfig.setBlockPolicy(ThreadPoolPolicy.DISCARD);
-        threadPool = tpm.create(threadPoolConfig, "Validation Service Thread Pool");
         ResourceResolver rr = null;
         try {
             rr = rrf.getServiceResourceResolver(null);
@@ -138,9 +126,6 @@ public class ResourceValidationModelProv
 
     @Deactivate
     protected void deactivate(ComponentContext componentContext) {
-        if (threadPool != null) {
-            tpm.release(threadPool);
-        }
         if (eventHandlerRegistration != null) {
             eventHandlerRegistration.unregister();
             eventHandlerRegistration = null;
@@ -149,39 +134,103 @@ public class ResourceValidationModelProv
 
     @Override
     public void handleEvent(Event event) {
-        LOG.debug("Asynchronously invalidating models cache due to event {}", event);
-        Runnable task = new Runnable() {
-            @Override
-            public void run() {
+        String path = (String) event.getProperty(SlingConstants.PROPERTY_PATH);
+        if (path == null) {
+            LOG.warn("Received event {}, but could not get the affected path", event);
+            return;
+        }
+        Set<String> resourceTypesToInvalidate = new HashSet<>();
+        switch (event.getTopic()) {
+        case SlingConstants.TOPIC_RESOURCE_REMOVED:
+            // find cache entries below the removed resource
+            for (Entry<String, List<ValidationModel>> validationModelByResourceType : validationModelCacheByResourceType.entrySet()) {
+                for (ValidationModel model : validationModelByResourceType.getValue()) {
+                    if (model.getSource().startsWith(path)) {
+                        LOG.debug("Invalidate validation model at {}, because resource at {} has been removed", model.getSource(), path);
+                        resourceTypesToInvalidate.add(validationModelByResourceType.getKey());
+                    }
+                }
+            }
+            break;
+        default:
+            // only consider additions/changes of resources with resource type = validation model resource type
+            String resourceType = (String) event.getProperty(SlingConstants.PROPERTY_RESOURCE_TYPE);
+            if (resourceType == null) {
+                LOG.warn("Received event {}, but could not get the modified/added resource type", event);
+                return;
+            }
+            if (VALIDATION_MODEL_RESOURCE_TYPE.equals(resourceType)) {
+                // retrieve the resource types covered by the newly added model
+                String resourceTypeToInvalidate = null;
                 try {
-                    // defer invalidating the cache, to prevent to many invalidation events to be sent in a row when a lot of modifications happen below the resource resolver's search paths
-                    Thread.sleep(500);
-                } catch (InterruptedException e) {
-                    LOG.warn("Could not wait 500 seconds till invalidating the cache", e);
+                    resourceTypeToInvalidate = getResourceTypeOfValidationModel(path);
+                } catch (Exception e) {
+                    LOG.warn("Could not get covered resource type of newly added validation model at " + path, e);
+                }
+                if (resourceTypeToInvalidate != null) {
+                    LOG.debug("Invalidate validation models for resource type {}, because resource at {} provides a new/modified validation model for that type", resourceType, path);
+                    resourceTypesToInvalidate.add(resourceTypeToInvalidate);
+                } else {
+                    LOG.debug("Resource at {} provides a new/modified validation model but could not yet determine for which resource type", path);
                 }
-                cache.invalidate();
             }
-        };
-        threadPool.execute(task);
+            // or paths already covered by the cache
+            for (Entry<String, List<ValidationModel>> validationModelByResourceType : validationModelCacheByResourceType.entrySet()) {
+                for (ValidationModel model : validationModelByResourceType.getValue()) {
+                    if (path.startsWith(model.getSource())) {
+                        LOG.debug("Invalidate validation model at {}, because resource below (at {}) has been modified", model.getSource(), path);
+                        resourceTypesToInvalidate.add(validationModelByResourceType.getKey());
+                    }
+                }
+            }
+        }
+        for (String resourceTypeToInvalidate : resourceTypesToInvalidate) {
+            validationModelCacheByResourceType.remove(resourceTypeToInvalidate);
+        }
+    }
+
+    private String getResourceTypeOfValidationModel(String path) throws LoginException {
+        ResourceResolver resourceResolver = null;
+        try {
+            resourceResolver = rrf.getServiceResourceResolver(null);
+            Resource modelResource = resourceResolver.getResource(path);
+            ValueMap properties = modelResource.adaptTo(ValueMap.class);
+            if (properties == null) {
+                throw new IllegalStateException("Could not adapt resource at " + path + " to a ValueMap");
+            }
+            return properties.get(VALIDATED_RESOURCE_TYPE, null);
+        } finally {
+            if (resourceResolver != null) {
+                resourceResolver.close();
+            }
+        }
     }
 
-    /**
-     * Searches for validation models bound to a specific resource type in the repository. All validation models
-     * will be returned in a {@link Trie} data structure for easy retrieval of the models using their
-     * {@code applicable paths} as trie keys.
-     *
-     * @param relativeResourceType
-     *            {@inheritDoc}
-     * @param validatorsMap
-     *            {@inheritDoc}
-     * @return {@inheritDoc}
-     * @throws {@inheritDoc}
+    /*
+     * (non-Javadoc)
+     * @see org.apache.sling.validation.model.spi.ValidationModelProvider#getModels(java.lang.String, java.util.Map)
      */
     @Override
+    public @Nonnull List<ValidationModel> getModels(@Nonnull String relativeResourceType, @Nonnull Map<String, Validator<?>> validatorsMap) {
+        List<ValidationModel> cacheEntry = validationModelCacheByResourceType.get(relativeResourceType);
+        if (cacheEntry == null) {
+            cacheEntry = doGetModels(relativeResourceType, validatorsMap);
+            validationModelCacheByResourceType.put(relativeResourceType, cacheEntry);
+        }
+        return cacheEntry;
+    }
+    
+    /** 
+     * Searches for validation models bound to a specific resource type through a search query. 
+     *
+     * @param relativeResourceType the resource type to look for
+     * @param validatorsMap  all known validators in a map (key=id of validator). Only one of those should be used in the returned validation models.
+     * @return a List of {@link ValidationModel}s. Never {@code null}, but might be empty collection in case no
+     *         model for the given resource type could be found. Returns the models below "/apps" before the models below "/libs".
+     * @throws IllegalStateException in case a validation model is found but it is invalid */
     @Nonnull
-    public Collection<ValidationModel> getModels(@Nonnull String relativeResourceType, @Nonnull Map<String, Validator<?>> validatorsMap) {
-        ValidationModelImpl vm;
-        Collection<ValidationModel> validationModels = new ArrayList<ValidationModel>();
+    private List<ValidationModel> doGetModels(@Nonnull String relativeResourceType, @Nonnull Map<String, Validator<?>> validatorsMap) {
+        List<ValidationModel> validationModels = new ArrayList<ValidationModel>();
         ResourceResolver resourceResolver = null;
         try {
             resourceResolver = rrf.getServiceResourceResolver(null);
@@ -192,22 +241,18 @@ public class ResourceValidationModelProv
                 while (models.hasNext()) {
                     Resource model = models.next();
                     LOG.debug("Found validation model resource {}.", model.getPath());
-                    String jcrPath = model.getPath();
+                    String resourcePath = model.getPath();
                     try {
+                        ValidationModelBuilder modelBuilder = new ValidationModelBuilder();
                         ValueMap validationModelProperties = model.adaptTo(ValueMap.class);
-                        String[] applicablePaths = PropertiesUtil.toStringArray(validationModelProperties.get(ResourceValidationModelProviderImpl.APPLICABLE_PATHS, String[].class));
-                        Resource r = model.getChild(ResourceValidationModelProviderImpl.PROPERTIES);
-                        List<ResourceProperty> resourceProperties = buildProperties(validatorsMap,r);
-                        List<ChildResource> children = buildChildren(model, model, validatorsMap);
-                        if (resourceProperties.isEmpty() && children.isEmpty()) {
-                            throw new IllegalArgumentException("Neither children nor properties set.");
-                        } else {
-                            vm = new ValidationModelImpl(resourceProperties, relativeResourceType,
-                                    applicablePaths, children);
-                            validationModels.add(vm);
-                        }
+                        modelBuilder.addApplicablePaths(validationModelProperties.get(ResourceValidationModelProviderImpl.APPLICABLE_PATHS, String[].class));
+                        Resource propertiesResource = model.getChild(ResourceValidationModelProviderImpl.PROPERTIES);
+                        modelBuilder.resourceProperties(buildProperties(validatorsMap, propertiesResource));
+                        modelBuilder.childResources(buildChildren(model, model, validatorsMap));
+                        ValidationModel vm = modelBuilder.build(relativeResourceType, resourcePath);
+                        validationModels.add(vm);
                     } catch (IllegalArgumentException e) {
-                        throw new IllegalStateException("Found invalid validation model in '" + jcrPath + "': "
+                        throw new IllegalStateException("Found invalid validation model in '" + resourcePath + "': "
                                 + e.getMessage(), e);
                     }
                 }
@@ -227,24 +272,22 @@ public class ResourceValidationModelProv
             }
         }
     }
-    
-    /**
-     * Creates a set of the properties that a resource is expected to have, together with the associated validators.
+
+    /** Creates a set of the properties that a resource is expected to have, together with the associated validators.
      *
-     * @param validatorsMap      a map containing {@link Validator}s as values and their id's as keys
+     * @param validatorsMap a map containing {@link Validator}s as values and their id's as keys
      * @param propertiesResource the resource identifying the properties node from a validation model's structure (might be {@code null})
      * @return a set of properties or an empty set if no properties are defined
-     * @see ResourceProperty
-     */
+     * @see ResourceProperty */
     private @Nonnull List<ResourceProperty> buildProperties(@Nonnull Map<String, Validator<?>> validatorsMap, Resource propertiesResource) {
         List<ResourceProperty> properties = new ArrayList<ResourceProperty>();
         if (propertiesResource != null) {
             for (Resource property : propertiesResource.getChildren()) {
                 String fieldName = property.getName();
                 ValueMap propertyValueMap = property.adaptTo(ValueMap.class);
-                Boolean propertyMultiple = PropertiesUtil.toBoolean(propertyValueMap.get(ResourceValidationModelProviderImpl.PROPERTY_MULTIPLE), false);
-                Boolean propertyRequired = !PropertiesUtil.toBoolean(propertyValueMap.get(ResourceValidationModelProviderImpl.OPTIONAL), false);
-                String nameRegex = PropertiesUtil.toString(propertyValueMap.get(ResourceValidationModelProviderImpl.NAME_REGEX), null);
+                Boolean propertyMultiple = propertyValueMap.get(ResourceValidationModelProviderImpl.PROPERTY_MULTIPLE, false);
+                Boolean propertyRequired = !propertyValueMap.get(ResourceValidationModelProviderImpl.OPTIONAL, false);
+                String nameRegex = propertyValueMap.get(ResourceValidationModelProviderImpl.NAME_REGEX, null);
                 Resource validators = property.getChild(ResourceValidationModelProviderImpl.VALIDATORS);
                 List<ParameterizedValidator> parameterizedValidators = new ArrayList<ParameterizedValidator>();
                 if (validators != null) {
@@ -261,7 +304,8 @@ public class ResourceValidationModelProv
                             throw new IllegalArgumentException("Could not find validator with id '" + validatorId + "'");
                         }
                         // get arguments for validator
-                        String[] validatorArguments = validatorProperties.get(ResourceValidationModelProviderImpl.VALIDATOR_ARGUMENTS, String[].class);
+                        String[] validatorArguments = validatorProperties.get(ResourceValidationModelProviderImpl.VALIDATOR_ARGUMENTS,
+                                String[].class);
                         Map<String, Object> validatorArgumentsMap = new HashMap<String, Object>();
                         if (validatorArguments != null) {
                             for (String arg : validatorArguments) {
@@ -277,25 +321,24 @@ public class ResourceValidationModelProv
                         parameterizedValidators.add(new ParameterizedValidatorImpl(validator, validatorArgumentsMap, severity));
                     }
                 }
-                ResourceProperty f = new ResourcePropertyImpl(fieldName, nameRegex, propertyMultiple, propertyRequired, parameterizedValidators);
+                ResourceProperty f = new ResourcePropertyImpl(fieldName, nameRegex, propertyMultiple, propertyRequired,
+                        parameterizedValidators);
                 properties.add(f);
             }
         }
         return properties;
     }
 
-    /**
-     * Searches children resources from a {@code modelResource}, starting from the {@code rootResource}. If one needs all the children
+    /** Searches children resources from a {@code modelResource}, starting from the {@code rootResource}. If one needs all the children
      * resources of a model, then the {@code modelResource} and the {@code rootResource} should be identical.
      *
-     * @param modelResource          the resource describing a {@link org.apache.sling.validation.api.ValidationModel}
-     * @param rootResource           the model's resource from which to search for children (this resource has to have a {@link
-     *                               ResourceValidationModelProviderImpl#CHILDREN} node directly underneath it)
-     * @param validatorsMap          a map containing {@link Validator}s as values and their class names as values
-     * @return a list of all the children resources; the list will be empty if there are no children resources
-     */
+     * @param modelResource the resource describing a {@link org.apache.sling.validation.api.ValidationModel}
+     * @param rootResource the model's resource from which to search for children (this resource has to have a
+     *            {@link ResourceValidationModelProviderImpl#CHILDREN} node directly underneath it)
+     * @param validatorsMap a map containing {@link Validator}s as values and their class names as values
+     * @return a list of all the children resources; the list will be empty if there are no children resources */
     private @Nonnull List<ChildResource> buildChildren(@Nonnull Resource modelResource, @Nonnull Resource rootResource,
-                                                    @Nonnull Map<String, Validator<?>> validatorsMap) {
+            @Nonnull Map<String, Validator<?>> validatorsMap) {
         List<ChildResource> children = new ArrayList<ChildResource>();
         Resource childrenResource = rootResource.getChild(ResourceValidationModelProviderImpl.CHILDREN);
         if (childrenResource != null) {
@@ -313,8 +356,10 @@ public class ResourceValidationModelProv
                     // otherwise fall back to the name
                     nameRegex = null;
                 }
-                boolean isRequired = !PropertiesUtil.toBoolean(childrenProperties.get(ResourceValidationModelProviderImpl.OPTIONAL), false);
-                ChildResource childResource = new ChildResourceImpl(name, nameRegex, isRequired, buildProperties(validatorsMap, child.getChild(ResourceValidationModelProviderImpl.PROPERTIES)), buildChildren(modelResource, child, validatorsMap));
+                boolean isRequired = !childrenProperties.get(ResourceValidationModelProviderImpl.OPTIONAL, false);
+                ChildResource childResource = new ChildResourceImpl(name, nameRegex, isRequired,
+                        buildProperties(validatorsMap, child.getChild(ResourceValidationModelProviderImpl.PROPERTIES)),
+                        buildChildren(modelResource, child, validatorsMap));
                 children.add(childResource);
             }
         }

Modified: sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationModelRetrieverImplTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationModelRetrieverImplTest.java?rev=1785131&r1=1785130&r2=1785131&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationModelRetrieverImplTest.java (original)
+++ sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationModelRetrieverImplTest.java Thu Mar  2 13:27:50 2017
@@ -21,8 +21,8 @@ package org.apache.sling.validation.impl
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Dictionary;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.annotation.Nonnull;
@@ -30,21 +30,18 @@ import javax.annotation.Nonnull;
 import org.apache.commons.collections4.MultiValuedMap;
 import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
 import org.apache.sling.api.resource.LoginException;
-import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.validation.impl.model.ResourcePropertyBuilder;
 import org.apache.sling.validation.impl.model.ValidationModelBuilder;
+import org.apache.sling.validation.impl.util.ResourcePropertyNameMatcher;
 import org.apache.sling.validation.impl.util.examplevalidators.DateValidator;
 import org.apache.sling.validation.impl.util.examplevalidators.StringValidator;
 import org.apache.sling.validation.model.ResourceProperty;
 import org.apache.sling.validation.model.ValidationModel;
 import org.apache.sling.validation.model.spi.ValidationModelProvider;
 import org.apache.sling.validation.spi.Validator;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
 import org.hamcrest.Matchers;
-import org.hamcrest.TypeSafeMatcher;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -55,9 +52,6 @@ import org.mockito.runners.MockitoJUnitR
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
-import org.osgi.service.event.Event;
-
-import edu.umd.cs.findbugs.annotations.When;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ValidationModelRetrieverImplTest {
@@ -86,18 +80,20 @@ public class ValidationModelRetrieverImp
      *
      */
     class TestModelProvider implements ValidationModelProvider {
-        int counter = 0;
-
+        private @Nonnull final String source;
+        public TestModelProvider(@Nonnull String source) {
+            this.source = source;
+        }
+        
         @Override
-        public @Nonnull Collection<ValidationModel> getModels(@Nonnull String relativeResourceType,
+        public @Nonnull List<ValidationModel> getModels(@Nonnull String relativeResourceType,
                 @Nonnull Map<String, Validator<?>> validatorsMap) {
             // make sure the date validator is passed along
             Assert.assertThat(validatorsMap,
                     Matchers.<String, Validator<?>> hasEntry(DATE_VALIDATOR_ID, dateValidator));
 
-            Collection<ValidationModel> models = new ArrayList<ValidationModel>();
-            Collection<String> applicablePaths = (Collection<String>) applicablePathPerResourceType
-                    .get(relativeResourceType);
+            List<ValidationModel> models = new ArrayList<ValidationModel>();
+            Collection<String> applicablePaths = applicablePathPerResourceType.get(relativeResourceType);
             if (applicablePaths != null) {
                 for (String applicablePath : applicablePaths) {
                     ValidationModelBuilder modelBuilder = new ValidationModelBuilder();
@@ -105,46 +101,21 @@ public class ValidationModelRetrieverImp
                         modelBuilder.addApplicablePath(applicablePath);
                     }
                     modelBuilder.resourceProperty(new ResourcePropertyBuilder().build(relativeResourceType));
-                    models.add(modelBuilder.build(relativeResourceType));
+                    models.add(modelBuilder.build(relativeResourceType, source));
                 }
             }
-            counter++;
             return models;
         }
     }
 
-    /**
-     * Custom Hamcrest matcher which matches Resource Properties based on the equality only on their name.
-     */
-    private static final class ResourcePropertyNameMatcher extends TypeSafeMatcher<ResourceProperty> {
-
-        private final String expectedName;
-
-        public ResourcePropertyNameMatcher(String name) {
-            expectedName = name;
-        }
-
-        @Override
-        public void describeTo(Description description) {
-            description.appendText("ResourceProperty with name=" + expectedName);
-        }
-
-        @Override
-        protected boolean matchesSafely(ResourceProperty resourceProperty) {
-           return expectedName.equals(resourceProperty.getName());
-        }
-    }
-
     @Before
     public void setup() throws LoginException {
         dateValidator = new DateValidator();
         applicablePathPerResourceType = new ArrayListValuedHashMap<>();
         validationModelRetriever = new ValidationModelRetrieverImpl();
-        modelProvider = new TestModelProvider();
-        // service id must be set (even if service ranking is not set)
-        Map<String, Object> properties = new HashMap<String, Object>();
-        properties.put(Constants.SERVICE_ID, 1L);
-        validationModelRetriever.addModelProvider(modelProvider, properties);
+        modelProvider = new TestModelProvider("source1");
+        validationModelRetriever.modelProviders = new ArrayList<>();
+        validationModelRetriever.modelProviders.add(modelProvider);
         Mockito.doReturn(1l).when(providingBundle).getBundleId();
         Mockito.doReturn(providingBundle).when(validatorServiceReference).getBundle();
         Mockito.doReturn(providingBundle).when(newValidatorServiceReference).getBundle();
@@ -214,36 +185,6 @@ public class ValidationModelRetrieverImp
     }
 
     @Test
-    public void testGetCachedModel() {
-        applicablePathPerResourceType.put("test/type", "/content/site1");
-        // call two times, the second time the counter must be the same (because provider is not called)
-        ValidationModel model = validationModelRetriever.getModel("test/type", "/content/site1", false);
-        Assert.assertNotNull(model);
-        Assert.assertEquals(1, modelProvider.counter);
-        model = validationModelRetriever.getModel("test/type", "/content/site1", false);
-        Assert.assertNotNull(model);
-        Assert.assertEquals(1, modelProvider.counter);
-
-        model = validationModelRetriever.getModel("invalid/type", "/content/site1", false);
-        Assert.assertNull(model);
-        Assert.assertEquals(2, modelProvider.counter);
-        model = validationModelRetriever.getModel("invalid/type", "/content/site1", false);
-        Assert.assertNull(model);
-        Assert.assertEquals(2, modelProvider.counter);
-    }
-
-    @Test
-    public void testGetCachedInvalidation() {
-        applicablePathPerResourceType.put("test/type", "/content/site1");
-        validationModelRetriever.getModel("test/type", "/content/site1", false);
-        Assert.assertEquals(1, modelProvider.counter);
-        validationModelRetriever.handleEvent(new Event(ValidationModelRetrieverImpl.CACHE_INVALIDATION_EVENT_TOPIC, (Dictionary<String, ?>) null));
-        // after cache invalidation the provider is called again
-        validationModelRetriever.getModel("test/type", "/content/site1", false);
-        Assert.assertEquals(2, modelProvider.counter);
-    }
-
-    @Test
     public void testGetModelWithResourceInheritance() {
         // in case no super type is known, just return model
         applicablePathPerResourceType.put("test/type", "/content/site1");
@@ -270,8 +211,6 @@ public class ValidationModelRetrieverImp
         model = validationModelRetriever.getModel("test/type", "/content/site1", true);
         Assert.assertNull("Found model although no model has been specified (neither in base nor in super type)", model);
         
-        validationModelRetriever.validationModelsCache.clear();
-        
         // only supertype has model being set
         applicablePathPerResourceType.put("test/supertype", "/content/site1");
         model = validationModelRetriever.getModel("test/type", "/content/site1", true);
@@ -290,4 +229,34 @@ public class ValidationModelRetrieverImp
         Assert.assertNotNull(model);
         Assert.assertThat(model.getResourceProperties(), Matchers.contains(new ResourcePropertyNameMatcher("test/type")));
     }
+    
+    @Test
+    public void testGetModelWithMultipleProvidersHigherRanking() {
+        ValidationModelProvider modelProvider2 = new TestModelProvider("source2");
+        validationModelRetriever.modelProviders.clear();
+        validationModelRetriever.modelProviders.add(modelProvider);
+        validationModelRetriever.modelProviders.add(modelProvider2);
+        // each provider must return the same applicable path but different
+        applicablePathPerResourceType.put("test/type", "/content/site1");
+
+        ValidationModel model = validationModelRetriever.getModel("test/type", "/content/site1/somepage", false);
+        Assert.assertNotNull(model);
+        Assert.assertEquals("source2", model.getSource());
+        
+    }
+    
+    @Test
+    public void testGetModelWithMultipleProvidersLowerRanking() {
+        ValidationModelProvider modelProvider2 = new TestModelProvider("source2");
+        validationModelRetriever.modelProviders.clear();
+        validationModelRetriever.modelProviders.add(modelProvider2);
+        validationModelRetriever.modelProviders.add(modelProvider);
+        // each provider must return the same applicable path but different
+        applicablePathPerResourceType.put("test/type", "/content/site1");
+
+        ValidationModel model = validationModelRetriever.getModel("test/type", "/content/site1/somepage", false);
+        Assert.assertNotNull(model);
+        Assert.assertEquals("source1", model.getSource());
+        
+    }
 }

Modified: sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java?rev=1785131&r1=1785130&r2=1785131&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java (original)
+++ sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java Thu Mar  2 13:27:50 2017
@@ -102,7 +102,7 @@ public class ValidationServiceImplTest {
     public void testValueMapWithWrongDataType() throws Exception {
         propertyBuilder.validator(new DateValidator());
         modelBuilder.resourceProperty(propertyBuilder.build("field1"));
-        ValidationModel vm = modelBuilder.build("sling/validation/test");
+        ValidationModel vm = modelBuilder.build("sling/validation/test", "some source");
 
         HashMap<String, Object> hashMap = new HashMap<String, Object>();
         hashMap.put("field1", "1");
@@ -126,7 +126,7 @@ public class ValidationServiceImplTest {
         };
         propertyBuilder.validator(myValidator);
         modelBuilder.resourceProperty(propertyBuilder.build("field1"));
-        ValidationModel vm = modelBuilder.build("sling/validation/test");
+        ValidationModel vm = modelBuilder.build("sling/validation/test", "some source");
 
         HashMap<String, Object> hashMap = new HashMap<String, Object>();
         hashMap.put("field1", "1");
@@ -141,7 +141,7 @@ public class ValidationServiceImplTest {
         modelBuilder.resourceProperty(propertyBuilder.build("field2"));
         modelBuilder.resourceProperty(propertyBuilder.build("field3"));
         modelBuilder.resourceProperty(propertyBuilder.build("field4"));
-        ValidationModel vm = modelBuilder.build("sling/validation/test");
+        ValidationModel vm = modelBuilder.build("sling/validation/test", "some source");
 
         // this should not be detected as missing property
         HashMap<String, Object> hashMap = new HashMap<String, Object>();
@@ -156,7 +156,7 @@ public class ValidationServiceImplTest {
     @Test()
     public void testValueMapWithMissingOptionalValue() throws Exception {
         modelBuilder.resourceProperty(propertyBuilder.optional().build("field1"));
-        ValidationModel vm = modelBuilder.build("sling/validation/test");
+        ValidationModel vm = modelBuilder.build("sling/validation/test", "some source");
 
         HashMap<String, Object> hashMap = new HashMap<String, Object>();
         hashMap.put("field2", "1");
@@ -170,7 +170,7 @@ public class ValidationServiceImplTest {
         propertyBuilder.optional();
         propertyBuilder.validator(new RegexValidator(), 2, RegexValidator.REGEX_PARAM, "abc");
         modelBuilder.resourceProperty(propertyBuilder.build("field1"));
-        ValidationModel vm = modelBuilder.build("sling/validation/test");
+        ValidationModel vm = modelBuilder.build("sling/validation/test", "some source");
 
         HashMap<String, Object> hashMap = new HashMap<String, Object>();
         hashMap.put("field1", "");
@@ -188,7 +188,7 @@ public class ValidationServiceImplTest {
         final String TEST_REGEX = "^test$";
         propertyBuilder.validator(new RegexValidator(), 0, RegexValidator.REGEX_PARAM, TEST_REGEX);
         modelBuilder.resourceProperty(propertyBuilder.build("field2"));
-        ValidationModel vm = modelBuilder.build("sling/validation/test");
+        ValidationModel vm = modelBuilder.build("sling/validation/test", "some source");
 
         HashMap<String, Object> hashMap = new HashMap<String, Object>();
         hashMap.put("field1", "HelloWorld");
@@ -213,7 +213,7 @@ public class ValidationServiceImplTest {
         modelChild = new ChildResourceImpl("optionalChild", null, false, Collections.singletonList(property), Collections.emptyList());
         modelBuilder.childResource(modelChild);
         
-        ValidationModel vm = modelBuilder.build("sometype");
+        ValidationModel vm = modelBuilder.build("sometype", "some source");
         ResourceResolver rr = context.resourceResolver();
         Resource nonExistingResource = new NonExistingResource(rr, "non-existing-resource");
         ValidationResult vr = validationService.validate(nonExistingResource, vm);
@@ -237,7 +237,7 @@ public class ValidationServiceImplTest {
         modelChild = new ChildResourceImpl("optionalChild", null, false, Collections.singletonList(property), Collections.emptyList());
         modelBuilder.childResource(modelChild);
         
-        ValidationModel vm = modelBuilder.build("sometype");
+        ValidationModel vm = modelBuilder.build("sometype", "some source");
         ResourceResolver rr = context.resourceResolver();
         Resource nonExistingResource = new SyntheticResource(rr, "someresource", "resourceType");
         ValidationResult vr = validationService.validate(nonExistingResource, vm);
@@ -260,7 +260,7 @@ public class ValidationServiceImplTest {
                 Collections.singletonList(modelGrandChild));
         modelBuilder.childResource(modelChild);
 
-        ValidationModel vm = modelBuilder.build("sometype");
+        ValidationModel vm = modelBuilder.build("sometype", "some source");
 
         // create a resource
         ResourceResolver rr = context.resourceResolver();
@@ -288,7 +288,7 @@ public class ValidationServiceImplTest {
         ChildResource child = new ChildResourceImpl("child", null, false, Collections.singletonList(property),
                 Collections.<ChildResource> emptyList());
         modelBuilder.childResource(child);
-        ValidationModel vm = modelBuilder.build("type");
+        ValidationModel vm = modelBuilder.build("type", "some source");
 
         // create a resource (lacking the optional "child" sub resource)
         ResourceResolver rr = context.resourceResolver();
@@ -310,7 +310,7 @@ public class ValidationServiceImplTest {
         ChildResource modelChild = new ChildResourceImpl("child", null, true, Collections.singletonList(property),
                 Collections.singletonList(modelGrandChild));
         modelBuilder.childResource(modelChild);
-        ValidationModel vm = modelBuilder.build("sometype");
+        ValidationModel vm = modelBuilder.build("sometype", "some source");
 
         // create a resource
         ResourceResolver rr = context.resourceResolver();
@@ -345,7 +345,7 @@ public class ValidationServiceImplTest {
         };
         propertyBuilder.validator(extendedValidator); // accept any digits
         modelBuilder.resourceProperty(propertyBuilder.build("field1"));
-        ValidationModel vm = modelBuilder.build("sometype");
+        ValidationModel vm = modelBuilder.build("sometype", "some source");
 
         // create a resource
         ResourceResolver rr = context.resourceResolver();
@@ -371,7 +371,7 @@ public class ValidationServiceImplTest {
 
         modelBuilder.childResource(modelChild);
         modelBuilder.childResource(siblingChild);
-        ValidationModel vm = modelBuilder.build("sometype");
+        ValidationModel vm = modelBuilder.build("sometype", "some source");
 
         ResourceResolver rr = context.resourceResolver();
         Resource testResource = ResourceUtil.getOrCreateResource(rr, "/apps/validation/1/resource",
@@ -405,7 +405,7 @@ public class ValidationServiceImplTest {
         modelBuilder.resourceProperty(propertyBuilder.build("otherfield"));
         propertyBuilder.nameRegex("optionalfield.*").optional();
         modelBuilder.resourceProperty(propertyBuilder.build("optionalfield"));
-        ValidationModel vm = modelBuilder.build("type");
+        ValidationModel vm = modelBuilder.build("type", "some source");
 
         // create a resource
         ResourceResolver rr = context.resourceResolver();
@@ -430,7 +430,7 @@ public class ValidationServiceImplTest {
         propertyBuilder.validator(new RegexValidator(), 0, RegexValidator.REGEX_PARAM, "\\d"); // accept any digits
         propertyBuilder.multiple();
         modelBuilder.resourceProperty(propertyBuilder.build("field"));
-        ValidationModel vm = modelBuilder.build("type");
+        ValidationModel vm = modelBuilder.build("type", "some source");
 
         ResourceResolver rr = context.resourceResolver();
         Resource testResource = ResourceUtil.getOrCreateResource(rr, "/content/validation/1/resource",
@@ -446,10 +446,10 @@ public class ValidationServiceImplTest {
     @Test()
     public void testValidateResourceRecursively() throws Exception {
         modelBuilder.resourceProperty(propertyBuilder.build("field1"));
-        final ValidationModel vm1 = modelBuilder.build("resourcetype1");
+        final ValidationModel vm1 = modelBuilder.build("resourcetype1", "some source");
         modelBuilder = new ValidationModelBuilder();
         modelBuilder.resourceProperty(propertyBuilder.build("field2"));
-        final ValidationModel vm2 = modelBuilder.build("resourcetype2");
+        final ValidationModel vm2 = modelBuilder.build("resourcetype2", "some source");
 
         // set model retriever
         validationService.modelRetriever = new ValidationModelRetriever() {

Modified: sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/model/MergedValidationModelTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/model/MergedValidationModelTest.java?rev=1785131&r1=1785130&r2=1785131&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/model/MergedValidationModelTest.java (original)
+++ sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/model/MergedValidationModelTest.java Thu Mar  2 13:27:50 2017
@@ -19,14 +19,11 @@
 package org.apache.sling.validation.impl.model;
 
 import java.util.Arrays;
-import java.util.regex.Pattern;
 
-import org.apache.sling.validation.model.ChildResource;
-import org.apache.sling.validation.model.ResourceProperty;
+import org.apache.sling.validation.impl.util.ChildResourceNameRegexMatcher;
+import org.apache.sling.validation.impl.util.ResourcePropertyNameRegexMatcher;
 import org.apache.sling.validation.model.ValidationModel;
-import org.hamcrest.Description;
 import org.hamcrest.Matchers;
-import org.hamcrest.TypeSafeMatcher;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -41,23 +38,25 @@ public class MergedValidationModelTest {
     public void setup() {
         modelBuilder = new ValidationModelBuilder();
         propertyBuilder = new ResourcePropertyBuilder();
+        // each model needs at least one property or childresource
+        modelBuilder.resourceProperty(propertyBuilder.build("nameToOverwrite"));
         childResourceBuilder = new ChildResourceBuilder();
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void testMoreSpecificApplicationPathInModelToMerge() {
         modelBuilder.addApplicablePath("/base/path").addApplicablePath("/base/path2");
-        ValidationModel baseValidationModel = modelBuilder.build("base");
+        ValidationModel baseValidationModel = modelBuilder.build("base", "some source");
         modelBuilder.setApplicablePath("/base/path3");
-        new MergedValidationModel(baseValidationModel, modelBuilder.build("superType"));
+        new MergedValidationModel(baseValidationModel, modelBuilder.build("superType", "some source"));
     }
 
     @Test
     public void testLessSpecificApplicationPathInModelToMerge() {
         modelBuilder.addApplicablePath("/base/path").addApplicablePath("/base/path2");
-        ValidationModel baseValidationModel = modelBuilder.build("base");
+        ValidationModel baseValidationModel = modelBuilder.build("base", "some source");
         modelBuilder.setApplicablePath("/base");
-        ValidationModel mergedModel = new MergedValidationModel(baseValidationModel, modelBuilder.build("superType"));
+        ValidationModel mergedModel = new MergedValidationModel(baseValidationModel, modelBuilder.build("superType", "some source"));
         Assert.assertThat(Arrays.asList(mergedModel.getApplicablePaths()),
                 Matchers.contains("/base/path", "/base/path2"));
     }
@@ -67,7 +66,7 @@ public class MergedValidationModelTest {
         modelBuilder.resourceProperty(propertyBuilder.nameRegex("overwrittenNameToOverwrite").build("nameToOverwrite"));
         modelBuilder.childResource(childResourceBuilder.nameRegex("overwrittenNameToOverwrite")
                 .build("nameToOverwrite"));
-        ValidationModel baseValidationModel = modelBuilder.build("base");
+        ValidationModel baseValidationModel = modelBuilder.build("base", "some source");
         modelBuilder = new ValidationModelBuilder();
         modelBuilder.resourceProperty(propertyBuilder.nameRegex("originalNameToOverwrite").build("nameToOverwrite"));
         modelBuilder.childResource(childResourceBuilder.nameRegex("originalNameToOverwrite").build("nameToOverwrite"));
@@ -75,7 +74,7 @@ public class MergedValidationModelTest {
                 "nameNotOverwritten"));
         modelBuilder.childResource(childResourceBuilder.nameRegex("originalNameNotOverwritten").build(
                 "nameNotOverwritten"));
-        ValidationModel mergedModel = new MergedValidationModel(baseValidationModel, modelBuilder.build("superType"));
+        ValidationModel mergedModel = new MergedValidationModel(baseValidationModel, modelBuilder.build("superType", "some source"));
         Assert.assertThat(mergedModel.getResourceProperties(), Matchers.containsInAnyOrder(
                 new ResourcePropertyNameRegexMatcher("overwrittenNameToOverwrite"),
                 new ResourcePropertyNameRegexMatcher("originalNameNotOverwritten")));
@@ -85,63 +84,9 @@ public class MergedValidationModelTest {
 
     @Test
     public void testValidatedResourceTypes() {
-        ValidationModel mergedModel = new MergedValidationModel(modelBuilder.build("base"),
-                modelBuilder.build("superType"));
+        ValidationModel mergedModel = new MergedValidationModel(modelBuilder.build("base", "some source"),
+                modelBuilder.build("superType", "some source"));
         Assert.assertThat(mergedModel.getValidatedResourceType(), Matchers.equalTo("base"));
     }
 
-    /**
-     * Custom Hamcrest matcher which matches Resource Properties based on the equality only on their namePatterns.
-     */
-    private static final class ResourcePropertyNameRegexMatcher extends TypeSafeMatcher<ResourceProperty> {
-
-        private final String expectedNameRegex;
-
-        public ResourcePropertyNameRegexMatcher(String nameRegex) {
-            expectedNameRegex = nameRegex;
-        }
-
-        @Override
-        public void describeTo(Description description) {
-            description.appendText("ResourceProperty with namePattern=" + expectedNameRegex);
-        }
-
-        @Override
-        protected boolean matchesSafely(ResourceProperty resourceProperty) {
-            Pattern namePattern = resourceProperty.getNamePattern();
-            if (namePattern == null) {
-                return false;
-            } else {
-                return expectedNameRegex.equals(namePattern.toString());
-            }
-        }
-
-    }
-
-    /**
-     * Custom Hamcrest matcher which matches ChildResource based on the equality only on their namePatterns.
-     */
-    private static final class ChildResourceNameRegexMatcher extends TypeSafeMatcher<ChildResource> {
-
-        private final String expectedNameRegex;
-
-        public ChildResourceNameRegexMatcher(String nameRegex) {
-            expectedNameRegex = nameRegex;
-        }
-
-        @Override
-        public void describeTo(Description description) {
-            description.appendText("ChildResource with namePattern=" + expectedNameRegex);
-        }
-
-        @Override
-        protected boolean matchesSafely(ChildResource childResource) {
-            Pattern namePattern = childResource.getNamePattern();
-            if (namePattern == null) {
-                return false;
-            } else {
-                return expectedNameRegex.equals(namePattern.toString());
-            }
-        }
-    }
 }

Modified: sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/resourcemodel/ResourceValidationModelProviderImplTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/resourcemodel/ResourceValidationModelProviderImplTest.java?rev=1785131&r1=1785130&r2=1785131&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/resourcemodel/ResourceValidationModelProviderImplTest.java (original)
+++ sling/trunk/bundles/extensions/validation/core/src/test/java/org/apache/sling/validation/impl/resourcemodel/ResourceValidationModelProviderImplTest.java Thu Mar  2 13:27:50 2017
@@ -222,9 +222,10 @@ public class ResourceValidationModelProv
     @Test
     public void testGetValidationModels() throws Exception {
         // build two models manually (which are identical except for the applicable path)
-        ValidationModel model1 = modelBuilder.build("sling/validation/test");
+        ResourcePropertyBuilder resourcePropertyBuilder = new ResourcePropertyBuilder();
+        ValidationModel model1 = modelBuilder.resourceProperty(resourcePropertyBuilder.build("property1")).build("sling/validation/test", libsValidatorsRoot.getPath() + "/testValidationModel1");
         modelBuilder.setApplicablePath("/content/site2");
-        ValidationModel model2 = modelBuilder.build("sling/validation/test");
+        ValidationModel model2 = modelBuilder.build("sling/validation/test", libsValidatorsRoot.getPath() + "/testValidationModel2");
 
         // build models in JCR
         createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", model1);
@@ -238,7 +239,7 @@ public class ResourceValidationModelProv
     @Test
     public void testGetValidationModelsOutsideSearchPath() throws Exception {
         // build two models manually (which are identical except for the applicable path)
-        ValidationModel model1 = modelBuilder.build("sling/validation/test");
+        ValidationModel model1 = modelBuilder.build("sling/validation/test", "some source");
 
         Resource contentValidatorsRoot = ResourceUtil.getOrCreateResource(rr, "/content",
                 (Map<String, Object>) null, "sling:Folder", true);
@@ -263,7 +264,7 @@ public class ResourceValidationModelProv
         ResourceProperty childproperty = resourcePropertyBuilder.build("child1property");
         modelBuilder.childResource(new ChildResourceImpl("child1", null, true,
                 Collections.singletonList(childproperty), Collections.<ChildResource> emptyList()));
-        ValidationModel model1 = modelBuilder.build("sling/validation/test");
+        ValidationModel model1 = modelBuilder.build("sling/validation/test", libsValidatorsRoot.getPath() + "/testValidationModel1");
 
         // build models in JCR
         createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", model1);
@@ -276,11 +277,11 @@ public class ResourceValidationModelProv
     @Test
     public void testGetValidationModelsWithOverlay() throws Exception {
         // create two models manually (which are identical except for the applicable path)
-        ValidationModel model1 = modelBuilder.build("sling/validation/test");
+        ValidationModel model1 = modelBuilder.build("sling/validation/test", libsValidatorsRoot.getPath() + "/testValidationModel1");
         modelBuilder.setApplicablePath("/content/site2");
-        ValidationModel model2 = modelBuilder.build("sling/validation/test");
+        ValidationModel model2 = modelBuilder.build("sling/validation/test", appsValidatorsRoot.getPath() + "/testValidationModel1");
 
-        // create two models in the JCR: one in libs and one in apps (distinguishable via applicablePath)
+        // create two models: one in libs and one in apps (distinguishable via applicablePath)
         createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", model1);
         createValidationModelResource(rr, appsValidatorsRoot.getPath(), "testValidationModel1", model2);
 
@@ -292,26 +293,45 @@ public class ResourceValidationModelProv
     @Test(expected = IllegalStateException.class)
     public void testGetValidationModelsWithInvalidValidator() throws Exception {
         // create one default model
-        ValidationModel model1 = modelBuilder.build("sling/validation/test");
+        ValidationModel model1 = modelBuilder.build("sling/validation/test", libsValidatorsRoot.getPath() + "/testValidationModel1");
         createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", model1);
 
         // clear validator map to make the referenced validator unknown
         validatorMap.clear();
         modelProvider.getModels("sling/validation/test", validatorMap);
     }
-    
+
     @Test(expected = IllegalStateException.class)
     public void testGetValidationModelsWithMissingChildrenAndProperties() throws Exception {
         // create a model with neither children nor properties
         modelBuilder = new ValidationModelBuilder();
         modelBuilder.addApplicablePath("content/site1");
-        ValidationModel model1 = modelBuilder.build("sling/validation/test");
+        ValidationModel model1 = modelBuilder.build("sling/validation/test", libsValidatorsRoot.getPath() + "/testValidationModel1");
         
         createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", model1);
 
         modelProvider.getModels("sling/validation/test", validatorMap);
     }
 
+    @Test
+    public void testCachingOfGetValidationModels() throws Exception {
+        // build one model
+        ResourcePropertyBuilder resourcePropertyBuilder = new ResourcePropertyBuilder();
+        ValidationModel model1 = modelBuilder.resourceProperty(resourcePropertyBuilder.build("property1")).build("sling/validation/test", libsValidatorsRoot.getPath() + "/testValidationModel1");
+        modelBuilder.setApplicablePath("/content/site2");
+
+        // build models in JCR
+        createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel1", model1);
+
+        // check that both models are returned
+        Collection<ValidationModel> models = modelProvider.getModels("sling/validation/test", validatorMap);
+        Assert.assertThat(models, Matchers.containsInAnyOrder(model1));
+        
+        // the 2nd time the same instance should be returned
+        Collection<ValidationModel> models2 = modelProvider.getModels("sling/validation/test", validatorMap);
+        Assert.assertEquals("Due to caching both models should be actually the same instance", System.identityHashCode(models), System.identityHashCode(models2));
+    }
+
     private Resource createValidationModelResource(ResourceResolver rr, String root, String name, ValidationModel model)
             throws Exception {
         Map<String, Object> modelProperties = new HashMap<String, Object>();