21
21
import java .util .Collection ;
22
22
import java .util .Collections ;
23
23
import java .util .LinkedHashSet ;
24
+ import java .util .List ;
24
25
import java .util .Map ;
25
26
import java .util .Set ;
26
27
import java .util .concurrent .Callable ;
27
28
import java .util .concurrent .Executor ;
28
29
import java .util .stream .Collectors ;
29
30
31
+ import graphql .schema .DataFetcher ;
30
32
import org .apache .commons .logging .Log ;
31
33
import org .apache .commons .logging .LogFactory ;
32
34
42
44
import org .springframework .format .support .FormattingConversionService ;
43
45
import org .springframework .graphql .data .method .HandlerMethod ;
44
46
import org .springframework .graphql .data .method .HandlerMethodArgumentResolverComposite ;
47
+ import org .springframework .graphql .execution .DataFetcherExceptionResolver ;
45
48
import org .springframework .lang .Nullable ;
46
49
import org .springframework .stereotype .Controller ;
47
50
import org .springframework .util .Assert ;
@@ -81,6 +84,9 @@ public abstract class AnnotatedControllerDetectionSupport<M> implements Applicat
81
84
82
85
private boolean fallBackOnDirectFieldAccess ;
83
86
87
+ @ Nullable
88
+ private AnnotatedControllerExceptionResolver exceptionResolver ;
89
+
84
90
@ Nullable
85
91
private Executor executor ;
86
92
@@ -120,6 +126,25 @@ protected boolean isFallBackOnDirectFieldAccess() {
120
126
return this .fallBackOnDirectFieldAccess ;
121
127
}
122
128
129
+ /**
130
+ * Return a {@link DataFetcherExceptionResolver} that resolves exceptions with
131
+ * {@code @GraphQlExceptionHandler} methods in {@code @ControllerAdvice}
132
+ * classes declared in Spring configuration. This is useful primarily for
133
+ * exceptions from non-controller {@link DataFetcher}s since exceptions from
134
+ * {@code @SchemaMapping} controller methods are handled automatically at
135
+ * the point of invocation.
136
+ *
137
+ * @return a resolver instance that can be plugged into
138
+ * {@link org.springframework.graphql.execution.GraphQlSource.Builder#exceptionResolvers(List)
139
+ * GraphQlSource.Builder}
140
+ *
141
+ * @since 1.2.0
142
+ */
143
+ public HandlerDataFetcherExceptionResolver getExceptionResolver () {
144
+ Assert .notNull (this .exceptionResolver , "afterPropertiesSet not called yet" );
145
+ return this .exceptionResolver ;
146
+ }
147
+
123
148
/**
124
149
* Configure an {@link Executor} to use for asynchronous handling of
125
150
* {@link Callable} return values from controller methods.
@@ -140,7 +165,7 @@ public Executor getExecutor() {
140
165
* Return the configured argument resolvers.
141
166
*/
142
167
protected HandlerMethodArgumentResolverComposite getArgumentResolvers () {
143
- Assert .notNull (this .argumentResolvers , "Not yet initialized, was afterPropertiesSet called? " );
168
+ Assert .notNull (this .argumentResolvers , "afterPropertiesSet not called yet " );
144
169
return this .argumentResolvers ;
145
170
}
146
171
@@ -163,6 +188,11 @@ protected final ApplicationContext obtainApplicationContext() {
163
188
@ Override
164
189
public void afterPropertiesSet () {
165
190
this .argumentResolvers = initArgumentResolvers ();
191
+
192
+ this .exceptionResolver = new AnnotatedControllerExceptionResolver (this .argumentResolvers );
193
+ if (getApplicationContext () != null ) {
194
+ this .exceptionResolver .registerControllerAdvice (getApplicationContext ());
195
+ }
166
196
}
167
197
168
198
protected abstract HandlerMethodArgumentResolverComposite initArgumentResolvers ();
@@ -192,17 +222,7 @@ protected Set<M> detectHandlerMethods() {
192
222
continue ;
193
223
}
194
224
Class <?> beanClass = context .getType (beanName );
195
- findHandlerMethods (beanName , beanClass ).forEach (info -> {
196
- HandlerMethod handlerMethod = getHandlerMethod (info );
197
- M existing = results .stream ().filter (o -> o .equals (info )).findFirst ().orElse (null );
198
- if (existing != null && !getHandlerMethod (existing ).equals (handlerMethod )) {
199
- throw new IllegalStateException (
200
- "Ambiguous mapping. Cannot map '" + handlerMethod .getBean () + "' method \n " +
201
- handlerMethod + "\n " + ": There is already '" +
202
- getHandlerMethod (existing ).getBean () + "' bean method\n " + existing + " mapped." );
203
- }
204
- results .add (info );
205
- });
225
+ findHandlerMethods (beanName , beanClass ).forEach (info -> registerHandlerMethod (info , results ));
206
226
}
207
227
return results ;
208
228
}
@@ -230,13 +250,6 @@ private Collection<M> findHandlerMethods(Object handler, @Nullable Class<?> hand
230
250
@ Nullable
231
251
protected abstract M getMappingInfo (Method method , Object handler , Class <?> handlerType );
232
252
233
- protected HandlerMethod createHandlerMethod (Method originalMethod , Object handler , Class <?> handlerType ) {
234
- Method method = AopUtils .selectInvocableMethod (originalMethod , handlerType );
235
- return (handler instanceof String beanName ?
236
- new HandlerMethod (beanName , obtainApplicationContext ().getAutowireCapableBeanFactory (), method ) :
237
- new HandlerMethod (handler , method ));
238
- }
239
-
240
253
private String formatMappings (Class <?> handlerType , Collection <M > infos ) {
241
254
String formattedType = Arrays .stream (ClassUtils .getPackageName (handlerType ).split ("\\ ." ))
242
255
.map (p -> p .substring (0 , 1 ))
@@ -252,4 +265,25 @@ private String formatMappings(Class<?> handlerType, Collection<M> infos) {
252
265
.collect (Collectors .joining ("\n \t " , "\n \t " + formattedType + ":" + "\n \t " , "" ));
253
266
}
254
267
268
+ private void registerHandlerMethod (M info , Set <M > results ) {
269
+ Assert .state (this .exceptionResolver != null , "afterPropertiesSet not called" );
270
+ HandlerMethod handlerMethod = getHandlerMethod (info );
271
+ M existing = results .stream ().filter (o -> o .equals (info )).findFirst ().orElse (null );
272
+ if (existing != null && !getHandlerMethod (existing ).equals (handlerMethod )) {
273
+ throw new IllegalStateException (
274
+ "Ambiguous mapping. Cannot map '" + handlerMethod .getBean () + "' method \n " +
275
+ handlerMethod + "\n " + ": There is already '" +
276
+ getHandlerMethod (existing ).getBean () + "' bean method\n " + existing + " mapped." );
277
+ }
278
+ results .add (info );
279
+ this .exceptionResolver .registerController (handlerMethod .getBeanType ());
280
+ }
281
+
282
+ protected HandlerMethod createHandlerMethod (Method originalMethod , Object handler , Class <?> handlerType ) {
283
+ Method method = AopUtils .selectInvocableMethod (originalMethod , handlerType );
284
+ return (handler instanceof String beanName ?
285
+ new HandlerMethod (beanName , obtainApplicationContext ().getAutowireCapableBeanFactory (), method ) :
286
+ new HandlerMethod (handler , method ));
287
+ }
288
+
255
289
}
0 commit comments