Skip to content

Commit 0552a0e

Browse files
committed
Merge branch '2.3.x' of github.com:grails/grails-core into 2.3.x
2 parents 1da2887 + 7186191 commit 0552a0e

File tree

6 files changed

+193
-31
lines changed

6 files changed

+193
-31
lines changed

grails-plugin-controllers/src/main/groovy/org/codehaus/groovy/grails/compiler/web/ControllerActionTransformer.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,12 @@ public void performInjectionOnAnnotatedClass(SourceUnit source, ClassNode classN
206206

207207
private boolean isExceptionHandlingMethod(MethodNode methodNode) {
208208
boolean isExceptionHandler = false;
209-
Parameter[] parameters = methodNode.getParameters();
210-
if(parameters.length == 1) {
211-
ClassNode parameterTypeClassNode = parameters[0].getType();
212-
isExceptionHandler = parameterTypeClassNode.isDerivedFrom(new ClassNode(Exception.class));
209+
if(!methodNode.isPrivate()) {
210+
Parameter[] parameters = methodNode.getParameters();
211+
if(parameters.length == 1) {
212+
ClassNode parameterTypeClassNode = parameters[0].getType();
213+
isExceptionHandler = parameterTypeClassNode.isDerivedFrom(new ClassNode(Exception.class));
214+
}
213215
}
214216
return isExceptionHandler;
215217
}

grails-plugin-validation/src/main/groovy/org/codehaus/groovy/grails/web/plugins/support/ValidationSupport.groovy

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,11 @@ class ValidationSupport {
5656
}
5757
for (prop in constraints.values()) {
5858
if (fieldsToValidate == null || fieldsToValidate.contains(prop.propertyName)) {
59-
prop.messageSource = messageSource
60-
prop.validate(object, object.getProperty(prop.propertyName), localErrors)
59+
def fieldError = originalErrors.getFieldError(prop.propertyName)
60+
if(fieldError == null || !fieldError.bindingFailure) {
61+
prop.messageSource = messageSource
62+
prop.validate(object, object.getProperty(prop.propertyName), localErrors)
63+
}
6164
}
6265
}
6366
object.errors = localErrors
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package org.codehaus.groovy.grails.orm
2+
3+
import grails.persistence.Entity
4+
import grails.test.mixin.Mock
5+
import grails.test.mixin.TestMixin
6+
import grails.test.mixin.domain.DomainClassUnitTestMixin
7+
8+
import org.codehaus.groovy.grails.web.binding.GrailsWebDataBinder
9+
10+
import spock.lang.Issue
11+
import spock.lang.Specification
12+
13+
@TestMixin(DomainClassUnitTestMixin)
14+
@Mock([Writer, Book])
15+
class GrailsWebDataBinderBindingXmlSpec extends Specification {
16+
17+
@Issue('GRAILS-10868')
18+
void 'Test binding a single XML child element to a List'() {
19+
given:
20+
def binder = new GrailsWebDataBinder(grailsApplication)
21+
def writer = new Writer(name: 'Writer One')
22+
23+
when:
24+
def xml = new XmlSlurper().parseText("""
25+
<writer>
26+
<name>Writer One</name>
27+
<books>
28+
<book><title>Book One</title><publisher>Publisher One</publisher></book>
29+
</books>
30+
</writer>
31+
""")
32+
binder.bind writer, xml
33+
34+
then:
35+
writer.name == 'Writer One'
36+
writer.books.size() == 1
37+
writer.books[0].title == 'Book One'
38+
writer.books[0].publisher == 'Publisher One'
39+
}
40+
41+
@Issue('GRAILS-10868')
42+
void 'Test adding an existing element to a List by id'() {
43+
given:
44+
def binder = new GrailsWebDataBinder(grailsApplication)
45+
def writer = new Writer(name: 'Writer One')
46+
def originalBook = new Book(title: 'Book One', publisher: 'Publisher One')
47+
48+
when:
49+
originalBook = originalBook.save()
50+
51+
then:
52+
originalBook
53+
54+
when:
55+
def xml = new XmlSlurper().parseText("""
56+
<writer>
57+
<name>Writer Two</name>
58+
<books>
59+
<book><id>${originalBook.id}</id><title>Updated Book One</title></book>
60+
<book><id>2</id><title>Book Two</title><publisher>Publisher Two</publisher></book>
61+
</books>
62+
</writer>
63+
""")
64+
binder.bind writer, xml
65+
66+
then:
67+
writer.name == 'Writer Two'
68+
writer.books.size() == 2
69+
writer.books[0].title == 'Updated Book One'
70+
writer.books[0].publisher == 'Publisher One'
71+
writer.books[1].publisher == 'Publisher Two'
72+
writer.books[1].title == 'Book Two'
73+
}
74+
}
75+
76+
@Entity
77+
class Writer {
78+
String name
79+
List books
80+
static hasMany = [books: Book]
81+
}
82+
83+
@Entity
84+
class Book {
85+
String publisher
86+
String title
87+
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package grails.validation
22

33
import grails.test.mixin.TestMixin
44
import grails.test.mixin.support.GrailsUnitTestMixin
5+
6+
import org.springframework.validation.FieldError
7+
8+
import spock.lang.Issue
59
import spock.lang.Specification
610

711
@TestMixin(GrailsUnitTestMixin)
@@ -77,6 +81,31 @@ class ValidateableSpec extends Specification {
7781
!validateable.hasErrors()
7882
validateable.errors.errorCount == 0
7983
}
84+
85+
@Issue('GRAILS-10871')
86+
void 'Test that binding failures are retained during validation and that the corresponding property is not validated'() {
87+
given:
88+
def validateable = new MyValidateable()
89+
90+
when:
91+
def fieldError = new FieldError(MyValidateable.name, 'age', 'forty two', true, null, null, null)
92+
validateable.errors.addError fieldError
93+
94+
then:
95+
validateable.hasErrors()
96+
validateable.errors.errorCount == 1
97+
validateable.errors.getFieldError('age').rejectedValue == 'forty two'
98+
99+
when:
100+
validateable.name = 'lower case'
101+
102+
then:
103+
!validateable.validate()
104+
validateable.hasErrors()
105+
validateable.errors.errorCount == 2
106+
validateable.errors.getFieldError('age').rejectedValue == 'forty two'
107+
validateable.errors.getFieldError('name').rejectedValue == 'lower case'
108+
}
80109
}
81110

82111
@Validateable

grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/web/controllers/ControllerExceptionHandlerSpec.groovy

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import grails.test.mixin.TestFor
66
import java.sql.BatchUpdateException
77
import java.sql.SQLException
88

9+
import javax.xml.soap.SOAPException
10+
911
import spock.lang.Issue
1012
import spock.lang.Specification
1113

@@ -93,6 +95,24 @@ class ControllerExceptionHandlerSpec extends Specification {
9395
model.problemDescription == 'A Number Was Invalid'
9496
}
9597

98+
void 'Test throwing an exception that does not have a handler'() {
99+
when:
100+
params.exceptionToThrow = 'javax.xml.soap.SOAPException'
101+
def model = controller.testActionWithNonCommandObjectParameter()
102+
103+
then:
104+
thrown SOAPException
105+
}
106+
107+
void 'Test throwing an exception that does not have a handler and does match a private method in the parent controller'() {
108+
when: 'a controller action throws an exception which matches an inherited private method which should not be treated as an exception handler'
109+
params.exceptionToThrow = 'java.io.IOException'
110+
def model = controller.testActionWithNonCommandObjectParameter()
111+
112+
then: 'the method is ignored and the exception is thrown'
113+
thrown IOException
114+
}
115+
96116
void 'Test action throws an exception that does not have a corresponding error handler'() {
97117
when:
98118
params.exceptionToThrow = 'java.lang.UnsupportedOperationException'
@@ -132,7 +152,14 @@ class ControllerExceptionHandlerSpec extends Specification {
132152
}
133153

134154
@Artefact('Controller')
135-
class ErrorHandlersController {
155+
abstract class SomeAbstractController {
156+
157+
private somePrivateMethodWhichIsNotAnExceptionHandler(IOException e) {
158+
}
159+
}
160+
161+
@Artefact('Controller')
162+
class ErrorHandlersController extends SomeAbstractController {
136163

137164
def testAction() {
138165
def exceptionClass = Class.forName(params.exceptionToThrow)

grails-web/src/main/groovy/org/codehaus/groovy/grails/web/binding/GrailsWebDataBinder.groovy

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -279,33 +279,47 @@ class GrailsWebDataBinder extends SimpleDataBinder {
279279
}
280280
} else if(Collection.isAssignableFrom(metaProperty.type)) {
281281
def referencedType = getReferencedTypeForCollection(propName, obj)
282-
if(referencedType && isDomainClass(referencedType) && val instanceof List) {
283-
needsBinding = false
284-
initializeCollection obj, metaProperty.name, metaProperty.type
285-
def itemsWhichNeedBinding = []
286-
val.each { item ->
287-
def persistentInstance
288-
if(item instanceof Map || item instanceof DataBindingSource) {
289-
def idValue = getIdentifierValueFrom(item)
290-
if(idValue != null) {
291-
persistentInstance = getPersistentInstance(referencedType, idValue)
292-
if(persistentInstance != null) {
293-
bind persistentInstance, new SimpleMapDataBindingSource(item), listener
294-
itemsWhichNeedBinding << persistentInstance
295-
}
296-
}
297-
}
298-
if(persistentInstance == null) {
299-
itemsWhichNeedBinding << item
282+
if(referencedType && isDomainClass(referencedType)) {
283+
def listValue
284+
if(val instanceof List) {
285+
listValue = (List)val
286+
} else if(val instanceof GPathResultMap && val.size() == 1) {
287+
def mapValue = (GPathResultMap)val
288+
def valueInMap = mapValue[mapValue.keySet()[0]]
289+
if(valueInMap instanceof List) {
290+
listValue = (List)valueInMap
291+
} else if(valueInMap instanceof GPathResultMap) {
292+
listValue = [valueInMap]
300293
}
301294
}
302-
if(itemsWhichNeedBinding) {
303-
def coll = obj[metaProperty.name]
304-
if(coll instanceof Collection) {
305-
coll.clear()
295+
if(listValue != null) {
296+
needsBinding = false
297+
initializeCollection obj, metaProperty.name, metaProperty.type
298+
def itemsWhichNeedBinding = []
299+
listValue.each { item ->
300+
def persistentInstance
301+
if(item instanceof Map || item instanceof DataBindingSource) {
302+
def idValue = getIdentifierValueFrom(item)
303+
if(idValue != null) {
304+
persistentInstance = getPersistentInstance(referencedType, idValue)
305+
if(persistentInstance != null) {
306+
bind persistentInstance, new SimpleMapDataBindingSource(item), listener
307+
itemsWhichNeedBinding << persistentInstance
308+
}
309+
}
310+
}
311+
if(persistentInstance == null) {
312+
itemsWhichNeedBinding << item
313+
}
306314
}
307-
for(item in itemsWhichNeedBinding) {
308-
addElementToCollection obj, metaProperty.name, metaProperty.type, item, false
315+
if(itemsWhichNeedBinding) {
316+
def coll = obj[metaProperty.name]
317+
if(coll instanceof Collection) {
318+
coll.clear()
319+
}
320+
for(item in itemsWhichNeedBinding) {
321+
addElementToCollection obj, metaProperty.name, metaProperty.type, item, false
322+
}
309323
}
310324
}
311325
}

0 commit comments

Comments
 (0)