Skip to content

Commit 62bc6c1

Browse files
authored
Merge pull request #10666 from tkvw/feature-databinding-initializer
Created BindInitializer annotation
2 parents 8cde2c5 + f3c3375 commit 62bc6c1

File tree

5 files changed

+160
-12
lines changed

5 files changed

+160
-12
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package grails.databinding;
17+
18+
import java.lang.annotation.Retention;
19+
import java.lang.annotation.RetentionPolicy;
20+
21+
/**
22+
* This annotation may be applied to a a field to
23+
* customize initialization of object properties in the data binding process.
24+
*
25+
* When the annotation is applied to a field, the value assigned to the
26+
* annotation should be a Closure which accepts 1 parameter. The
27+
* parameter is the object that data binding is being applied to.
28+
* The value returned by the Closure will be bound to the field. The
29+
* following code demonstrates using this technique to bind a contact
30+
* to user with the same account as the user.
31+
*
32+
<pre>
33+
class Contact{
34+
Account account
35+
String firstName
36+
}
37+
class User {
38+
&#064;BindInitializer({
39+
obj -> new Contact(account:obj.account)
40+
})
41+
Contact contact
42+
Account account
43+
}
44+
</pre>
45+
46+
*
47+
* @since 3.2.11
48+
* @see BindingHelper
49+
* @see DataBindingSource
50+
*/
51+
@Retention(RetentionPolicy.RUNTIME)
52+
public @interface BindInitializer {
53+
Class<?> value();
54+
}

grails-databinding/src/main/groovy/grails/databinding/SimpleDataBinder.groovy

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,22 @@ package grails.databinding
1818
import grails.databinding.converters.FormattedValueConverter
1919
import grails.databinding.converters.ValueConverter
2020
import grails.databinding.events.DataBindingListener
21+
import grails.databinding.initializers.ValueInitializer
2122
import groovy.transform.CompileStatic
2223
import groovy.transform.TypeCheckingMode
2324
import groovy.util.slurpersupport.GPathResult
25+
import org.grails.databinding.ClosureValueConverter
26+
import org.grails.databinding.ClosureValueInitializer
27+
import org.grails.databinding.IndexedPropertyReferenceDescriptor
28+
import org.grails.databinding.converters.*
29+
import org.grails.databinding.errors.SimpleBindingError
30+
import org.grails.databinding.xml.GPathResultMap
2431

2532
import java.lang.annotation.Annotation
2633
import java.lang.reflect.Array
2734
import java.lang.reflect.Field
2835
import java.lang.reflect.ParameterizedType
2936

30-
import grails.databinding.BindUsing
31-
import org.grails.databinding.ClosureValueConverter
32-
import org.grails.databinding.IndexedPropertyReferenceDescriptor
33-
import org.grails.databinding.converters.ConversionService
34-
import org.grails.databinding.converters.FormattedDateValueConverter
35-
import org.grails.databinding.converters.StructuredCalendarBindingEditor
36-
import org.grails.databinding.converters.StructuredDateBindingEditor
37-
import org.grails.databinding.converters.StructuredSqlDateBindingEditor
38-
import org.grails.databinding.errors.SimpleBindingError
39-
import org.grails.databinding.xml.GPathResultMap
40-
4137
/**
4238
* A data binder that will bind nested Maps to an object.
4339
*
@@ -713,9 +709,51 @@ class SimpleDataBinder implements DataBinder {
713709
}
714710

715711
protected initializeProperty(obj, String propName, Class propertyType, DataBindingSource source) {
716-
obj[propName] = propertyType.newInstance()
712+
def initializer = getPropertyInitializer(obj,propName)
713+
if(initializer){
714+
obj[propName] = initializer.initialize()
715+
}
716+
else{
717+
obj[propName] = propertyType.newInstance()
718+
}
719+
}
720+
721+
protected ValueInitializer getPropertyInitializer(obj, String propName){
722+
def initializer = getValueInitializerForField obj, propName
723+
initializer
717724
}
718725

726+
protected ValueInitializer getValueInitializerForField(obj, String propName) {
727+
def initializer
728+
try {
729+
def field = getField(obj.getClass(), propName)
730+
if (field) {
731+
def annotation = field.getAnnotation(BindInitializer)
732+
if (annotation) {
733+
def valueClass = getValueOfBindInitializer(annotation)
734+
if (Closure.isAssignableFrom(valueClass)) {
735+
Closure closure = (Closure)valueClass.newInstance(null, null)
736+
initializer = new ClosureValueInitializer(initializerClosure: closure.curry(obj), targetType: field.type)
737+
}
738+
}
739+
}
740+
} catch (Exception e) {
741+
}
742+
initializer
743+
}
744+
/**
745+
* @param annotation An instance of grails.databinding.BindInitializer
746+
* @return the value Class of the annotation
747+
*/
748+
protected Class getValueOfBindInitializer(Annotation annotation) {
749+
assert annotation instanceof BindInitializer
750+
def value
751+
if(annotation instanceof BindInitializer) {
752+
value = ((BindInitializer)annotation).value()
753+
}
754+
value
755+
}
756+
719757
protected convert(Class typeToConvertTo, value) {
720758
if (value == null || typeToConvertTo.isAssignableFrom(value?.getClass())) {
721759
return value
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package grails.databinding.initializers;
2+
3+
4+
public interface ValueInitializer {
5+
Object initialize();
6+
Class<?> getTargetType();
7+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.grails.databinding
2+
3+
import grails.databinding.initializers.ValueInitializer
4+
import groovy.transform.CompileStatic
5+
6+
@CompileStatic
7+
class ClosureValueInitializer implements ValueInitializer {
8+
9+
Closure initializerClosure
10+
Class targetType
11+
12+
13+
@Override
14+
Object initialize() {
15+
initializerClosure.call()
16+
}
17+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package grails.databinding
2+
3+
import spock.lang.Specification
4+
5+
6+
class BindInitializerSpec extends Specification {
7+
8+
void 'Test BindInitializer for specific property'() {
9+
given:
10+
def binder = new SimpleDataBinder()
11+
def obj = new ClassWithBindInitializerOnProperty()
12+
when:
13+
binder.bind(obj, new SimpleMapDataBindingSource(['association': [valueBound:'valueBound']]))
14+
15+
then:
16+
obj.association.valueBound == 'valueBound'
17+
obj.association.valueInitialized == 'valueInitialized'
18+
}
19+
20+
21+
static class ReferencedClass{
22+
String valueInitialized
23+
String valueBound
24+
}
25+
class ClassWithBindInitializerOnProperty {
26+
@BindInitializer({
27+
obj ->
28+
new ReferencedClass(valueInitialized:'valueInitialized')
29+
})
30+
ReferencedClass association
31+
}
32+
}

0 commit comments

Comments
 (0)