GRAILS-1019: Allowing expressions to be used with the 'disabled' attribute for g...
[grails.git] / src / commons / org / codehaus / groovy / grails / validation / GrailsDomainClassValidator.java
blobe997896468fa4cce9af2e8799f257124de972913
1 /* Copyright 2004-2005 the original author or authors.
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.codehaus.groovy.grails.validation;
17 import groovy.lang.GroovyObject;
18 import org.codehaus.groovy.grails.commons.GrailsDomainClass;
19 import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty;
20 import org.codehaus.groovy.runtime.InvokerHelper;
21 import org.springframework.beans.BeanWrapper;
22 import org.springframework.beans.BeanWrapperImpl;
23 import org.springframework.context.MessageSource;
24 import org.springframework.validation.Errors;
25 import org.springframework.validation.Validator;
27 import java.util.*;
29 /**
30 * A specialised Spring validator that validates a domain class instance using the constraints defined in the
31 * static constraints closure.
34 * @author Graeme Rocher
35 * @since 0.1
37 * Created: 07-Nov-2005
39 public class GrailsDomainClassValidator implements Validator, CascadingValidator {
42 private static final List EMBEDDED_EXCLUDES = new ArrayList() {{
43 add(GrailsDomainClassProperty.IDENTITY);
44 add(GrailsDomainClassProperty.VERSION);
45 }};
47 private Class targetClass;
48 private GrailsDomainClass domainClass;
49 private MessageSource messageSource;
50 private static final String ERRORS_PROPERTY = "errors";
52 public boolean supports(Class clazz) {
53 return this.targetClass.equals( clazz );
57 /**
58 * @param domainClass The domainClass to set.
60 public void setDomainClass(GrailsDomainClass domainClass) {
61 this.domainClass = domainClass;
62 this.domainClass.setValidator(this);
63 this.targetClass = this.domainClass.getClazz();
67 public GrailsDomainClass getDomainClass() {
68 return domainClass;
71 /**
72 * @param messageSource The messageSource to set.
74 public void setMessageSource(MessageSource messageSource) {
75 this.messageSource = messageSource;
78 /**
79 * @see org.codehaus.groovy.grails.validation.CascadingValidator#validate(Object, org.springframework.validation.Errors, boolean)
81 public void validate(Object obj, Errors errors, boolean cascade) {
82 if(!domainClass.getClazz().isInstance(obj))
83 throw new IllegalArgumentException("Argument ["+obj+"] is not an instance of ["+domainClass.getClazz()+"] which this validator is configured for");
85 BeanWrapper bean = new BeanWrapperImpl(obj);
87 Map constrainedProperties = domainClass.getConstrainedProperties();
89 GrailsDomainClassProperty[] persistentProperties = domainClass.getPersistentProperties();
91 for (int i = 0; i < persistentProperties.length; i++) {
92 GrailsDomainClassProperty persistentProperty = persistentProperties[i];
93 String propertyName = persistentProperty.getName();
94 if(constrainedProperties.containsKey(propertyName)) {
95 validatePropertyWithConstraint(propertyName, obj, errors, bean, constrainedProperties);
98 if((persistentProperty.isAssociation() || persistentProperty.isEmbedded()) && cascade) {
99 cascadeToAssociativeProperty(errors, bean, persistentProperty);
103 if(obj instanceof GroovyObject) {
104 ((GroovyObject)obj).setProperty(ERRORS_PROPERTY, errors);
106 else {
107 InvokerHelper.setProperty(obj,ERRORS_PROPERTY,errors);
113 * @see org.springframework.validation.Validator#validate(Object, org.springframework.validation.Errors)
115 public void validate(Object obj, Errors errors) {
116 validate(obj,errors,false);
120 * Cascades validation onto an associative property maybe a one-to-many, one-to-one or many-to-one relationship
122 * @param errors The Errors instnace
123 * @param bean The original bean
124 * @param persistentProperty The associative property
126 protected void cascadeToAssociativeProperty(Errors errors, BeanWrapper bean, GrailsDomainClassProperty persistentProperty) {
127 String propertyName = persistentProperty.getName();
128 if(errors.hasFieldErrors(propertyName)) return;
129 if(persistentProperty.isManyToOne() || persistentProperty.isOneToOne() || persistentProperty.isEmbedded() ) {
130 Object associatedObject = bean.getPropertyValue(propertyName);
131 cascadeValidationToOne(errors, bean,associatedObject, persistentProperty, propertyName);
133 else if(persistentProperty.isOneToMany()) {
134 cascadeValidationToMany(errors, bean, persistentProperty, propertyName);
139 * Cascades validation to a one-to-many type relationship. Normally a collection such as a List or Set each element
140 * in the association will also be validated
142 * @param errors The Errors instance
143 * @param bean The original BeanWrapper
144 * @param persistentProperty An association whose isOneToMeny() method returns true
145 * @param propertyName The name of the property
147 protected void cascadeValidationToMany(Errors errors, BeanWrapper bean, GrailsDomainClassProperty persistentProperty, String propertyName) {
148 Object collection = bean.getPropertyValue(propertyName);
149 if(collection instanceof Collection) {
150 for (Iterator i = ((Collection) collection).iterator(); i.hasNext();) {
151 Object associatedObject = i.next();
152 cascadeValidationToOne(errors, bean,associatedObject, persistentProperty, propertyName);
155 else if(collection instanceof Map) {
156 Map map = (Map)collection;
157 for (Iterator i = map.keySet().iterator(); i.hasNext();) {
158 Object key = i.next();
159 cascadeValidationToOne(errors, bean,map.get(key), persistentProperty, propertyName);
164 private void validatePropertyWithConstraint(String propertyName, Object obj, Errors errors, BeanWrapper bean, Map constrainedProperties) {
165 int i = propertyName.lastIndexOf(".");
166 String constrainedPropertyName;
167 if(i > -1){
168 constrainedPropertyName = propertyName.substring(i+1,propertyName.length());
170 else {
171 constrainedPropertyName = propertyName;
173 ConstrainedProperty c = (ConstrainedProperty)constrainedProperties.get(constrainedPropertyName);
174 c.setMessageSource(this.messageSource);
175 c.validate(obj, bean.getPropertyValue(constrainedPropertyName), errors);
181 * Cascades validation to a one-to-one or many-to-one property
183 * @param errors The Errors instance
184 * @param bean The original BeanWrapper
185 * @param associatedObject The associated object's current value
186 * @param persistentProperty The GrailsDomainClassProperty instance
187 * @param propertyName The name of the property
189 protected void cascadeValidationToOne(Errors errors, BeanWrapper bean, Object associatedObject, GrailsDomainClassProperty persistentProperty, String propertyName) {
191 if(associatedObject != null) {
193 GrailsDomainClass associatedDomainClass = persistentProperty.isEmbedded() ? persistentProperty.getComponent() : persistentProperty.getReferencedDomainClass();
194 if(associatedDomainClass != null && associatedDomainClass.isOwningClass(bean.getWrappedClass())) {
195 GrailsDomainClassProperty otherSide = null;
196 if(persistentProperty.isBidirectional()) {
197 otherSide = persistentProperty.getOtherSide();
200 Map associatedConstraintedProperties = associatedDomainClass.getConstrainedProperties();
202 GrailsDomainClassProperty[] associatedPersistentProperties = associatedDomainClass.getPersistentProperties();
203 String nestedPath = errors.getNestedPath();
204 try {
205 errors.setNestedPath(nestedPath+propertyName);
208 for (int i = 0; i < associatedPersistentProperties.length; i++) {
209 GrailsDomainClassProperty associatedPersistentProperty = associatedPersistentProperties[i];
210 if(associatedPersistentProperty.equals(otherSide)) continue;
211 if(persistentProperty.isEmbedded() && EMBEDDED_EXCLUDES.contains(associatedPersistentProperty.getName())) continue;
214 String associatedPropertyName = associatedPersistentProperty.getName();
215 if(associatedConstraintedProperties.containsKey(associatedPropertyName)) {
217 validatePropertyWithConstraint(errors.getNestedPath() + associatedPropertyName, associatedObject, errors, new BeanWrapperImpl(associatedObject), associatedConstraintedProperties);
220 if(associatedPersistentProperty.isAssociation()) {
221 cascadeToAssociativeProperty(errors, new BeanWrapperImpl(associatedObject), associatedPersistentProperty);
226 finally {
227 errors.setNestedPath(nestedPath);