Skip to content

Commit c690aa3

Browse files
committed
Properties with only getters shouldn't be constrained. Fixes #9754
1 parent b651a1d commit c690aa3

File tree

2 files changed

+32
-33
lines changed

2 files changed

+32
-33
lines changed

grails-test-suite-uber/src/test/groovy/grails/validation/DomainClassValidationSpec.groovy

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,19 +137,17 @@ class DomainClassValidationSpec extends Specification {
137137
def constraints = getAssociatedDomainClassFromApplication(new MyDomainClass()).getConstrainedProperties()
138138

139139
then:
140-
constraints.size() == 5
140+
constraints.size() == 4
141141
constraints.containsKey 'name'
142142
constraints.containsKey 'town'
143143
constraints.containsKey 'age'
144144
constraints.containsKey 'someProperty'
145-
constraints.containsKey 'twiceAge' // only getter method
146145

147146
and:
148147
constraints.name.appliedConstraints.size() == 2
149148
constraints.age.appliedConstraints.size() == 2
150149
constraints.town.appliedConstraints.size() == 1
151150
constraints.someProperty.appliedConstraints.size() == 1
152-
constraints.twiceAge.appliedConstraints.size() == 1
153151

154152
and:
155153
constraints.name.hasAppliedConstraint 'matches'
@@ -158,14 +156,12 @@ class DomainClassValidationSpec extends Specification {
158156
constraints.age.hasAppliedConstraint 'nullable'
159157
constraints.town.hasAppliedConstraint 'nullable'
160158
constraints.someProperty.hasAppliedConstraint 'nullable'
161-
constraints.twiceAge.hasAppliedConstraint 'nullable'
162159

163160
and: 'implicit defaultNullable is nullable:false'
164161
!constraints.name.nullable
165162
!constraints.age.nullable
166163
!constraints.town.nullable
167164
!constraints.someProperty.nullable
168-
!constraints.twiceAge.nullable
169165
}
170166

171167
@Ignore('defaultNullable is not supported yet')
@@ -216,9 +212,8 @@ class DomainClassValidationSpec extends Specification {
216212
expect: 'validation is executed and public properties/getters are marked nullable in errors'
217213
!obj.validate()
218214
obj.hasErrors()
219-
obj.errors.errorCount == 2
215+
obj.errors.errorCount == 1
220216
obj.errors['town']?.code == 'nullable' // public property
221-
obj.errors['name']?.code == 'nullable' // only public getter method
222217
!obj.errors['surname'] // only protected getter method
223218
!obj.errors['email'] // only private getter method
224219
}
@@ -228,8 +223,7 @@ class DomainClassValidationSpec extends Specification {
228223
Map constraints = getAssociatedDomainClassFromApplication(new DomainClassGetters()).getConstrainedProperties()
229224

230225
then: 'only public properties and public getters should be considered domainClass properties by default'
231-
constraints.size() == 5
232-
constraints.name
226+
constraints.size() == 4
233227
constraints.town
234228
constraints.bar
235229
constraints.qux

grails-validation/src/main/groovy/org/grails/validation/DefaultConstraintEvaluator.java

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import grails.core.GrailsDomainClass;
1919
import grails.core.GrailsDomainClassProperty;
2020
import grails.io.IOUtils;
21+
import grails.persistence.PersistenceMethod;
2122
import grails.util.GrailsClassUtils;
2223
import grails.validation.Constrained;
2324
import grails.validation.ConstrainedProperty;
@@ -34,6 +35,8 @@
3435
import org.grails.core.artefact.DomainClassArtefactHandler;
3536
import org.grails.core.exceptions.GrailsConfigurationException;
3637
import org.grails.core.support.GrailsDomainConfigurationUtil;
38+
import org.springframework.beans.BeanUtils;
39+
import org.springframework.beans.CachedIntrospectionResults;
3740

3841
import java.beans.BeanInfo;
3942
import java.beans.IntrospectionException;
@@ -43,13 +46,7 @@
4346
import java.lang.reflect.Field;
4447
import java.lang.reflect.Method;
4548
import java.lang.reflect.Modifier;
46-
import java.util.ArrayList;
47-
import java.util.Arrays;
48-
import java.util.Collection;
49-
import java.util.Collections;
50-
import java.util.HashMap;
51-
import java.util.List;
52-
import java.util.Map;
49+
import java.util.*;
5350

5451
/**
5552
* Default implementation of the {@link grails.validation.ConstraintsEvaluator} interface.
@@ -156,7 +153,7 @@ protected Map<String, Constrained> evaluateConstraints(Map<String, Constrained>
156153

157154
boolean isDomainClass = DomainClassArtefactHandler.isDomainClass(theClass);
158155
Map<String, GrailsDomainClassProperty> domainClassPropertyMap = indexPropertiesByPropertyName(domainClassProperties);
159-
Map<String, Method> constrainablePropertyMap = getConstrainablePropertyMap(theClass);
156+
Map<String, Method> constrainablePropertyMap = getConstrainablePropertyMap(theClass, isDomainClass);
160157

161158
for (String propertyName : constrainablePropertyMap.keySet()) {
162159
GrailsDomainClassProperty domainClassProperty = domainClassPropertyMap.get(propertyName);
@@ -177,6 +174,10 @@ protected Map<String, Constrained> evaluateConstraints(Map<String, Constrained>
177174
}
178175
}
179176
}
177+
else {
178+
// for domain class, a constraint of a property as only getter method is not supported
179+
continue;
180+
}
180181
}
181182

182183
// complete constraints not defined by user.
@@ -359,38 +360,42 @@ protected void applyDefaultNullableConstraint(Constrained constrained, boolean d
359360
}
360361
}
361362

362-
protected Map<String, Method> getConstrainablePropertyMap(Class theClass) {
363-
List<String> ignoredProperties = new ArrayList<>();
363+
protected Map<String, Method> getConstrainablePropertyMap(Class theClass, boolean isDomainClass) {
364+
Set<String> ignoredProperties = new HashSet<>();
364365
ignoredProperties.add(GrailsDomainClassProperty.CLASS);
365366
ignoredProperties.add(GrailsDomainClassProperty.META_CLASS);
366367
ignoredProperties.add(GrailsDomainClassProperty.ERRORS);
367-
if (DomainClassArtefactHandler.isDomainClass(theClass)) {
368+
369+
if (isDomainClass) {
368370
ignoredProperties.add(GrailsDomainConfigurationUtil.PROPERTIES_PROPERTY);
369371
ignoredProperties.add(GrailsDomainClassProperty.IDENTITY);
370372
ignoredProperties.add(GrailsDomainClassProperty.VERSION);
371373
ignoredProperties.add(GrailsDomainClassProperty.DIRTY_PROPERTY_NAMES);
372374
ignoredProperties.add(GrailsDomainClassProperty.DIRTY);
373375
ignoredProperties.add(GrailsDomainClassProperty.ATTACHED);
374-
}
375-
376-
final Object transients = GrailsClassUtils.getStaticPropertyValue(theClass,
377-
GrailsDomainClassProperty.TRANSIENT);
378-
if(transients instanceof List) {
379-
ignoredProperties.addAll((List) transients);
380-
}
381-
try {
382-
final BeanInfo beanInfo = Introspector.getBeanInfo(theClass);
383-
final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
376+
final Object transients = GrailsClassUtils.getStaticPropertyValue(theClass,
377+
GrailsDomainClassProperty.TRANSIENT);
378+
if(transients instanceof List) {
379+
ignoredProperties.addAll((List) transients);
380+
}
381+
final PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(theClass);
384382
for(PropertyDescriptor descriptor : propertyDescriptors) {
385383
final Method readMethod = descriptor.getReadMethod();
386-
if(readMethod != null && Modifier.isTransient(readMethod.getModifiers())) {
384+
385+
final Method writeMethod = descriptor.getWriteMethod();
386+
if( readMethod == null ) {
387387
ignoredProperties.add(descriptor.getName());
388388
}
389+
else if(writeMethod == null || (Modifier.isTransient(readMethod.getModifiers()))) {
390+
PersistenceMethod annotation = readMethod.getAnnotation(PersistenceMethod.class);
391+
if(annotation == null) {
392+
ignoredProperties.add(descriptor.getName());
393+
}
394+
}
389395
}
390-
} catch (IntrospectionException e) {
391-
LOG.error("An error occurred introspecting properties", e);
392396
}
393397

398+
394399
Field[] declaredFields = theClass.getDeclaredFields();
395400
for(Field field : declaredFields) {
396401
if(Modifier.isTransient(field.getModifiers())) {

0 commit comments

Comments
 (0)