Package org.grails.validation

Source Code of org.grails.validation.DefaultConstraintEvaluator

/*
* Copyright (C) 2011 SpringSource
*
* 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.GrailsDomainClass;
import grails.core.GrailsDomainClassProperty;
import grails.io.IOUtils;
import grails.util.GrailsClassUtils;
import grails.validation.Constrained;
import grails.validation.ConstrainedProperty;
import grails.validation.ConstraintsEvaluator;
import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import groovy.lang.Script;

import java.io.InputStream;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.persistence.Entity;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.control.CompilationFailedException;
import org.grails.core.exceptions.GrailsConfigurationException;

/**
* Default implementation of the {@link grails.validation.ConstraintsEvaluator} interface.
*
* TODO: Subclass this to add hibernate-specific exceptions!
*
* @author Graeme Rocher
* @since 2.0
*/
public class DefaultConstraintEvaluator implements ConstraintsEvaluator, org.codehaus.groovy.grails.validation.ConstraintsEvaluator {

    private static final Log LOG = LogFactory.getLog(DefaultConstraintEvaluator.class);
    private Map<String, Object> defaultConstraints;

    public DefaultConstraintEvaluator(Map<String, Object> defaultConstraints) {
        this.defaultConstraints = defaultConstraints;
    }

    public DefaultConstraintEvaluator() {
        // default
    }

    public Map<String, Object> getDefaultConstraints() {
        return defaultConstraints;
    }

    public Map<String, Constrained> evaluate(@SuppressWarnings("rawtypes") Class cls) {
        return evaluateConstraints(cls, null, false);
    }

    public Map<String, Constrained> evaluate(@SuppressWarnings("rawtypes") Class cls, boolean defaultNullable) {
        return evaluateConstraints(cls, null, defaultNullable);
    }
   
    public Map<String, Constrained> evaluate(GrailsDomainClass cls) {
        return evaluate(cls.getClazz(), cls.getPersistentProperties());
    }

    /**
     * Evaluates the constraints closure to build the list of constraints
     *
     * @param theClass  The domain class to evaluate constraints for
     * @param properties The properties of the instance
     *
     * @return A Map of constraints
     */
    protected Map<String, Constrained> evaluateConstraints(
          final Class<?> theClass, GrailsDomainClassProperty[] properties) {
        return evaluateConstraints(theClass, properties, false);
    }
   
    /**
     * Evaluates the constraints closure to build the list of constraints
     *
     * @param theClass  The domain class to evaluate constraints for
     * @param properties The properties of the instance
     * @param defaultNullable Indicates if properties are nullable by default
     *
     * @return A Map of constraints
     */
    protected Map<String, Constrained> evaluateConstraints(
          final Class<?> theClass, GrailsDomainClassProperty[] properties, boolean defaultNullable) {

        boolean javaEntity = theClass.isAnnotationPresent(Entity.class);
        LinkedList<?> classChain = getSuperClassChain(theClass);
        Class<?> clazz;

        ConstrainedPropertyBuilder delegate = new ConstrainedPropertyBuilder(theClass);

        // Evaluate all the constraints closures in the inheritance chain
        for (Object aClassChain : classChain) {
            clazz = (Class<?>) aClassChain;
            Closure<?> c = (Closure<?>) GrailsClassUtils.getStaticFieldValue(clazz, ConstraintsEvaluator.PROPERTY_NAME);
            if (c == null) {
                c = getConstraintsFromScript(theClass);
            }

            if (c != null) {
                c = (Closure<?>) c.clone();
                c.setResolveStrategy(Closure.DELEGATE_ONLY);
                c.setDelegate(delegate);
                c.call();
            }
            else {
                LOG.debug("User-defined constraints not found on class [" + clazz + "], applying default constraints");
            }
        }

        Map<String, Constrained> constrainedProperties = delegate.getConstrainedProperties();
        if (properties != null && !(constrainedProperties.isEmpty() && javaEntity)) {

            for (GrailsDomainClassProperty p : properties) {
                // assume no formula issues if Hibernate isn't available to avoid CNFE
                if (canPropertyBeConstrained(p)) {
                    if (p.isDerived()) {
                        if (constrainedProperties.remove(p.getName()) != null) {
                            LOG.warn("Derived properties may not be constrained. Property [" + p.getName() + "] of domain class " + theClass.getName() + " will not be checked during validation.");
                        }
                    } else {
                        final String propertyName = p.getName();
                        Constrained cp = constrainedProperties.get(propertyName);
                        if (cp == null) {
                            ConstrainedProperty constrainedProperty = new ConstrainedProperty(p.getDomainClass().getClazz(), propertyName, p.getType());
                            cp = constrainedProperty;
                            constrainedProperty.setOrder(constrainedProperties.size() + 1);
                            constrainedProperties.put(propertyName, cp);
                        }
                        // Make sure all fields are required by default, unless
                        // specified otherwise by the constraints
                        // If the field is a Java entity annotated with @Entity skip this
                        applyDefaultConstraints(propertyName, p, cp, defaultConstraints);
                    }
                }
            }
        }

        if (properties == null || properties.length == 0) {
            final Set<Entry<String, Constrained>> entrySet = constrainedProperties.entrySet();
            for (Entry<String, Constrained> entry : entrySet) {
                final Constrained constrainedProperty = entry.getValue();
                if (!constrainedProperty.hasAppliedConstraint(ConstrainedProperty.NULLABLE_CONSTRAINT)) {
                    applyDefaultNullableConstraint(constrainedProperty, defaultNullable);
                }
            }
        }

        applySharedConstraints(delegate, constrainedProperties);

        return constrainedProperties;
    }

    protected void applySharedConstraints(
            ConstrainedPropertyBuilder constrainedPropertyBuilder,
            Map<String, Constrained> constrainedProperties) {
        for (Map.Entry<String, Constrained> entry : constrainedProperties.entrySet()) {
            String propertyName = entry.getKey();
            Constrained constrainedProperty = entry.getValue();
            String sharedConstraintReference = constrainedPropertyBuilder.getSharedConstraint(propertyName);
            if (sharedConstraintReference != null && defaultConstraints !=  null) {
                Object o = defaultConstraints.get(sharedConstraintReference);
                if (o instanceof Map) {
                    @SuppressWarnings({ "unchecked", "rawtypes" })
                    Map<String, Object> constraintsWithinSharedConstraint = (Map) o;
                    for (Map.Entry<String, Object> e : constraintsWithinSharedConstraint.entrySet()) {
                        constrainedProperty.applyConstraint(e.getKey(), e.getValue());
                    }
                } else {
                    throw new GrailsConfigurationException("Property [" +
                            constrainedProperty.getOwner().getName() + '.' + propertyName +
                            "] references shared constraint [" + sharedConstraintReference +
                            ":" + o + "], which doesn't exist!");
                }
            }
        }
    }

    protected boolean canPropertyBeConstrained(GrailsDomainClassProperty property) {
        return true;
    }

    public static LinkedList<?> getSuperClassChain(Class<?> theClass) {
        LinkedList<Class<?>> classChain = new LinkedList<Class<?>>();
        Class<?> clazz = theClass;
        while (clazz != Object.class && clazz != null) {
            classChain.addFirst(clazz);
            clazz = clazz.getSuperclass();
        }
        return classChain;
    }

    protected Closure<?> getConstraintsFromScript(Class<?> theClass) {
        // Fallback to xxxxConstraints.groovy script for Java domain classes
        String className = theClass.getName();
        String constraintsScript = className.replaceAll("\\.","/") + ConstraintsEvaluator.CONSTRAINTS_GROOVY_SCRIPT;
        InputStream stream = getClass().getClassLoader().getResourceAsStream(constraintsScript);

        if (stream != null) {
            GroovyClassLoader gcl = new GroovyClassLoader();
            try {
                Class<?> scriptClass = gcl.parseClass(IOUtils.toString(stream, "UTF-8"));
                Script script = (Script)scriptClass.newInstance();
                script.run();
                Binding binding = script.getBinding();
                if (binding.getVariables().containsKey(ConstraintsEvaluator.PROPERTY_NAME)) {
                    return (Closure<?>)binding.getVariable(ConstraintsEvaluator.PROPERTY_NAME);
                }
                LOG.warn("Unable to evaluate constraints from [" + constraintsScript + "], constraints closure not found!");
                return null;
            }
            catch (CompilationFailedException e) {
                LOG.error("Compilation error evaluating constraints for class [" + className + "]: " + e.getMessage(), e);
                return null;
            }
            catch (InstantiationException e) {
                LOG.error("Instantiation error evaluating constraints for class [" + className + "]: " + e.getMessage(), e);
                return null;
            }
            catch (IllegalAccessException e) {
                LOG.error("Illegal access error evaluating constraints for class [" + className + "]: " + e.getMessage(), e);
                return null;
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    protected void applyDefaultConstraints(String propertyName, GrailsDomainClassProperty p,
            Constrained cp, Map<String, Object> defaultConstraints) {

        if (defaultConstraints != null && !defaultConstraints.isEmpty()) {
            if (defaultConstraints.containsKey("*")) {
                final Object o = defaultConstraints.get("*");
                if (o instanceof Map) {
                    Map<String, Object> globalConstraints = (Map<String, Object>)o;
                    applyMapOfConstraints(globalConstraints, propertyName, p, cp);
                }
            }
        }

        if (canApplyNullableConstraint(propertyName, p, cp)) {
            applyDefaultNullableConstraint(p, cp);
        }
    }

    protected void applyDefaultNullableConstraint(GrailsDomainClassProperty p, Constrained cp) {
        applyDefaultNullableConstraint(cp, false);
    }

    protected void applyDefaultNullableConstraint(Constrained cp, boolean defaultNullable) {
        boolean isCollection = Collection.class.isAssignableFrom(cp.getPropertyType()) || Map.class.isAssignableFrom(cp.getPropertyType());
        cp.applyConstraint(ConstrainedProperty.NULLABLE_CONSTRAINT, isCollection || defaultNullable);
    }

    protected boolean canApplyNullableConstraint(String propertyName, GrailsDomainClassProperty property, Constrained constrained) {
        if (property == null || property.getType() == null) return false;

        final GrailsDomainClass domainClass = property.getDomainClass();
        // only apply default nullable to Groovy entities not legacy Java ones
        if (!GroovyObject.class.isAssignableFrom(domainClass.getClazz())) return false;

        final GrailsDomainClassProperty versionProperty = domainClass.getVersion();
        final boolean isVersion = versionProperty != null && versionProperty.equals(property);
        return !constrained.hasAppliedConstraint(ConstrainedProperty.NULLABLE_CONSTRAINT) &&
            isConstrainableProperty(property, propertyName) && !property.isIdentity() && !isVersion && !property.isDerived();
    }

    protected void applyMapOfConstraints(Map<String, Object> constraints, String propertyName, GrailsDomainClassProperty p, Constrained cp) {
        for (Map.Entry<String, Object> entry : constraints.entrySet()) {
            String constraintName = entry.getKey();
            Object constrainingValue = entry.getValue();
            if (!cp.hasAppliedConstraint(constraintName) && cp.supportsContraint(constraintName)) {
                if (ConstrainedProperty.NULLABLE_CONSTRAINT.equals(constraintName)) {
                    if (isConstrainableProperty(p,propertyName)) {
                        cp.applyConstraint(constraintName, constrainingValue);
                    }
                }
                else {
                    cp.applyConstraint(constraintName,constrainingValue);
                }
            }
        }
    }

    protected boolean isConstrainableProperty(GrailsDomainClassProperty p, String propertyName) {
        return !propertyName.equals(GrailsDomainClassProperty.DATE_CREATED) &&
               !propertyName.equals(GrailsDomainClassProperty.LAST_UPDATED) &&
               !((p.isOneToOne() || p.isManyToOne()) && p.isCircular());
    }

    public Map<String, Constrained> evaluate(Object object, GrailsDomainClassProperty[] properties) {
        return evaluateConstraints(object.getClass(), properties);
    }

    public Map<String, Constrained> evaluate(Class<?> cls, GrailsDomainClassProperty[] properties) {
        return evaluateConstraints(cls, properties);
    }
}
TOP

Related Classes of org.grails.validation.DefaultConstraintEvaluator

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.