17
17
18
18
import java .lang .reflect .Method ;
19
19
import java .util .Arrays ;
20
+ import java .util .Collection ;
20
21
import java .util .Collections ;
21
22
import java .util .HashMap ;
22
- import java .util .LinkedHashMap ;
23
23
import java .util .Map ;
24
24
import java .util .stream .Collectors ;
25
25
43
43
import org .springframework .graphql .data .method .HandlerMethod ;
44
44
import org .springframework .graphql .data .method .HandlerMethodArgumentResolver ;
45
45
import org .springframework .graphql .data .method .HandlerMethodArgumentResolverComposite ;
46
- import org .springframework .graphql .data .method .annotation .MutationMapping ;
47
- import org .springframework .graphql .data .method .annotation .QueryMapping ;
46
+ import org .springframework .graphql .data .method .InvocableHandlerMethod ;
48
47
import org .springframework .graphql .data .method .annotation .SchemaMapping ;
49
- import org .springframework .graphql .data .method .annotation .SubscriptionMapping ;
50
48
import org .springframework .graphql .execution .RuntimeWiringConfigurer ;
51
49
import org .springframework .lang .Nullable ;
52
50
import org .springframework .stereotype .Controller ;
@@ -96,6 +94,11 @@ public void setApplicationContext(ApplicationContext applicationContext) {
96
94
this .applicationContext = applicationContext ;
97
95
}
98
96
97
+ protected final ApplicationContext obtainApplicationContext () {
98
+ Assert .state (this .applicationContext != null , "No ApplicationContext" );
99
+ return this .applicationContext ;
100
+ }
101
+
99
102
100
103
@ Override
101
104
public void afterPropertiesSet () {
@@ -115,10 +118,10 @@ public void afterPropertiesSet() {
115
118
116
119
@ Override
117
120
public void configure (RuntimeWiring .Builder builder ) {
118
- Assert .notNull (this .applicationContext , "ApplicationContext is required " );
119
- Assert . notNull ( this . argumentResolvers , "`argumentResolvers` are required" );
120
-
121
- detectHandlerMethods (). forEach (( coordinates , handlerMethod ) -> {
121
+ Assert .state (this .argumentResolvers != null , "`argumentResolvers` is not initialized " );
122
+ findHandlerMethods (). forEach (( info ) -> {
123
+ FieldCoordinates coordinates = info . getCoordinates ();
124
+ HandlerMethod handlerMethod = info . getHandlerMethod ();
122
125
DataFetcher <?> dataFetcher = new AnnotatedDataFetcher (coordinates , handlerMethod , this .argumentResolvers );
123
126
builder .type (coordinates .getTypeName (), typeBuilder ->
124
127
typeBuilder .dataFetcher (coordinates .getFieldName (), dataFetcher ));
@@ -128,143 +131,184 @@ public void configure(RuntimeWiring.Builder builder) {
128
131
/**
129
132
* Scan beans in the ApplicationContext, detect and prepare a map of handler methods.
130
133
*/
131
- private Map <FieldCoordinates , HandlerMethod > detectHandlerMethods () {
132
- Map <FieldCoordinates , HandlerMethod > result = new HashMap <>();
133
- for (String beanName : this .applicationContext .getBeanNamesForType (Object .class )) {
134
+ private Collection <MappingInfo > findHandlerMethods () {
135
+ ApplicationContext context = obtainApplicationContext ();
136
+ Map <FieldCoordinates , MappingInfo > result = new HashMap <>();
137
+ for (String beanName : context .getBeanNamesForType (Object .class )) {
134
138
if (beanName .startsWith (SCOPED_TARGET_NAME_PREFIX )) {
135
139
continue ;
136
140
}
137
141
Class <?> beanType = null ;
138
142
try {
139
- beanType = this . applicationContext .getType (beanName );
143
+ beanType = context .getType (beanName );
140
144
}
141
145
catch (Throwable ex ) {
142
146
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
143
147
if (logger .isTraceEnabled ()) {
144
148
logger .trace ("Could not resolve type for bean '" + beanName + "'" , ex );
145
149
}
146
150
}
147
- if (beanType == null || !isHandler (beanType )) {
151
+ if (beanType == null || !AnnotatedElementUtils . hasAnnotation (beanType , Controller . class )) {
148
152
continue ;
149
153
}
150
- detectHandlerMethodsOnBean (beanName ).forEach ((coordinates , handlerMethod ) -> {
151
- HandlerMethod existing = result .put (coordinates , handlerMethod );
152
- if (existing != null && !existing .equals (handlerMethod )) {
154
+ Class <?> beanClass = context .getType (beanName );
155
+ findHandlerMethods (beanName , beanClass ).forEach ((info ) -> {
156
+ HandlerMethod handlerMethod = info .getHandlerMethod ();
157
+ MappingInfo existing = result .put (info .getCoordinates (), info );
158
+ if (existing != null && !existing .getHandlerMethod ().equals (handlerMethod )) {
153
159
throw new IllegalStateException (
154
160
"Ambiguous mapping. Cannot map '" + handlerMethod .getBean () + "' method \n " +
155
- handlerMethod + "\n to " + coordinates + ": There is already '" +
156
- existing .getBean () + "' bean method\n " + existing + " mapped." );
161
+ handlerMethod + "\n to " + info . getCoordinates () + ": There is already '" +
162
+ existing .getHandlerMethod (). getBean () + "' bean method\n " + existing + " mapped." );
157
163
}
158
164
});
159
165
}
160
- return result ;
166
+ return result . values () ;
161
167
}
162
168
163
- private boolean isHandler (Class <?> beanType ) {
164
- return (AnnotatedElementUtils .hasAnnotation (beanType , Controller .class ) ||
165
- AnnotatedElementUtils .hasAnnotation (beanType , SchemaMapping .class ));
166
- }
167
-
168
- private Map <FieldCoordinates , HandlerMethod > detectHandlerMethodsOnBean (Object handler ) {
169
- Class <?> beanClass = (handler instanceof String ?
170
- this .applicationContext .getType ((String ) handler ) : handler .getClass ());
171
- if (beanClass == null ) {
172
- return Collections .emptyMap ();
169
+ private Collection <MappingInfo > findHandlerMethods (Object handler , @ Nullable Class <?> handlerClass ) {
170
+ if (handlerClass == null ) {
171
+ return Collections .emptyList ();
173
172
}
174
173
175
- Class <?> userClass = ClassUtils .getUserClass (beanClass );
176
- Map <Method , FieldCoordinates > methodsMap =
177
- MethodIntrospector .selectMethods (userClass , (Method method ) -> getCoordinates (method , userClass ));
178
- if (methodsMap .isEmpty ()) {
179
- return Collections .emptyMap ();
180
- }
174
+ Class <?> userClass = ClassUtils .getUserClass (handlerClass );
175
+ Map <Method , MappingInfo > map =
176
+ MethodIntrospector .selectMethods (userClass , (Method method ) -> getMappingInfo (method , handler , userClass ));
181
177
182
- Map <FieldCoordinates , HandlerMethod > result = new LinkedHashMap <>(methodsMap .size ());
183
- for (Map .Entry <Method , FieldCoordinates > entry : methodsMap .entrySet ()) {
184
- Method method = AopUtils .selectInvocableMethod (entry .getKey (), userClass );
185
- HandlerMethod handlerMethod = (handler instanceof String ?
186
- new HandlerMethod ((String ) handler , this .applicationContext .getAutowireCapableBeanFactory (), method ) :
187
- new HandlerMethod (handler , method ));
188
- FieldCoordinates coordinates = entry .getValue ();
189
- coordinates = updateCoordinates (coordinates , handlerMethod );
190
- result .put (coordinates , handlerMethod );
191
- }
178
+ Collection <MappingInfo > mappingInfos = map .values ();
192
179
193
- if (logger .isTraceEnabled ()) {
194
- logger .trace (formatMappings (userClass , result ));
180
+ if (logger .isTraceEnabled () && ! mappingInfos . isEmpty () ) {
181
+ logger .trace (formatMappings (userClass , mappingInfos ));
195
182
}
196
183
197
- return result ;
184
+ return mappingInfos ;
198
185
}
199
186
200
187
@ Nullable
201
- private FieldCoordinates getCoordinates (Method method , Class <?> handlerType ) {
202
- QueryMapping query = AnnotatedElementUtils .findMergedAnnotation (method , QueryMapping .class );
203
- if (query != null ) {
204
- String name = (StringUtils .hasText (query .name ()) ? query .name () : method .getName ());
205
- return FieldCoordinates .coordinates ("Query" , name );
188
+ private MappingInfo getMappingInfo (Method method , Object handler , Class <?> handlerType ) {
189
+ SchemaMapping annotation = AnnotatedElementUtils .findMergedAnnotation (method , SchemaMapping .class );
190
+ if (annotation == null ) {
191
+ return null ;
206
192
}
207
- MutationMapping mutation = AnnotatedElementUtils .findMergedAnnotation (method , MutationMapping .class );
208
- if (mutation != null ) {
209
- String name = (StringUtils .hasText (mutation .name ()) ? mutation .name () : method .getName ());
210
- return FieldCoordinates .coordinates ("Mutation" , name );
211
- }
212
- SubscriptionMapping subscription = AnnotatedElementUtils .findMergedAnnotation (method , SubscriptionMapping .class );
213
- if (subscription != null ) {
214
- String name = (StringUtils .hasText (subscription .name ()) ? subscription .name () : method .getName ());
215
- return FieldCoordinates .coordinates ("Subscription" , name );
216
- }
217
- SchemaMapping schemaMapping = AnnotatedElementUtils .findMergedAnnotation (method , SchemaMapping .class );
218
- if (schemaMapping != null ) {
219
- String typeName = schemaMapping .typeName ();
220
- String field = schemaMapping .field ();
221
- if (!StringUtils .hasText (typeName )) {
222
- schemaMapping = AnnotatedElementUtils .findMergedAnnotation (handlerType , SchemaMapping .class );
223
- if (schemaMapping != null ) {
224
- typeName = schemaMapping .typeName ();
225
- }
193
+
194
+ String typeName = annotation .typeName ();
195
+ String field = (StringUtils .hasText (annotation .field ()) ? annotation .field () : method .getName ());
196
+ HandlerMethod handlerMethod = createHandlerMethod (method , handler , handlerType );
197
+
198
+ if (!StringUtils .hasText (typeName )) {
199
+ SchemaMapping mapping = AnnotatedElementUtils .findMergedAnnotation (handlerType , SchemaMapping .class );
200
+ if (mapping != null ) {
201
+ typeName = annotation .typeName ();
226
202
}
227
- return FieldCoordinates .coordinates (typeName , field );
228
203
}
229
- return null ;
230
- }
231
204
232
- private FieldCoordinates updateCoordinates (FieldCoordinates coordinates , HandlerMethod handlerMethod ) {
233
- boolean hasTypeName = StringUtils .hasText (coordinates .getTypeName ());
234
- boolean hasFieldName = StringUtils .hasText (coordinates .getFieldName ());
235
- if (hasTypeName && hasFieldName ) {
236
- return coordinates ;
237
- }
238
- String typeName = coordinates .getTypeName ();
239
- if (!hasTypeName ) {
205
+ if (!StringUtils .hasText (typeName )) {
206
+ Assert .state (this .argumentResolvers != null , "`argumentResolvers` is not initialized" );
240
207
for (MethodParameter parameter : handlerMethod .getMethodParameters ()) {
241
208
HandlerMethodArgumentResolver resolver = this .argumentResolvers .getArgumentResolver (parameter );
242
209
if (resolver instanceof SourceMethodArgumentResolver ) {
243
210
typeName = parameter .getParameterType ().getSimpleName ();
244
211
break ;
245
212
}
246
213
}
247
- Assert .hasText (typeName ,
248
- "No parentType specified, and a source/container method argument was also not found: " +
249
- handlerMethod .getShortLogMessage ());
250
214
}
251
- return FieldCoordinates .coordinates (typeName ,
252
- (hasFieldName ? coordinates .getFieldName () : handlerMethod .getMethod ().getName ()));
215
+
216
+ Assert .hasText (typeName ,
217
+ "No parentType specified, and a source/parent method argument was also not found: " +
218
+ handlerMethod .getShortLogMessage ());
219
+
220
+ return new MappingInfo (typeName , field , handlerMethod );
253
221
}
254
222
255
- private String formatMappings (Class <?> handlerType , Map <FieldCoordinates , HandlerMethod > mappings ) {
223
+ private HandlerMethod createHandlerMethod (Method method , Object handler , Class <?> handlerType ) {
224
+ Method invocableMethod = AopUtils .selectInvocableMethod (method , handlerType );
225
+ return (handler instanceof String ?
226
+ new HandlerMethod ((String ) handler , obtainApplicationContext ().getAutowireCapableBeanFactory (), invocableMethod ) :
227
+ new HandlerMethod (handler , invocableMethod ));
228
+ }
229
+
230
+ private String formatMappings (Class <?> handlerType , Collection <MappingInfo > mappings ) {
256
231
String formattedType = Arrays .stream (ClassUtils .getPackageName (handlerType ).split ("\\ ." ))
257
232
.map (p -> p .substring (0 , 1 ))
258
233
.collect (Collectors .joining ("." , "" , "." + handlerType .getSimpleName ()));
259
- return mappings .entrySet (). stream ()
260
- .map (entry -> {
261
- Method method = entry . getValue ().getMethod ();
234
+ return mappings .stream ()
235
+ .map (mappingInfo -> {
236
+ Method method = mappingInfo . getHandlerMethod ().getMethod ();
262
237
String methodParameters = Arrays .stream (method .getParameterTypes ())
263
238
.map (Class ::getSimpleName )
264
239
.collect (Collectors .joining ("," , "(" , ")" ));
265
- return entry . getKey () + " => " + method .getName () + methodParameters ;
240
+ return mappingInfo . getCoordinates () + " => " + method .getName () + methodParameters ;
266
241
})
267
242
.collect (Collectors .joining ("\n \t " , "\n \t " + formattedType + ":" + "\n \t " , "" ));
268
243
}
269
244
245
+
246
+ private static class MappingInfo {
247
+
248
+ private final FieldCoordinates coordinates ;
249
+
250
+ private final HandlerMethod handlerMethod ;
251
+
252
+ public MappingInfo (String typeName , String field , HandlerMethod handlerMethod ) {
253
+ this .coordinates = FieldCoordinates .coordinates (typeName , field );
254
+ this .handlerMethod = handlerMethod ;
255
+ }
256
+
257
+ public FieldCoordinates getCoordinates () {
258
+ return this .coordinates ;
259
+ }
260
+
261
+ public HandlerMethod getHandlerMethod () {
262
+ return this .handlerMethod ;
263
+ }
264
+ }
265
+
266
+
267
+ /**
268
+ * {@link DataFetcher} that wrap and invokes a {@link HandlerMethod}.
269
+ */
270
+ static class AnnotatedDataFetcher implements DataFetcher <Object > {
271
+
272
+ private final FieldCoordinates coordinates ;
273
+
274
+ private final HandlerMethod handlerMethod ;
275
+
276
+ private final HandlerMethodArgumentResolverComposite argumentResolvers ;
277
+
278
+
279
+ public AnnotatedDataFetcher (FieldCoordinates coordinates , HandlerMethod handlerMethod ,
280
+ HandlerMethodArgumentResolverComposite resolvers ) {
281
+
282
+ this .coordinates = coordinates ;
283
+ this .handlerMethod = handlerMethod ;
284
+ this .argumentResolvers = resolvers ;
285
+ }
286
+
287
+
288
+ /**
289
+ * Return the {@link FieldCoordinates} the HandlerMethod is mapped to.
290
+ */
291
+ public FieldCoordinates getCoordinates () {
292
+ return this .coordinates ;
293
+ }
294
+
295
+ /**
296
+ * Return the {@link HandlerMethod} used to fetch data.
297
+ */
298
+ public HandlerMethod getHandlerMethod () {
299
+ return this .handlerMethod ;
300
+ }
301
+
302
+
303
+ @ Override
304
+ @ SuppressWarnings ("ConstantConditions" )
305
+ public Object get (DataFetchingEnvironment environment ) throws Exception {
306
+
307
+ InvocableHandlerMethod invocable =
308
+ new InvocableHandlerMethod (this .handlerMethod .createWithResolvedBean (), this .argumentResolvers );
309
+
310
+ return invocable .invoke (environment );
311
+ }
312
+ }
313
+
270
314
}
0 commit comments