Package org.grails.validation

Source Code of org.grails.validation.GrailsDomainClassValidator

/*
* Copyright 2004-2005 the original author or authors.
*
* Licensed 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.grails.validation;

import grails.core.GrailsApplication;
import grails.core.GrailsDomainClass;
import grails.core.GrailsDomainClassProperty;
import grails.core.support.GrailsApplicationAware;
import grails.validation.CascadingValidator;
import grails.validation.ConstrainedProperty;
import groovy.lang.GString;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;

import org.grails.core.artefact.DomainClassArtefactHandler;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.context.MessageSource;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;

/**
* A specialised Spring validator that validates a domain class instance using
* the constraints defined in the static constraints closure.
*
* @author Graeme Rocher
* @since 0.1
*/
public class GrailsDomainClassValidator implements CascadingValidator, GrailsApplicationAware {

    private static final List<String> EMBEDDED_EXCLUDES = Arrays.asList(
        GrailsDomainClassProperty.IDENTITY,
        GrailsDomainClassProperty.VERSION);

    protected Class<?> targetClass;
    protected GrailsDomainClass domainClass;
    protected MessageSource messageSource;
    protected GrailsApplication grailsApplication;

    @SuppressWarnings("rawtypes")
    public boolean supports(Class clazz) {
        return targetClass.equals(clazz);
    }

    /**
     * @see org.codehaus.groovy.grails.validation.CascadingValidator#validate(Object, org.springframework.validation.Errors, boolean)
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public void validate(Object obj, Errors errors, boolean cascade) {
        if (obj == null) {
            throw new IllegalArgumentException("Argument [" + obj + "] is not an instance of [" +
                    domainClass.getClazz() + "] which this validator is configured for");
        }

        BeanWrapper bean = new BeanWrapperImpl(obj);

        Map constrainedProperties = domainClass.getConstrainedProperties();
        Set<String> constrainedPropertyNames = new HashSet(constrainedProperties.keySet());

        for (Object key : constrainedProperties.keySet()) {
            String propertyName = (String) key;
            validatePropertyWithConstraint(propertyName, obj, errors, bean, constrainedProperties);
        }

        GrailsDomainClassProperty[] persistentProperties = domainClass.getPersistentProperties();

        for (GrailsDomainClassProperty persistentProperty : persistentProperties) {
            String propertyName = persistentProperty.getName();

            if ((persistentProperty.isAssociation() || persistentProperty.isEmbedded()) && cascade) {
                cascadeToAssociativeProperty(errors, bean, persistentProperty);
            }

            // Remove this property from the set of constrained property
            // names because we have already processed it.
            constrainedPropertyNames.remove(propertyName);
        }

        // Now process the remaining constrained properties, for example any transients.
        for (String name : constrainedPropertyNames) {
            validatePropertyWithConstraint(name, obj, errors, bean, constrainedProperties);
        }

        postValidate(obj,errors);
    }

    /**
     * Subclasses can overrite to provide custom handling of the errors object post validation.
     *
     * @param obj  The object to validate
     * @param errors The Errors object
     */
    protected void postValidate(Object obj, Errors errors) {
        // do nothing
    }

    /**
     * @see org.springframework.validation.Validator#validate(Object, org.springframework.validation.Errors)
     */
    public void validate(Object obj, Errors errors) {
        validate(obj, errors, false);
    }

    /**
     * Cascades validation onto an associative property maybe a one-to-many, one-to-one or many-to-one relationship.
     *
     * @param errors The Errors instnace
     * @param bean The original bean
     * @param persistentProperty The associative property
     */
    protected void cascadeToAssociativeProperty(Errors errors, BeanWrapper bean, GrailsDomainClassProperty persistentProperty) {
        String propertyName = persistentProperty.getName();
        if (errors.hasFieldErrors(propertyName)) return;

        if (persistentProperty.isManyToOne() || persistentProperty.isOneToOne() || persistentProperty.isEmbedded()) {
            Object associatedObject = bean.getPropertyValue(propertyName);
            cascadeValidationToOne(errors, bean,associatedObject, persistentProperty, propertyName, null);
        }
        else if (persistentProperty.isOneToMany()) {
            cascadeValidationToMany(errors, bean, persistentProperty, propertyName);
        }
    }

    /**
     * Cascades validation to a one-to-many type relationship. Normally a collection such as a List or Set
     * each element in the association will also be validated.
     *
     * @param errors The Errors instance
     * @param bean The original BeanWrapper
     * @param persistentProperty An association whose isOneToMeny() method returns true
     * @param propertyName The name of the property
     */
    @SuppressWarnings("rawtypes")
    protected void cascadeValidationToMany(Errors errors, BeanWrapper bean,
            GrailsDomainClassProperty persistentProperty, String propertyName) {

        Object collection = bean.getPropertyValue(propertyName);
        if (collection instanceof List || collection instanceof SortedSet) {
            int idx = 0;
            for (Object associatedObject : ((Collection)collection)) {
                cascadeValidationToOne(errors, bean,associatedObject, persistentProperty, propertyName, idx++);
            }
        }
        else if (collection instanceof Collection) {
            Integer index = 0;
            for (Object associatedObject : ((Collection)collection)) {
                cascadeValidationToOne(errors, bean,associatedObject, persistentProperty, propertyName, index++);
            }
        }
        else if (collection instanceof Map) {

            filterGStringKeys((Map)collection);
            for (Object entryObject : ((Map) collection).entrySet()) {
                Map.Entry entry = (Map.Entry) entryObject;
                cascadeValidationToOne(errors, bean, entry.getValue(), persistentProperty, propertyName, entry.getKey());
            }
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void filterGStringKeys(Map collection) {
        Set set = collection.keySet();
        Set<GString> gstrings = new HashSet<GString>();
        for (Object o : set) {
            if (o instanceof GString) {
                gstrings.add((GString) o);
            }
        }

        for (GString gstring : gstrings) {
            Object value = collection.remove(gstring);
            collection.put(gstring.toString(), value);
        }
    }

    @SuppressWarnings("rawtypes")
    private void validatePropertyWithConstraint(String propertyName, Object obj, Errors errors,
            BeanWrapper bean, Map constrainedProperties) {

        int i = propertyName.lastIndexOf(".");
        String constrainedPropertyName;
        if (i > -1) {
            constrainedPropertyName = propertyName.substring(i + 1, propertyName.length());
        }
        else {
            constrainedPropertyName = propertyName;
        }
        FieldError fieldError = errors.getFieldError(constrainedPropertyName);
        if (fieldError == null) {
            ConstrainedProperty c = (ConstrainedProperty) constrainedProperties.get(constrainedPropertyName);
            c.setMessageSource(messageSource);
            c.validate(obj, bean.getPropertyValue(constrainedPropertyName), errors);
        }
    }

    /**
     * Cascades validation to a one-to-one or many-to-one property.
     *
     * @param errors The Errors instance
     * @param bean The original BeanWrapper
     * @param associatedObject The associated object's current value
     * @param persistentProperty The GrailsDomainClassProperty instance
     * @param propertyName The name of the property
     * @param indexOrKey
     */
    @SuppressWarnings("rawtypes")
    protected void cascadeValidationToOne(Errors errors, BeanWrapper bean, Object associatedObject,
            GrailsDomainClassProperty persistentProperty, String propertyName, Object indexOrKey) {

        if (associatedObject == null) {
            return;
        }

        GrailsDomainClass associatedDomainClass = getAssociatedDomainClass(associatedObject, persistentProperty);

        if (associatedDomainClass == null || !isOwningInstance(bean, associatedDomainClass) && !persistentProperty.isExplicitSaveUpdateCascade()) {
            return;
        }

        GrailsDomainClassProperty otherSide = null;
        if (persistentProperty.isBidirectional()) {
            otherSide = persistentProperty.getOtherSide();
        }

        Map associatedConstraintedProperties = associatedDomainClass.getConstrainedProperties();

        GrailsDomainClassProperty[] associatedPersistentProperties = associatedDomainClass.getPersistentProperties();
        String nestedPath = errors.getNestedPath();
        try {
            errors.setNestedPath(buildNestedPath(nestedPath, propertyName, indexOrKey));

            for (GrailsDomainClassProperty associatedPersistentProperty : associatedPersistentProperties) {
                if (associatedPersistentProperty.equals(otherSide)) continue;
                if (persistentProperty.isEmbedded() && EMBEDDED_EXCLUDES.contains(associatedPersistentProperty.getName())) {
                    continue;
                }

                String associatedPropertyName = associatedPersistentProperty.getName();
                if (associatedConstraintedProperties.containsKey(associatedPropertyName)) {
                    validatePropertyWithConstraint(errors.getNestedPath() + associatedPropertyName,
                            associatedObject, errors, new BeanWrapperImpl(associatedObject),
                            associatedConstraintedProperties);
                }

                if (associatedPersistentProperty.isAssociation()) {
                    cascadeToAssociativeProperty(errors, new BeanWrapperImpl(associatedObject),
                            associatedPersistentProperty);
                }
            }
        }
        finally {
            errors.setNestedPath(nestedPath);
        }
    }

    private String buildNestedPath(String nestedPath, String componentName, Object indexOrKey) {
        if (indexOrKey == null) {
            // Component is neither part of a Collection nor Map.
            return nestedPath + componentName;
        }

        if (indexOrKey instanceof Integer) {
          // Component is part of a Collection. Collection access string
          // e.g. path.object[1] will be appended to the nested path.
            return nestedPath + componentName + "[" + indexOrKey + "]";
        }

        // Component is part of a Map. Nested path should have a key surrounded
        // with apostrophes at the end.
        return nestedPath + componentName + "['" + indexOrKey + "']";
    }

    private GrailsDomainClass getAssociatedDomainClass(Object associatedObject, GrailsDomainClassProperty persistentProperty) {
        if (persistentProperty.isEmbedded()) {
            return persistentProperty.getComponent();
        }

        if (grailsApplication != null) {
            return getAssociatedDomainClassFromApplication(associatedObject);
        }

        return persistentProperty.getReferencedDomainClass();
    }

    protected GrailsDomainClass getAssociatedDomainClassFromApplication(Object associatedObject) {
        String associatedObjectType = associatedObject.getClass().getName();
        return (GrailsDomainClass) grailsApplication.getArtefact(DomainClassArtefactHandler.TYPE, associatedObjectType);
    }

    private boolean isOwningInstance(BeanWrapper bean, GrailsDomainClass associatedDomainClass) {
        Class<?> currentClass = bean.getWrappedClass();
        while (currentClass != Object.class) {
            if (associatedDomainClass.isOwningClass(currentClass)) {
                return true;
            }
            currentClass = currentClass.getSuperclass();
        }
        return false;
    }

    /**
     * @param domainClass The domainClass to set.
     */
    public void setDomainClass(GrailsDomainClass domainClass) {
        this.domainClass = domainClass;
        domainClass.setValidator(this);
        targetClass = domainClass.getClazz();
    }

    public GrailsDomainClass getDomainClass() {
        return domainClass;
    }

    /**
     * @param messageSource The messageSource to set.
     */
    public void setMessageSource(MessageSource messageSource) {
         this.messageSource = messageSource;
     }

    public void setGrailsApplication(GrailsApplication grailsApplication) {
        this.grailsApplication = grailsApplication;
    }
}
TOP

Related Classes of org.grails.validation.GrailsDomainClassValidator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.