You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2014/10/07 17:10:40 UTC

svn commit: r1629907 [2/3] - in /sling/trunk/contrib/validation: ./ api/ api/src/ api/src/main/ api/src/main/java/ api/src/main/java/org/ api/src/main/java/org/apache/ api/src/main/java/org/apache/sling/ api/src/main/java/org/apache/sling/validation/ a...

Added: sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java?rev=1629907&view=auto
==============================================================================
--- sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java (added)
+++ sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/ValidationServiceImpl.java Tue Oct  7 15:10:37 2014
@@ -0,0 +1,390 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.validation.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.SlingConstants;
+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.api.resource.ValueMap;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.commons.threads.ThreadPool;
+import org.apache.sling.commons.threads.ThreadPoolManager;
+import org.apache.sling.validation.api.ChildResource;
+import org.apache.sling.validation.api.ResourceProperty;
+import org.apache.sling.validation.api.Type;
+import org.apache.sling.validation.api.ValidationModel;
+import org.apache.sling.validation.api.ValidationResult;
+import org.apache.sling.validation.api.ValidationService;
+import org.apache.sling.validation.api.Validator;
+import org.apache.sling.validation.api.ValidatorLookupService;
+import org.apache.sling.validation.api.exceptions.SlingValidationException;
+import org.apache.sling.validation.impl.util.JCRBuilder;
+import org.apache.sling.validation.impl.util.Trie;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.ComponentContext;
+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;
+
+import javax.jcr.query.Query;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component()
+@Service(ValidationService.class)
+public class ValidationServiceImpl implements ValidationService, EventHandler {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ValidationServiceImpl.class);
+
+    static final String MODEL_XPATH_QUERY = "/jcr:root/%s/" + Constants.MODELS_HOME + "*[@sling:resourceType=\"%s\" and @%s=\"%s\"]";
+    static final String[] TOPICS = {SlingConstants.TOPIC_RESOURCE_REMOVED, SlingConstants.TOPIC_RESOURCE_CHANGED,
+            SlingConstants.TOPIC_RESOURCE_ADDED};
+
+    private Map<String, Trie<JCRValidationModel>> validationModelsCache = new ConcurrentHashMap<String, Trie<JCRValidationModel>>();
+    private ThreadPool threadPool;
+    private ServiceRegistration eventHandlerRegistration;
+
+    @Reference
+    private ResourceResolverFactory rrf = null;
+
+    @Reference
+    private ValidatorLookupService validatorLookupService = null;
+
+    @Reference
+    private ThreadPoolManager tpm = null;
+
+    // ValidationService ###################################################################################################################
+    @Override
+    public ValidationModel getValidationModel(String validatedResourceType, String resourcePath) {
+        ValidationModel model = null;
+        Trie<JCRValidationModel> modelsForResourceType = validationModelsCache.get(validatedResourceType);
+        if (modelsForResourceType != null) {
+            model = modelsForResourceType.getElementForLongestMatchingKey(resourcePath).getValue();
+        }
+        if (model == null) {
+            modelsForResourceType = searchAndStoreValidationModel(validatedResourceType);
+            if (modelsForResourceType != null) {
+                model = modelsForResourceType.getElementForLongestMatchingKey(resourcePath).getValue();
+            }
+        }
+        return model;
+    }
+
+    @Override
+    public ValidationModel getValidationModel(Resource resource) {
+        return getValidationModel(resource.getResourceType(), resource.getPath());
+    }
+
+    @Override
+    public ValidationResult validate(Resource resource, ValidationModel model) {
+        if (resource == null || model == null) {
+            throw new IllegalArgumentException("ValidationResult.validate - cannot accept null parameters");
+        }
+        ValidationResultImpl result = new ValidationResultImpl();
+
+        // validate direct properties of the resource
+        validateResourceProperties(resource, resource, model.getResourceProperties(), result);
+
+        // validate children resources, if any
+        for (ChildResource childResource : model.getChildren()) {
+            Resource expectedResource = resource.getChild(childResource.getName());
+            if (expectedResource != null) {
+                validateResourceProperties(resource, expectedResource, childResource.getProperties(), result);
+            } else {
+                result.addFailureMessage(childResource.getName(), "Missing required child resource.");
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public ValidationResult validate(ValueMap valueMap, ValidationModel model) {
+        if (valueMap == null || model == null) {
+            throw new IllegalArgumentException("ValidationResult.validate - cannot accept null parameters");
+        }
+        ValidationResultImpl result = new ValidationResultImpl();
+        for (ResourceProperty resourceProperty : model.getResourceProperties()) {
+            String property = resourceProperty.getName();
+            Object valuesObject = valueMap.get(property);
+            if (valuesObject == null) {
+                result.addFailureMessage(property, "Missing required property.");
+            }
+            Type propertyType = resourceProperty.getType();
+            Map<Validator, Map<String, String>> validators = resourceProperty.getValidators();
+            if (resourceProperty.isMultiple()) {
+                if (valuesObject instanceof String[]) {
+                    for (String fieldValue : (String[]) valuesObject) {
+                        validatePropertyValue(result, property, fieldValue, propertyType, validators);
+                    }
+                } else {
+                    result.addFailureMessage(property, "Expected multiple-valued property.");
+                }
+            } else {
+                if (valuesObject instanceof String[]) {
+                    // treat request attributes which are arrays
+                    String[] fieldValues = (String[]) valuesObject;
+                    if (fieldValues.length == 1) {
+                        validatePropertyValue(result, property, fieldValues[0], propertyType, validators);
+                    } else {
+                        result.addFailureMessage(property, "Expected single-valued property.");
+                    }
+                } else if (valuesObject instanceof String) {
+                    validatePropertyValue(result, property, (String) valuesObject, propertyType, validators);
+                }
+            }
+        }
+        return result;
+    }
+
+    // EventHandler ########################################################################################################################
+    @Override
+    public void handleEvent(Event event) {
+        Runnable task = new Runnable() {
+            @Override
+            public void run() {
+                validationModelsCache.clear();
+            }
+        };
+        threadPool.execute(task);
+    }
+
+    // OSGi ################################################################################################################################
+    @SuppressWarnings("unused")
+    protected void activate(ComponentContext componentContext) {
+        threadPool = tpm.get("Validation Service Thread Pool");
+        ResourceResolver rr = null;
+        try {
+            rr = rrf.getAdministrativeResourceResolver(null);
+        } catch (LoginException e) {
+            LOG.error("Cannot obtain a resource resolver.");
+        }
+        if (rr != null) {
+            StringBuilder sb = new StringBuilder("(");
+            String[] searchPaths = rr.getSearchPath();
+            if (searchPaths.length > 1) {
+                sb.append("|");
+            }
+            for (String searchPath : searchPaths) {
+                if (searchPath.endsWith("/")) {
+                    searchPath = searchPath.substring(0, searchPath.length() - 1);
+                }
+                String path = searchPath + "/" + Constants.MODELS_HOME;
+                sb.append("(path=").append(path).append("*)");
+            }
+            sb.append(")");
+            Dictionary<String, Object> eventHandlerProperties = new Hashtable<String, Object>();
+            eventHandlerProperties.put(EventConstants.EVENT_TOPIC, TOPICS);
+            eventHandlerProperties.put(EventConstants.EVENT_FILTER, sb.toString());
+            eventHandlerRegistration = componentContext.getBundleContext().registerService(EventHandler.class.getName(), this,
+                    eventHandlerProperties);
+            rr.close();
+        } else {
+            LOG.warn("Null resource resolver. Cannot apply path filtering for event processing. Skipping registering this service as an " +
+                    "EventHandler");
+        }
+    }
+
+    @SuppressWarnings("unused")
+    protected void deactivate(ComponentContext componentContext) {
+        if (threadPool != null) {
+            tpm.release(threadPool);
+        }
+        if (eventHandlerRegistration != null) {
+            eventHandlerRegistration.unregister();
+            eventHandlerRegistration = null;
+        }
+    }
+
+    private void validateResourceProperties(Resource rootResource, Resource resource, Set<ResourceProperty> resourceProperties,
+                                            ValidationResultImpl result) {
+        for (ResourceProperty resourceProperty : resourceProperties) {
+            String property = resourceProperty.getName();
+            ValueMap valueMap = resource.adaptTo(ValueMap.class);
+            Object fieldValues = valueMap.get(property);
+            String relativePath = resource.getPath().replace(rootResource.getPath(), "");
+            if (relativePath.length() > 0) {
+                if (relativePath.startsWith("/")) {
+                    relativePath = relativePath.substring(1);
+                }
+                property = relativePath + "/" + property;
+            }
+            if (fieldValues == null) {
+                result.addFailureMessage(property, "Missing required property.");
+            }
+            Type propertyType = resourceProperty.getType();
+            Map<Validator, Map<String, String>> validators = resourceProperty.getValidators();
+            if (fieldValues instanceof String[]) {
+                for (String fieldValue : (String[]) fieldValues) {
+                    validatePropertyValue(result, property, fieldValue, propertyType, validators);
+                }
+            } else if (fieldValues instanceof String) {
+                validatePropertyValue(result, property, (String) fieldValues, propertyType, validators);
+            }
+        }
+    }
+
+    /**
+     * Searches for valid validation models in the JCR repository for a certain resource type. 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.
+     * <p/>
+     * A valid content-tree {@code ValidationModel} has the following structure:
+     * <pre>
+     * validationModel
+     *      &#064;validatedResourceType
+     *      &#064;applicablePaths = [path1,path2,...] (optional)
+     *      &#064;sling:resourceType = sling/validation/model
+     *      fields
+     *          field1
+     *              &#064;fieldType
+     *              validators
+     *                  validator1
+     *                      &#064;validatorArguments = [key=value,key=value...] (optional)
+     *                  validatorN
+     *                      #064;validatorArguments = [key=value,key=value...] (optional)
+     *          fieldN
+     *              &#064;fieldType
+     *              validators
+     *                  validator1
+     *                  &#064;validatorArguments = [key=value,key=value...] (optional)
+     * </pre>
+     *
+     * @param validatedResourceType the type of resource for which to scan the JCR repository for validation models
+     * @return a {@link Trie} with the validation models; an empty trie if no model is found
+     */
+    private Trie<JCRValidationModel> searchAndStoreValidationModel(String validatedResourceType) {
+        Trie<JCRValidationModel> modelsForResourceType = null;
+        ResourceResolver rr = null;
+        JCRValidationModel vm;
+        try {
+            rr = rrf.getAdministrativeResourceResolver(null);
+            String[] searchPaths = rr.getSearchPath();
+            for (String searchPath : searchPaths) {
+                if (searchPath.endsWith("/")) {
+                    searchPath = searchPath.substring(0, searchPath.length() - 1);
+                }
+                final String queryString = String.format(MODEL_XPATH_QUERY, searchPath, Constants.VALIDATION_MODEL_RESOURCE_TYPE,
+                        Constants.VALIDATED_RESOURCE_TYPE, validatedResourceType);
+                Iterator<Resource> models = rr.findResources(queryString, Query.XPATH);
+                while (models.hasNext()) {
+                    Resource model = models.next();
+                    LOG.info("Found validation model resource {}.", model.getPath());
+                    String jcrPath = model.getPath();
+                    ValueMap validationModelProperties = model.adaptTo(ValueMap.class);
+                    String[] applicablePaths = PropertiesUtil.toStringArray(validationModelProperties.get(Constants.APPLICABLE_PATHS,
+                            String[].class));
+                    if (validatedResourceType != null && !"".equals(validatedResourceType)) {
+                        Resource r = model.getChild(Constants.PROPERTIES);
+                        if (r != null) {
+                            Set<ResourceProperty> resourceProperties = JCRBuilder.buildProperties(validatorLookupService, r);
+                            if (!resourceProperties.isEmpty()) {
+                                List<ChildResource> children = JCRBuilder.buildChildren(model, model, validatorLookupService);
+                                vm = new JCRValidationModel(jcrPath, resourceProperties, validatedResourceType, applicablePaths, children);
+                                modelsForResourceType = validationModelsCache.get(validatedResourceType);
+                                /**
+                                 * if the modelsForResourceType is null the canAcceptModel will return true: performance optimisation so that
+                                 * the Trie is created only if the model is accepted
+                                 */
+
+                                if (canAcceptModel(vm, searchPath, searchPaths, modelsForResourceType)) {
+                                    if (modelsForResourceType == null) {
+                                        modelsForResourceType = new Trie<JCRValidationModel>();
+                                        validationModelsCache.put(validatedResourceType, modelsForResourceType);
+                                    }
+                                    for (String applicablePath : vm.getApplicablePaths()) {
+                                        modelsForResourceType.insert(applicablePath, vm);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (LoginException e) {
+            LOG.error("Unable to obtain a resource resolver.", e);
+        }
+        if (rr != null) {
+            rr.close();
+        }
+        return modelsForResourceType;
+    }
+
+    /**
+     * Checks if the {@code validationModel} does not override an existing stored model given the fact that the overlaying is done based on
+     * the order in which the search paths are in the {@code searchPaths} array: the lower the index, the higher the priority.
+     *
+     * @param validationModel   the model to be checked
+     * @param currentSearchPath the current search path
+     * @param searchPaths       the available search paths
+     * @param validationModels  the existing validation models
+     * @return {@code true} if the new model can be stored, {@code false} otherwise
+     */
+    private boolean canAcceptModel(JCRValidationModel validationModel, String currentSearchPath, String[] searchPaths,
+                                   Trie<JCRValidationModel> validationModels) {
+        // perform null check to optimise performance in callee - no need to previously create the Trie if we're not going to accept the model
+        if (validationModels != null) {
+            String relativeModelPath = validationModel.getJcrPath().replaceFirst(currentSearchPath, "");
+            for (String searchPath : searchPaths) {
+                if (!currentSearchPath.equals(searchPath)) {
+                    for (String applicablePath : validationModel.getApplicablePaths()) {
+                        JCRValidationModel existingVM = validationModels.getElement(applicablePath).getValue();
+                        if (existingVM != null) {
+                            String existingModelRelativeModelPath = existingVM.getJcrPath().replaceFirst(searchPath, "");
+                            if (existingModelRelativeModelPath.equals(relativeModelPath)) {
+                                return false;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    private void validatePropertyValue(ValidationResultImpl result, String property, String value, Type propertyType, Map<Validator,
+            Map<String, String>> validators) {
+        if (!propertyType.isValid(value)) {
+            result.addFailureMessage(property, "Property was expected to be of type " + propertyType.getName());
+        }
+        for (Map.Entry<Validator, Map<String, String>> validatorEntry : validators.entrySet()) {
+            Validator validator = validatorEntry.getKey();
+            Map<String, String> arguments = validatorEntry.getValue();
+            try {
+                if (!validator.validate(value, arguments)) {
+                    result.addFailureMessage(property, "Property does not contain a valid value for the " + validator
+                            .getClass().getName() + " validator");
+                }
+            } catch (SlingValidationException e) {
+                LOG.error("SlingValidationException for resourceProperty " + property, e);
+                result.addFailureMessage(property, "Validator " + validator.getClass() + "encountered a problem: " + e.getMessage());
+            }
+        }
+    }
+}
\ No newline at end of file

Added: sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/ValidatorLookupServiceImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/ValidatorLookupServiceImpl.java?rev=1629907&view=auto
==============================================================================
--- sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/ValidatorLookupServiceImpl.java (added)
+++ sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/ValidatorLookupServiceImpl.java Tue Oct  7 15:10:37 2014
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.validation.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.validation.api.Validator;
+import org.apache.sling.validation.api.ValidatorLookupService;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component()
+@Service(ValidatorLookupService.class)
+@Reference(
+        name = "validator",
+        referenceInterface = Validator.class,
+        policy = ReferencePolicy.DYNAMIC,
+        cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE
+)
+public class ValidatorLookupServiceImpl implements ValidatorLookupService {
+
+    Map<String, Validator> validators = new ConcurrentHashMap<String, Validator>();
+
+    public Validator getValidator(String validatorType) {
+        return validators.get(validatorType);
+    }
+
+    // OSGi ################################################################################################################################
+    protected void bindValidator(Validator validator, Map<?, ?> properties) {
+        validators.put(validator.getClass().getName(), validator);
+    }
+
+    protected void unbindValidator(Validator validator, Map<?, ?> properties) {
+        validators.remove(validator.getClass().getName());
+    }
+}

Added: sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java?rev=1629907&view=auto
==============================================================================
--- sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java (added)
+++ sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/util/JCRBuilder.java Tue Oct  7 15:10:37 2014
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.validation.impl.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.validation.api.ChildResource;
+import org.apache.sling.validation.api.ResourceProperty;
+import org.apache.sling.validation.api.Type;
+import org.apache.sling.validation.api.Validator;
+import org.apache.sling.validation.api.ValidatorLookupService;
+import org.apache.sling.validation.impl.ChildResourceImpl;
+import org.apache.sling.validation.impl.Constants;
+import org.apache.sling.validation.impl.ResourcePropertyImpl;
+
+/**
+ * Helps building validation related objects from JCR content trees.
+ */
+public class JCRBuilder {
+
+    /**
+     * Creates a set of the properties that a resource is expected to have, together with the associated validators.
+     *
+     * @param vls                the {@link ValidatorLookupService}
+     * @param propertiesResource the resource identifying the properties node from a validation model's structure
+     * @return a set of properties or an empty set if no properties are defined
+     * @see ResourceProperty
+     */
+    public static Set<ResourceProperty> buildProperties(ValidatorLookupService vls, Resource propertiesResource) {
+        Set<ResourceProperty> properties = new HashSet<ResourceProperty>();
+        if (propertiesResource != null) {
+            for (Resource property : propertiesResource.getChildren()) {
+                String fieldName = property.getName();
+                ValueMap propertyValueMap = property.adaptTo(ValueMap.class);
+                Type type = Type.getType(propertyValueMap.get(Constants.PROPERTY_TYPE, String.class));
+                Boolean propertyMultiple = PropertiesUtil.toBoolean(propertyValueMap.get(Constants.PROPERTY_MULTIPLE), false);
+                Resource validators = property.getChild(Constants.VALIDATORS);
+                Map<Validator, Map<String, String>> validatorsMap = new HashMap<Validator, Map<String, String>>();
+                if (validators != null) {
+                    Iterator<Resource> validatorsIterator = validators.listChildren();
+                    while (validatorsIterator.hasNext()) {
+                        Resource validator = validatorsIterator.next();
+                        ValueMap validatorProperties = validator.adaptTo(ValueMap.class);
+                        String validatorName = validator.getName();
+                        Validator v = vls.getValidator(validatorName);
+                        String[] validatorArguments = validatorProperties.get(Constants.VALIDATOR_ARGUMENTS, String[].class);
+                        Map<String, String> validatorArgumentsMap = new HashMap<String, String>();
+                        if (validatorArguments != null) {
+                            for (String arg : validatorArguments) {
+                                String[] keyValuePair = arg.split("=");
+                                if (keyValuePair.length != 2) {
+                                    continue;
+                                }
+                                validatorArgumentsMap.put(keyValuePair[0], keyValuePair[1]);
+                            }
+                        }
+                        validatorsMap.put(v, validatorArgumentsMap);
+                    }
+                }
+                ResourceProperty f = new ResourcePropertyImpl(fieldName, type, propertyMultiple, validatorsMap);
+                properties.add(f);
+            }
+        }
+        return properties;
+    }
+
+    /**
+     * 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
+     *                               Constants#CHILDREN} node directly underneath it)
+     * @param validatorLookupService the {@link ValidatorLookupService}
+     * @return a list of all the children resources; the list will be empty if there are no children resources
+     */
+    public static List<ChildResource> buildChildren(Resource modelResource, Resource rootResource,
+                                                    ValidatorLookupService validatorLookupService) {
+        List<ChildResource> children = new ArrayList<ChildResource>();
+        Resource childrenResource = rootResource.getChild(Constants.CHILDREN);
+        if (childrenResource != null) {
+            for (Resource child : childrenResource.getChildren()) {
+                ChildResource childResource = new ChildResourceImpl(modelResource, child, validatorLookupService);
+                children.add(childResource);
+                children.addAll(buildChildren(modelResource, child, validatorLookupService));
+            }
+        }
+        return children;
+    }
+}

Added: sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/util/Trie.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/util/Trie.java?rev=1629907&view=auto
==============================================================================
--- sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/util/Trie.java (added)
+++ sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/util/Trie.java Tue Oct  7 15:10:37 2014
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.validation.impl.util;
+
+import java.util.Map;
+
+/**
+ * Trie data structure used for storing objects using {@link String} keys that allows object retrieval using a longest matching key
+ * mechanism.
+ */
+public class Trie<T> {
+
+    /**
+     * The {@code ROOT} node of the Trie, initialised with the "null" character.
+     */
+    public final TrieNode<T> ROOT = new TrieNode<T>('\0');
+
+    /**
+     * Inserts an object {@link T} under the specified {@code key}.
+     *
+     * @param key   the key under which the object will be stored
+     * @param value the object to be stored
+     */
+    public void insert(String key, T value) {
+        if (key != null && !"".equals(key)) {
+            int length = key.length();
+            TrieNode<T> node = ROOT;
+            for (int index = 0; index < length; index++) {
+                Map<Character, TrieNode<T>> children = node.getChildren();
+                char character = key.charAt(index);
+                node = children.get(character);
+                if (node == null) {
+                    node = new TrieNode<T>(character);
+                    children.put(character, node);
+                }
+            }
+            node.setLeaf(true);
+            node.setValue(value);
+        }
+    }
+
+    /**
+     * Retrieves the {@link TrieNode} stored under the best matching key.
+     *
+     * @param key the key; if the key doesn't match with an existing key, the best matching key will be used for retrieval; if no match is
+     *            found the {@link Trie#ROOT} node will be returned.
+     * @return the {@link TrieNode} stored under the best matching key or the {@link Trie#ROOT} node if no match was found
+     */
+    public TrieNode<T> getElementForLongestMatchingKey(String key) {
+        TrieNode<T> result = ROOT;
+        if (key != null && !"".equals(key)) {
+            int length = key.length();
+            TrieNode<T> node = ROOT;
+            for (int index = 0; index < length; index++) {
+                char character = key.charAt(index);
+                Map<Character, TrieNode<T>> children = node.getChildren();
+                node = children.get(character);
+                if (node != null) {
+                    if (node.isLeaf()) {
+                        result = node;
+                    }
+                } else {
+                    break;
+                }
+            }
+
+        }
+        return result;
+    }
+
+    /**
+     * Returns the {@link TrieNode} stored under the given {@code key}. If no element is stored under that key, the {@link Trie#ROOT} node
+     * will be returned.
+     * @param key the key
+     * @return the {@link TrieNode} stored under the given key or the {@link Trie#ROOT} if no node is found under that {@code key}
+     */
+    public TrieNode<T> getElement(String key) {
+        TrieNode<T> result = ROOT;
+        TrieNode<T> node = null;
+        boolean nodeExists = true;
+        if (key != null && !"".equals(key)) {
+            int length = key.length();
+            node = ROOT;
+            for (int index = 0; index < length; index++) {
+                char character = key.charAt(index);
+                Map<Character, TrieNode<T>> children = node.getChildren();
+                node = children.get(character);
+                if (node == null) {
+                    nodeExists = false;
+                    break;
+                }
+            }
+        }
+        if (nodeExists) {
+            result = node;
+        }
+        return result;
+    }
+}

Added: sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/util/TrieNode.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/util/TrieNode.java?rev=1629907&view=auto
==============================================================================
--- sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/util/TrieNode.java (added)
+++ sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/util/TrieNode.java Tue Oct  7 15:10:37 2014
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.validation.impl.util;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Implements a Trie node.
+ */
+public class TrieNode<T> {
+
+    private char character;
+    private T value;
+    private Map<Character, TrieNode<T>> children;
+    private boolean isLeaf;
+
+    TrieNode(char ch) {
+        character = ch;
+        children = new ConcurrentHashMap<Character, TrieNode<T>>();
+        isLeaf = false;
+    }
+
+    public char getCharacter() {
+        return character;
+    }
+
+    public T getValue() {
+        return value;
+    }
+
+    public void setValue(T value) {
+        this.value = value;
+    }
+
+    public Map<Character, TrieNode<T>> getChildren() {
+        return children;
+    }
+
+    public boolean isLeaf() {
+        return isLeaf;
+    }
+
+    public void setLeaf(boolean isLeaf) {
+        this.isLeaf = isLeaf;
+    }
+
+
+}

Added: sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/validators/RegexValidator.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/validators/RegexValidator.java?rev=1629907&view=auto
==============================================================================
--- sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/validators/RegexValidator.java (added)
+++ sling/trunk/contrib/validation/core/src/main/java/org/apache/sling/validation/impl/validators/RegexValidator.java Tue Oct  7 15:10:37 2014
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.validation.impl.validators;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.validation.api.Validator;
+import org.apache.sling.validation.api.exceptions.SlingValidationException;
+
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Performs regular expressions validation on the supplied data with the help of the {@link Pattern} class. This {@code Validator} expects a
+ * mandatory parameter in the arguments map: {@link RegexValidator#REGEX_PARAM}.
+ */
+@Component()
+@Service(Validator.class)
+public class RegexValidator implements Validator {
+
+    public static final String REGEX_PARAM = "regex";
+
+    @Override
+    public boolean validate(String data, Map<String, String> arguments) {
+        if (data == null || arguments == null) {
+            throw new SlingValidationException("Cannot perform data validation with null parameters");
+        }
+        String regex = arguments.get(REGEX_PARAM);
+        if (regex == null) {
+            throw new SlingValidationException("Mandatory " + REGEX_PARAM + " is missing from the arguments map.");
+        }
+        Pattern pattern = Pattern.compile(regex);
+        return pattern.matcher(data).matches();
+    }
+}

Added: sling/trunk/contrib/validation/core/src/main/resources/OSGI-INF/metatype/metatype.properties
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/validation/core/src/main/resources/OSGI-INF/metatype/metatype.properties?rev=1629907&view=auto
==============================================================================
--- sling/trunk/contrib/validation/core/src/main/resources/OSGI-INF/metatype/metatype.properties (added)
+++ sling/trunk/contrib/validation/core/src/main/resources/OSGI-INF/metatype/metatype.properties Tue Oct  7 15:10:37 2014
@@ -0,0 +1,5 @@
+validationservice.label = Sling Validation Service
+validationservice.description = The Sling Validation Service is responsible for locating Sling Validators
+
+alphacharactersvalidator.label = Alpha Characters Validator
+alphacharactersvalidator.description = The Alpha Characters Validator checks that submitted data contains only Unicode letters
\ No newline at end of file

Added: sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java?rev=1629907&view=auto
==============================================================================
--- sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java (added)
+++ sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/ValidationServiceImplTest.java Tue Oct  7 15:10:37 2014
@@ -0,0 +1,423 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.validation.impl;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.ModifiableValueMap;
+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.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.jcr.resource.JcrResourceConstants;
+import org.apache.sling.validation.api.Type;
+import org.apache.sling.validation.api.ValidationModel;
+import org.apache.sling.validation.api.ValidationResult;
+import org.apache.sling.validation.api.ValidationService;
+import org.apache.sling.validation.api.ValidatorLookupService;
+import org.apache.sling.validation.impl.setup.MockedResourceResolver;
+import org.apache.sling.validation.impl.validators.RegexValidator;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.reflect.Whitebox;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ValidationServiceImplTest {
+
+    /**
+     * Assume the validation models are stored under (/libs|/apps) + / + VALIDATION_MODELS_RELATIVE_PATH.
+     */
+    private static final String VALIDATION_MODELS_RELATIVE_PATH = "sling/validation/models";
+    private static final String APPS = "/apps";
+    private static final String LIBS = "/libs";
+    private static ResourceResolverFactory rrf;
+    private static Resource appsValidatorsRoot;
+    private static Resource libsValidatorsRoot;
+    private ValidationService validationService;
+    private ValidatorLookupService validatorLookupService;
+
+    @BeforeClass
+    public static void init() throws Exception {
+        rrf = mock(ResourceResolverFactory.class);
+        when(rrf.getAdministrativeResourceResolver(null)).thenAnswer(new Answer<ResourceResolver>() {
+            public ResourceResolver answer(InvocationOnMock invocation) throws Throwable {
+                return new MockedResourceResolver();
+            }
+        });
+        ResourceResolver rr = rrf.getAdministrativeResourceResolver(null);
+        if (rr != null) {
+            appsValidatorsRoot = ResourceUtil.getOrCreateResource(rr, APPS + "/" + VALIDATION_MODELS_RELATIVE_PATH, (Map) null,
+                    "sling:Folder", true);
+            libsValidatorsRoot = ResourceUtil.getOrCreateResource(rr, LIBS + "/" + VALIDATION_MODELS_RELATIVE_PATH, (Map) null,
+                    "sling:Folder", true);
+            rr.close();
+        }
+    }
+
+    @AfterClass
+    public static void beNiceAndClean() throws Exception {
+        ResourceResolver rr = rrf.getAdministrativeResourceResolver(null);
+        if (rr != null) {
+            if (appsValidatorsRoot != null) {
+                rr.delete(appsValidatorsRoot);
+            }
+            if (libsValidatorsRoot != null) {
+                rr.delete(libsValidatorsRoot);
+            }
+            rr.commit();
+            rr.close();
+        }
+    }
+
+    @Before
+    public void setUp() {
+        validationService = new ValidationServiceImpl();
+        Whitebox.setInternalState(validationService, "rrf", rrf);
+        validatorLookupService = mock(ValidatorLookupService.class);
+    }
+
+    @Test
+    public void testGetValidationModel() throws Exception {
+        when(validatorLookupService.getValidator("org.apache.sling.validation.impl.validators.RegexValidator")).thenReturn(new
+                RegexValidator());
+        Whitebox.setInternalState(validationService, "validatorLookupService", validatorLookupService);
+
+        List<TestProperty> properties = new ArrayList<TestProperty>();
+        TestProperty property = new TestProperty();
+        property.name = "field1";
+        property.type = Type.DATE;
+        property.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", null);
+        properties.add(property);
+        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);
+                model2 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel2", "sling/validation/test",
+                        new String[]{"/apps/validation/1",
+                        "/apps/validation/2"}, properties);
+            }
+
+            // BEST MATCHING PATH = /apps/validation/1; assume the applicable paths contain /apps/validation/2
+            ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource");
+            assertTrue(arrayContainsString(vm.getApplicablePaths(), "/apps/validation/2"));
+
+            // BEST MATCHING PATH = /apps/validation; assume the applicable paths contain /apps/validation but not /apps/validation/1
+            vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/resource");
+            assertTrue(arrayContainsString(vm.getApplicablePaths(), "/apps/validation"));
+            assertTrue(!arrayContainsString(vm.getApplicablePaths(), "/apps/validation/1"));
+            if (model1 != null) {
+                rr.delete(model1);
+            }
+            if (model2 != null) {
+                rr.delete(model2);
+            }
+        } finally {
+            if (rr != null) {
+                rr.commit();
+                rr.close();
+            }
+        }
+    }
+
+    @Test
+    public void testGetValidationModelWithOverlay() throws Exception {
+        when(validatorLookupService.getValidator("org.apache.sling.validation.impl.validators.RegexValidator")).thenReturn(new
+                RegexValidator());
+        Whitebox.setInternalState(validationService, "validatorLookupService", validatorLookupService);
+
+        List<TestProperty> fields = new ArrayList<TestProperty>();
+        TestProperty field = new TestProperty();
+        field.name = "field1";
+        field.type = Type.DATE;
+        field.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", null);
+        fields.add(field);
+        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);
+                model2 = createValidationModelResource(rr, appsValidatorsRoot.getPath(), "testValidationModel1", "sling/validation/test",
+                        new String[]{"/apps/validation/1",
+                                "/apps/validation/2"}, fields);
+                model3 = createValidationModelResource(rr, libsValidatorsRoot.getPath(), "testValidationModel2", "sling/validation/test",
+                        new String[]{"/apps/validation/3"}, fields);
+            }
+
+            // BEST MATCHING PATH = /apps/validation/1; assume the applicable paths contain /apps/validation/2
+            ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource");
+            assertTrue(arrayContainsString(vm.getApplicablePaths(), "/apps/validation/2"));
+
+            vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/3/resource");
+            assertTrue(arrayContainsString(vm.getApplicablePaths(), "/apps/validation/3"));
+
+            if (model1 != null) {
+                rr.delete(model1);
+            }
+            if (model2 != null) {
+                rr.delete(model2);
+            }
+            if (model3 != null) {
+                rr.delete(model3);
+            }
+        } finally {
+            if (rr != null) {
+                rr.commit();
+                rr.close();
+            }
+        }
+    }
+
+    @Test
+    public void testValueMapWithWrongDataType() throws Exception {
+        when(validatorLookupService.getValidator("org.apache.sling.validation.impl.validators.RegexValidator")).thenReturn(new
+                RegexValidator());
+        Whitebox.setInternalState(validationService, "validatorLookupService", validatorLookupService);
+
+        List<TestProperty> properties = new ArrayList<TestProperty>();
+        TestProperty property = new TestProperty();
+        property.name = "field1";
+        property.type = Type.DATE;
+        property.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", null);
+        properties.add(property);
+        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);
+            }
+            ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource");
+            HashMap<String, Object> hashMap = new HashMap<String, Object>() {{
+                put("field1", "1");
+            }};
+            ValueMap map = new ValueMapDecorator(hashMap);
+            ValidationResult vr = validationService.validate(map, vm);
+            assertFalse(vr.isValid());
+            if (model1 != null) {
+                rr.delete(model1);
+            }
+        } finally {
+            if (rr != null) {
+                rr.commit();
+                rr.close();
+            }
+        }
+    }
+
+    @Test
+    public void testValueMapWithCorrectDataType() throws Exception {
+        when(validatorLookupService.getValidator("org.apache.sling.validation.impl.validators.RegexValidator")).thenReturn(new
+                RegexValidator());
+        Whitebox.setInternalState(validationService, "validatorLookupService", validatorLookupService);
+
+        List<TestProperty> fields = new ArrayList<TestProperty>();
+        TestProperty field = new TestProperty();
+        field.name = "field1";
+        field.type = Type.STRING;
+        field.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new String[] {"regex=^\\p{L}+$"});
+        fields.add(field);
+        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);
+            }
+            ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource");
+            HashMap<String, Object> hashMap = new HashMap<String, Object>() {{
+                put("field1", "HelloWorld");
+            }};
+            ValueMap map = new ValueMapDecorator(hashMap);
+            ValidationResult vr = validationService.validate(map, vm);
+            assertTrue(vr.isValid());
+            if (model1 != null) {
+                rr.delete(model1);
+            }
+        } finally {
+            if (rr != null) {
+                rr.commit();
+                rr.close();
+            }
+        }
+    }
+
+    @Test
+     public void testResourceWithMissingChildProperty() throws Exception {
+        when(validatorLookupService.getValidator("org.apache.sling.validation.impl.validators.RegexValidator")).thenReturn(new
+                RegexValidator());
+        Whitebox.setInternalState(validationService, "validatorLookupService", validatorLookupService);
+
+        List<TestProperty> fields = new ArrayList<TestProperty>();
+        TestProperty property = new TestProperty();
+        property.name = "field1";
+        property.type = Type.INT;
+        property.validators.put("org.apache.sling.validation.impl.validators.RegexValidator", new String[] {RegexValidator.REGEX_PARAM + "=" + "\\d"});
+        fields.add(property);
+        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>(){{
+                    put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+                }});
+                Resource child = rr.create(modelChildren, "child1", new HashMap<String, Object>(){{
+                    put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+                }});
+                Resource childProperties = rr.create(child, "properties", new HashMap<String, Object>(){{
+                    put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+                }});
+                Resource childProperty = rr.create(childProperties, "hello", 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>(){{
+                    put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+                }});
+                Resource grandChild = rr.create(grandChildren, "grandChild1", new HashMap<String, Object>(){{
+                    put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+                }});
+
+                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);
+                }});
+                rr.commit();
+
+                ModifiableValueMap mvm = testResource.adaptTo(ModifiableValueMap.class);
+                mvm.put("field1", "1");
+                rr.commit();
+
+                // /apps/validation/1/resource/child1 will miss its mandatory "hello" property
+                Resource resourceChild = rr.create(testResource, "child1", new HashMap<String, Object>(){{
+                    put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+                }});
+
+                Resource resourceGrandChild = rr.create(resourceChild, "grandChild1", new HashMap<String, Object>(){{
+                    put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+                }});
+                rr.commit();
+            }
+            ValidationModel vm = validationService.getValidationModel("sling/validation/test", "/apps/validation/1/resource");
+            ValidationResult vr = validationService.validate(testResource, vm);
+            assertFalse(vr.isValid());
+            assertTrue(vr.getFailureMessages().containsKey("child1/hello"));
+        } finally {
+            if (rr != null) {
+                if (model1 != null) {
+                    rr.delete(model1);
+                }
+                if (testResource != null) {
+                    rr.delete(testResource);
+                }
+                rr.commit();
+                rr.close();
+            }
+        }
+    }
+
+    private Resource createValidationModelResource(ResourceResolver rr, String root, String name, String validatedResourceType,
+                                               String[] applicableResourcePaths, List<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);
+        modelProperties.put(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY, Constants.VALIDATION_MODEL_RESOURCE_TYPE);
+        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(Constants.PROPERTY_TYPE, property.type.getName());
+                    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);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return model;
+    }
+
+    private boolean arrayContainsString(String[] array, String string) {
+        boolean result = false;
+        if (array != null && string != null) {
+            for (String s : array) {
+                if (string.equals(s)) {
+                    result = true;
+                    break;
+                }
+            }
+        }
+        return result;
+    }
+
+    private class TestProperty {
+        String name;
+        Type type;
+        Map<String, String[]> validators;
+
+        TestProperty() {
+            validators = new HashMap<String, String[]>();
+        }
+    }
+
+    private class TestChild {
+
+    }
+
+}

Added: sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/setup/MockedResource.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/setup/MockedResource.java?rev=1629907&view=auto
==============================================================================
--- sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/setup/MockedResource.java (added)
+++ sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/setup/MockedResource.java Tue Oct  7 15:10:37 2014
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.validation.impl.setup;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.SyntheticResource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.jcr.resource.JcrResourceConstants;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class MockedResource extends SyntheticResource {
+
+    private final MockedResourceResolver mockedResourceResolver;
+    private Session session;
+
+    public MockedResource(MockedResourceResolver resourceResolver, Node node) throws RepositoryException {
+        super(resourceResolver, node.getPath(), node.getProperty("./sling:resourceType").getString() != null ? node.getProperty("./" +
+                JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY).getString() : node.getProperty(JcrConstants.JCR_PRIMARYTYPE).getString
+                ());
+        mockedResourceResolver = resourceResolver;
+
+    }
+
+    public MockedResource(MockedResourceResolver resourceResolver, String path,
+                          String resourceType) {
+        super(resourceResolver, path, resourceType);
+        mockedResourceResolver = resourceResolver;
+        resourceResolver.register(this);
+    }
+
+    private Session getSession() {
+        synchronized (this) {
+            if (session == null) {
+                try {
+                    session = mockedResourceResolver.createSession();
+                } catch (RepositoryException e) {
+                    throw new RuntimeException("RepositoryException: " + e, e);
+                }
+            }
+            return session;
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        close();
+        super.finalize();
+    }
+
+    public void close() {
+        synchronized (this) {
+            if (session != null) {
+                if (session.isLive()) {
+                    session.logout();
+                }
+                session = null;
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        if (type.equals(Node.class)) {
+            try {
+                return (AdapterType) getSession().getNode(getPath());
+            } catch (Exception e) {
+                throw new RuntimeException("Exception occurred: " + e, e);
+            }
+        } else if (type.equals(ValueMap.class)) {
+            try {
+                Session session = getSession();
+                Node node = session.getNode(getPath());
+                HashMap<String, Object> map = new HashMap<String, Object>();
+
+                PropertyIterator properties = node.getProperties();
+                while (properties.hasNext()) {
+                    Property p = properties.nextProperty();
+                    List valuesList;
+                    if (p.isMultiple()) {
+                        switch (p.getType()) {
+                            case PropertyType.STRING:
+                                valuesList = new ArrayList<String>();
+                                for (Value v : p.getValues()) {
+                                    valuesList.add(v.getString());
+                                }
+                                map.put(p.getName(), valuesList.toArray());
+                                break;
+                            case PropertyType.NAME:
+                                valuesList = new ArrayList<String>();
+                                for (Value v : p.getValues()) {
+                                    valuesList.add(v.getString());
+                                }
+                                map.put(p.getName(), valuesList.toArray());
+                                break;
+                        }
+                    } else if (p.getType() == PropertyType.BOOLEAN) {
+                        map.put(p.getName(), p.getBoolean());
+                    } else if (p.getType() == PropertyType.STRING) {
+                        map.put(p.getName(), p.getString());
+                    } else if (p.getType() == PropertyType.DATE) {
+                        map.put(p.getName(), p.getDate().getTime());
+                    } else if (p.getType() == PropertyType.NAME) {
+                        map.put(p.getName(), p.getName());
+                    } else {
+                        throw new RuntimeException(
+                                "Unsupported property type: " + p.getType());
+                    }
+                }
+                ValueMap valueMap = new ValueMapDecorator(map);
+                return (AdapterType) valueMap;
+            } catch (Exception e) {
+                e.printStackTrace();
+                return null;
+            }
+        } else if (type.equals(ModifiableValueMap.class)) {
+            return (AdapterType) new ModifiableValueMap() {
+
+                public Collection<Object> values() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public int size() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Object remove(Object arg0) {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void putAll(Map<? extends String, ? extends Object> arg0) {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Object put(String arg0, Object arg1) {
+                    Session session = getSession();
+                    try {
+                        final Node node = session.getNode(getPath());
+                        Object result = null;
+                        if (node.hasProperty(arg0)) {
+                            final Property previous = node.getProperty(arg0);
+                            if (previous == null) {
+                                // null
+                            } else if (previous.getType() == PropertyType.STRING) {
+                                result = previous.getString();
+                            } else if (previous.getType() == PropertyType.DATE) {
+                                result = previous.getDate();
+                            } else if (previous.getType() == PropertyType.BOOLEAN) {
+                                result = previous.getBoolean();
+                            } else {
+                                throw new UnsupportedOperationException();
+                            }
+                        }
+                        if (arg1 instanceof String) {
+                            node.setProperty(arg0, (String) arg1);
+                        } else if (arg1 instanceof Calendar) {
+                            node.setProperty(arg0, (Calendar) arg1);
+                        } else if (arg1 instanceof Boolean) {
+                            node.setProperty(arg0, (Boolean) arg1);
+                        } else {
+                            throw new UnsupportedOperationException();
+                        }
+                        return result;
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+
+                public Set<String> keySet() {
+                    Session session = getSession();
+                    try {
+                        final Node node = session.getNode(getPath());
+                        final PropertyIterator pi = node.getProperties();
+                        final Set<String> result = new HashSet<String>();
+                        while (pi.hasNext()) {
+                            final Property p = pi.nextProperty();
+                            result.add(p.getName());
+                        }
+                        return result;
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+
+                public boolean isEmpty() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Object get(Object arg0) {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Set<Entry<String, Object>> entrySet() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public boolean containsValue(Object arg0) {
+                    throw new UnsupportedOperationException();
+                }
+
+                public boolean containsKey(Object arg0) {
+                    Session session = getSession();
+                    try {
+                        final Node node = session.getNode(getPath());
+                        return node.hasProperty(String.valueOf(arg0));
+                    } catch (RepositoryException re) {
+                        throw new RuntimeException(re);
+                    }
+                }
+
+                public void clear() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public <T> T get(String name, T defaultValue) {
+                    throw new UnsupportedOperationException();
+                }
+
+                public <T> T get(String name, Class<T> type) {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        } else {
+            return super.adaptTo(type);
+        }
+    }
+
+}

Added: sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/setup/MockedResourceResolver.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/setup/MockedResourceResolver.java?rev=1629907&view=auto
==============================================================================
--- sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/setup/MockedResourceResolver.java (added)
+++ sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/setup/MockedResourceResolver.java Tue Oct  7 15:10:37 2014
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.validation.impl.setup;
+
+import org.apache.sling.api.resource.LoginException;
+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.commons.testing.jcr.RepositoryProvider;
+import org.apache.sling.commons.testing.jcr.RepositoryUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class MockedResourceResolver implements ResourceResolver {
+
+    private static final Logger LOG = LoggerFactory.getLogger(MockedResourceResolver.class);
+
+    private static final String[] SEARCH_PATHS = new String[] {"/apps", "/libs"};
+
+    public final RepositoryProvider repoProvider;
+    private List<MockedResource> resources = new LinkedList<MockedResource>();
+
+    private Session session;
+
+    public MockedResourceResolver() throws Exception {
+        this.repoProvider = RepositoryProvider.instance();
+        createSession();
+        RepositoryUtil.registerSlingNodeTypes(session);
+    }
+
+    public Session createSession() throws RepositoryException {
+        synchronized (this) {
+            if (session != null) {
+                return session;
+            }
+            session = repoProvider.getRepository().loginAdministrative(null);
+            return session;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        if (type.equals(Session.class)) {
+            try {
+                return (AdapterType) createSession();
+            } catch (RepositoryException e) {
+                throw new RuntimeException("RepositoryException: " + e, e);
+            }
+        } else if (type.equals(Repository.class)) {
+            try {
+                return (AdapterType) repoProvider.getRepository();
+            } catch (RepositoryException e) {
+                throw new RuntimeException("RepositoryException: " + e, e);
+            }
+        }
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Resource resolve(HttpServletRequest request, String absPath) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Resource resolve(String absPath) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Deprecated
+    public Resource resolve(HttpServletRequest request) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public String map(String resourcePath) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public String map(HttpServletRequest request, String resourcePath) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Resource getResource(String path) {
+        Session session;
+        try {
+            session = createSession();
+            session.getNode(path);
+        } catch (PathNotFoundException e) {
+            return null;
+        } catch (RepositoryException e) {
+            throw new RuntimeException("RepositoryException: " + e, e);
+        }
+        return new MockedResource(this, path, "nt:unstructured");
+    }
+
+    public Resource getResource(Resource base, String path) {
+        if (base.getPath().equals("/")) {
+            return getResource("/" + path);
+        } else {
+            return getResource(base.getPath() + "/" + path);
+        }
+    }
+
+    public String[] getSearchPath() {
+        return SEARCH_PATHS;
+    }
+
+    public Iterator<Resource> listChildren(Resource parent) {
+        try {
+            Node node = parent.adaptTo(Node.class);
+            final NodeIterator nodes = node.getNodes();
+            return new Iterator<Resource>() {
+
+                public void remove() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Resource next() {
+                    Node next = nodes.nextNode();
+                    try {
+                        return new MockedResource(MockedResourceResolver.this,
+                                next.getPath(), "nt:unstructured");
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException("RepositoryException: " + e,
+                                e);
+                    }
+                }
+
+                public boolean hasNext() {
+                    return nodes.hasNext();
+                }
+            };
+        } catch (RepositoryException e) {
+            throw new RuntimeException("RepositoryException: " + e, e);
+        }
+    }
+
+    public Iterable<Resource> getChildren(Resource parent) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Iterator<Resource> findResources(String query, String language) {
+        List<Resource> resources = new ArrayList<Resource>();
+        try {
+            NodeIterator iterator = session.getWorkspace().getQueryManager().createQuery(query, language).execute().getNodes();
+            while (iterator.hasNext()) {
+                Node n = iterator.nextNode();
+                Resource resource = new MockedResource(this, n);
+                resources.add(resource);
+            }
+        } catch (RepositoryException e) {
+            LOG.error("Unable to execute JCR query", e);
+        }
+        return resources.iterator();
+    }
+
+    public Iterator<Map<String, Object>> queryResources(String query,
+            String language) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public ResourceResolver clone(Map<String, Object> authenticationInfo)
+            throws LoginException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public boolean isLive() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public void close() {
+        Iterator<MockedResource> it = resources.iterator();
+        while (it.hasNext()) {
+            MockedResource r = it.next();
+            r.close();
+        }
+        if (session != null) {
+            if (session.isLive()) {
+                session.logout();
+            }
+            session = null;
+        }
+    }
+
+    public void register(MockedResource mockedResource) {
+        resources.add(mockedResource);
+    }
+
+    public String getUserID() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Iterator<String> getAttributeNames() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Object getAttribute(String name) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public void delete(Resource resource) throws PersistenceException {
+        if (resources.contains(resource)) {
+            resources.remove(resource);
+        }
+        Node node = resource.adaptTo(Node.class);
+        try {
+            node.remove();
+        } catch (RepositoryException e) {
+            throw new PersistenceException("RepositoryException: "+e, e);
+        }
+    }
+
+    public Resource create(Resource parent, String name,
+            Map<String, Object> properties) throws PersistenceException {
+        final Node parentNode = parent.adaptTo(Node.class);
+        try {
+            final Node child;
+            if (properties!=null && properties.containsKey("jcr:primaryType")) {
+                child = parentNode.addNode(name, (String) properties.get("jcr:primaryType"));
+            } else {
+                child = parentNode.addNode(name);
+            }
+            if (properties!=null) {
+                final Iterator<Entry<String, Object>> it = properties.entrySet().iterator();
+                while(it.hasNext()) {
+                    final Entry<String, Object> entry = it.next();
+                    if (entry.getKey().equals("jcr:primaryType")) {
+                        continue;
+                    }
+                    if (entry.getValue() instanceof String) {
+                        child.setProperty(entry.getKey(), (String)entry.getValue());
+                    } else if (entry.getValue() instanceof Boolean) {
+                        child.setProperty(entry.getKey(), (Boolean)entry.getValue());
+                    } else if (entry.getValue() instanceof  String[]) {
+                        child.setProperty(entry.getKey(), (String[]) entry.getValue());
+                    } else {
+                        throw new UnsupportedOperationException("Not implemented");
+                    }
+                }
+            }
+            return getResource(parent, name);
+        } catch (RepositoryException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void revert() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public void commit() throws PersistenceException {
+        try {
+            this.session.save();
+        } catch (final RepositoryException re) {
+            throw new PersistenceException("Unable to commit changes.", re);
+        }
+    }
+
+    public boolean hasChanges() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public String getParentResourceType(Resource resource) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public String getParentResourceType(String resourceType) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public boolean isResourceType(Resource resource, String resourceType) {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    public void refresh() {
+        // TODO Auto-generated method stub
+
+    }
+}

Added: sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/util/TrieTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/util/TrieTest.java?rev=1629907&view=auto
==============================================================================
--- sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/util/TrieTest.java (added)
+++ sling/trunk/contrib/validation/core/src/test/java/org/apache/sling/validation/impl/util/TrieTest.java Tue Oct  7 15:10:37 2014
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.validation.impl.util;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+public class TrieTest {
+
+    private Trie<Object> dictionary;
+
+    @Before
+    public void setUp() {
+        dictionary = new Trie<Object>();
+        dictionary.insert("/apps/example", "/apps/example");
+        dictionary.insert("/apps/examples/node/jcr:content", "/apps/examples/node/jcr:content");
+        dictionary.insert("/apps/examples/node/jcr:content/nodes", "/apps/examples/node/jcr:content/nodes");
+    }
+
+    @Test
+    public void testLongestMatchingKey() throws Exception {
+        TrieNode<Object> node;
+        node = dictionary.getElementForLongestMatchingKey("/apps/examples/node/jcr:content/nodes/1");
+        assertTrue("/apps/examples/node/jcr:content/nodes".equals(node.getValue()));
+
+        node = dictionary.getElementForLongestMatchingKey("/apps/example/node/jcr:content/nodes/1");
+        assertTrue("/apps/example".equals(node.getValue()));
+
+        node = dictionary.getElementForLongestMatchingKey("/libs");
+        assertTrue(node.getValue() == null);
+    }
+
+    @Test
+    public void testExactKey() {
+        TrieNode<Object> node;
+
+        node = dictionary.getElement("/apps/examples/node/jcr:content/nodes");
+        assertTrue("/apps/examples/node/jcr:content/nodes".equals(node.getValue()));
+
+        node = dictionary.getElement("/apps/example");
+        assertTrue("/apps/example".equals(node.getValue()));
+
+        node = dictionary.getElement("/libs");
+        assertTrue(dictionary.ROOT.equals(node));
+    }
+
+}

Propchange: sling/trunk/contrib/validation/examples/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Tue Oct  7 15:10:37 2014
@@ -0,0 +1,14 @@
+target
+bin
+*.iml
+*.ipr
+*.iws
+.settings
+.project
+.classpath
+.externalToolBuilders
+maven-eclipse.xml
+felix-cache
+sling-crankstart
+derby.log
+