1 /* Copyright 2004-2005 the original author or authors.
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
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
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
;
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
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
);
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
);
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() {
72 * @param messageSource The messageSource to set.
74 public void setMessageSource(MessageSource messageSource
) {
75 this.messageSource
= messageSource
;
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
);
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
;
168 constrainedPropertyName
= propertyName
.substring(i
+1,propertyName
.length());
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();
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
);
227 errors
.setNestedPath(nestedPath
);