24
24
import java .util .Optional ;
25
25
import java .util .Stack ;
26
26
27
+ import graphql .schema .DataFetchingEnvironment ;
28
+
27
29
import org .springframework .beans .BeanUtils ;
28
30
import org .springframework .beans .MutablePropertyValues ;
29
31
import org .springframework .beans .SimpleTypeConverter ;
30
- import org .springframework .beans .TypeConverter ;
31
32
import org .springframework .core .CollectionFactory ;
32
33
import org .springframework .core .MethodParameter ;
34
+ import org .springframework .core .ResolvableType ;
33
35
import org .springframework .core .convert .ConversionService ;
34
- import org .springframework .core .convert .TypeDescriptor ;
35
36
import org .springframework .lang .Nullable ;
36
37
import org .springframework .util .Assert ;
37
38
import org .springframework .validation .DataBinder ;
@@ -56,27 +57,108 @@ public GraphQlArgumentInitializer(@Nullable ConversionService conversionService)
56
57
57
58
58
59
/**
59
- * Return the underlying {@link DataBinder}.
60
+ * Initialize an Object of the given {@code targetType}, either from a named
61
+ * {@link DataFetchingEnvironment#getArgument(String) argument value}, or from all
62
+ * {@link DataFetchingEnvironment#getArguments() values} as the source.
63
+ * @param environment the environment with the argument values
64
+ * @param name optionally, the name of an argument to initialize from,
65
+ * or if {@code null}, the full map of arguments is used.
66
+ * @param targetType the type of Object to initialize
67
+ * @return the initialized Object, or {@code null}
60
68
*/
61
- public TypeConverter getTypeConverter () {
62
- return this .typeConverter ;
69
+ @ Nullable
70
+ @ SuppressWarnings ("unchecked" )
71
+ public Object initializeArgument (
72
+ DataFetchingEnvironment environment , @ Nullable String name , ResolvableType targetType ) {
73
+
74
+ Object sourceValue = (name != null ? environment .getArgument (name ) : environment .getArguments ());
75
+
76
+ if (sourceValue == null ) {
77
+ return wrapAsOptionalIfNecessary (null , targetType );
78
+ }
79
+
80
+ Class <?> targetClass = targetType .resolve ();
81
+ Assert .notNull (targetClass , "Could not determine target type from " + targetType );
82
+
83
+ // From Collection
84
+
85
+ if (CollectionFactory .isApproximableCollectionType (sourceValue .getClass ())) {
86
+ Assert .isAssignable (Collection .class , targetClass ,
87
+ "Argument '" + name + "' is a Collection while method parameter is " + targetClass .getName ());
88
+ Class <?> elementType = targetType .asCollection ().getGeneric (0 ).resolve ();
89
+ Assert .notNull (elementType , "Could not determine element type for " + targetType );
90
+ return initializeFromCollection ((Collection <Object >) sourceValue , elementType );
91
+ }
92
+
93
+ if (targetClass == Optional .class ) {
94
+ targetClass = targetType .getNested (2 ).resolve ();
95
+ Assert .notNull (targetClass , "Could not determine Optional<T> type from " + targetType );
96
+ }
97
+
98
+ // From Map
99
+
100
+ if (sourceValue instanceof Map ) {
101
+ Object target = initializeFromMap ((Map <String , Object >) sourceValue , targetClass );
102
+ return wrapAsOptionalIfNecessary (target , targetType );
103
+ }
104
+
105
+ // From Scalar
106
+
107
+ if (targetClass .isInstance (sourceValue )) {
108
+ return wrapAsOptionalIfNecessary (sourceValue , targetType );
109
+ }
110
+
111
+ Object target = this .typeConverter .convertIfNecessary (sourceValue , targetClass );
112
+ if (target == null ) {
113
+ throw new IllegalStateException ("Cannot convert argument value " +
114
+ "type [" + sourceValue .getClass ().getName () + "] to method parameter " +
115
+ "type [" + targetClass .getName () + "]." );
116
+ }
117
+
118
+ return wrapAsOptionalIfNecessary (target , targetType );
63
119
}
64
120
121
+ @ Nullable
122
+ private Object wrapAsOptionalIfNecessary (@ Nullable Object value , ResolvableType type ) {
123
+ return (type .resolve (Object .class ).equals (Optional .class ) ? Optional .ofNullable (value ) : value );
124
+ }
125
+
126
+ /**
127
+ * Instantiate a collection of {@code elementType} using the given {@code values}.
128
+ * <p>This will instantiate a new Collection of the closest type possible
129
+ * from the one provided as an argument.
130
+ *
131
+ * @param <T> the type of Collection elements
132
+ * @param values the collection of values to bind and instantiate
133
+ * @param elementClass the type of elements in the given Collection
134
+ * @return the instantiated and populated Collection.
135
+ * @throws IllegalStateException if there is no suitable constructor.
136
+ */
137
+ @ SuppressWarnings ("unchecked" )
138
+ private <T > Collection <T > initializeFromCollection (Collection <Object > values , Class <T > elementClass ) {
139
+ Collection <T > collection = CollectionFactory .createApproximateCollection (values , values .size ());
140
+ for (Object item : values ) {
141
+ if (elementClass .isAssignableFrom (item .getClass ())) {
142
+ collection .add ((T ) item );
143
+ }
144
+ else if (item instanceof Map ) {
145
+ collection .add ((T ) this .initializeFromMap ((Map <String , Object >) item , elementClass ));
146
+ }
147
+ else {
148
+ collection .add (this .typeConverter .convertIfNecessary (item , elementClass ));
149
+ }
150
+ }
151
+ return collection ;
152
+ }
65
153
66
154
/**
67
155
* Instantiate an Object of the given target type and bind
68
156
* {@link graphql.schema.DataFetchingEnvironment} argument values to it.
69
- * This considers using the default constructor or a primary constructor,
70
- * if available.
71
- *
72
- * @param arguments the data fetching environment arguments
73
- * @param targetType the type of the argument to instantiate
74
- * @param <T> the type of the input argument
75
- * @return the instantiated and populated input argument.
157
+ * This considers the default constructor or a primary constructor, if available.
76
158
* @throws IllegalStateException if there is no suitable constructor.
77
159
*/
78
160
@ SuppressWarnings ("unchecked" )
79
- public < T > T initializeFromMap (Map <String , Object > arguments , Class <T > targetType ) {
161
+ private Object initializeFromMap (Map <String , Object > arguments , Class <? > targetType ) {
80
162
Object target ;
81
163
Constructor <?> ctor = BeanUtils .getResolvableConstructor (targetType );
82
164
@@ -85,7 +167,7 @@ public <T> T initializeFromMap(Map<String, Object> arguments, Class<T> targetTyp
85
167
target = BeanUtils .instantiateClass (ctor );
86
168
DataBinder dataBinder = new DataBinder (target );
87
169
dataBinder .bind (propertyValues );
88
- return ( T ) target ;
170
+ return target ;
89
171
}
90
172
91
173
// Data class constructor
@@ -96,56 +178,25 @@ public <T> T initializeFromMap(Map<String, Object> arguments, Class<T> targetTyp
96
178
for (int i = 0 ; i < paramNames .length ; i ++) {
97
179
String paramName = paramNames [i ];
98
180
Object value = arguments .get (paramName );
99
- MethodParameter methodParam = new MethodParameter (ctor , i );
100
- if (value == null && methodParam .isOptional ()) {
101
- args [i ] = (methodParam .getParameterType () == Optional .class ? Optional .empty () : null );
181
+ MethodParameter methodParameter = new MethodParameter (ctor , i );
182
+ if (value == null && methodParameter .isOptional ()) {
183
+ args [i ] = (methodParameter .getParameterType () == Optional .class ? Optional .empty () : null );
102
184
}
103
185
else if (value != null && CollectionFactory .isApproximableCollectionType (value .getClass ())) {
104
- TypeDescriptor typeDescriptor = new TypeDescriptor (methodParam );
105
- Class <?> elementType = typeDescriptor .getElementTypeDescriptor ().getType ();
186
+ ResolvableType resolvableType = ResolvableType .forMethodParameter (methodParameter );
187
+ Class <?> elementType = resolvableType .asCollection ().getGeneric (0 ).resolve ();
188
+ Assert .notNull (elementType , "Cannot determine element type for " + resolvableType );
106
189
args [i ] = initializeFromCollection ((Collection <Object >) value , elementType );
107
190
}
108
191
else if (value instanceof Map ) {
109
- args [i ] = this .initializeFromMap ((Map <String , Object >) value , methodParam .getParameterType ());
192
+ args [i ] = this .initializeFromMap ((Map <String , Object >) value , methodParameter .getParameterType ());
110
193
}
111
194
else {
112
- args [i ] = this .typeConverter .convertIfNecessary (value , paramTypes [i ], methodParam );
195
+ args [i ] = this .typeConverter .convertIfNecessary (value , paramTypes [i ], methodParameter );
113
196
}
114
197
}
115
198
116
- return (T ) BeanUtils .instantiateClass (ctor , args );
117
- }
118
-
119
- /**
120
- * Instantiate a collection of {@code elementType} using the given {@code values}.
121
- * <p>This will instantiate a new Collection of the closest type possible
122
- * from the one provided as an argument.
123
- *
124
- * @param <T> the type of Collection elements
125
- * @param values the collection of values to bind and instantiate
126
- * @param elementType the type of elements in the given Collection
127
- * @return the instantiated and populated Collection.
128
- * @throws IllegalStateException if there is no suitable constructor.
129
- */
130
- @ SuppressWarnings ("unchecked" )
131
- public <T > Collection <T > initializeFromCollection (Collection <Object > values , Class <T > elementType ) {
132
- Assert .state (CollectionFactory .isApproximableCollectionType (values .getClass ()),
133
- () -> "Cannot instantiate Collection for type " + values .getClass ());
134
- Collection <T > instances = CollectionFactory .createApproximateCollection (values , values .size ());
135
- values .forEach (item -> {
136
- T value ;
137
- if (elementType .isAssignableFrom (item .getClass ())) {
138
- value = (T ) item ;
139
- }
140
- else if (item instanceof Map ) {
141
- value = this .initializeFromMap ((Map <String , Object >)item , elementType );
142
- }
143
- else {
144
- value = this .typeConverter .convertIfNecessary (item , elementType );
145
- }
146
- instances .add (value );
147
- });
148
- return instances ;
199
+ return BeanUtils .instantiateClass (ctor , args );
149
200
}
150
201
151
202
/**
0 commit comments