Skip to content

Commit 5a8a99a

Browse files
committed
Refactoring in AnnotatedDataFetcherConfigurer
Introduce MappingInfo to allow passing additional mapping metadata up the stack. In preparation for @BatchMapping methods. See gh-130
1 parent b03097a commit 5a8a99a

File tree

3 files changed

+164
-193
lines changed

3 files changed

+164
-193
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedDataFetcher.java

Lines changed: 0 additions & 75 deletions
This file was deleted.

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedDataFetcherConfigurer.java

Lines changed: 137 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717

1818
import java.lang.reflect.Method;
1919
import java.util.Arrays;
20+
import java.util.Collection;
2021
import java.util.Collections;
2122
import java.util.HashMap;
22-
import java.util.LinkedHashMap;
2323
import java.util.Map;
2424
import java.util.stream.Collectors;
2525

@@ -43,10 +43,8 @@
4343
import org.springframework.graphql.data.method.HandlerMethod;
4444
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
4545
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;
4847
import org.springframework.graphql.data.method.annotation.SchemaMapping;
49-
import org.springframework.graphql.data.method.annotation.SubscriptionMapping;
5048
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
5149
import org.springframework.lang.Nullable;
5250
import org.springframework.stereotype.Controller;
@@ -96,6 +94,11 @@ public void setApplicationContext(ApplicationContext applicationContext) {
9694
this.applicationContext = applicationContext;
9795
}
9896

97+
protected final ApplicationContext obtainApplicationContext() {
98+
Assert.state(this.applicationContext != null, "No ApplicationContext");
99+
return this.applicationContext;
100+
}
101+
99102

100103
@Override
101104
public void afterPropertiesSet() {
@@ -115,10 +118,10 @@ public void afterPropertiesSet() {
115118

116119
@Override
117120
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();
122125
DataFetcher<?> dataFetcher = new AnnotatedDataFetcher(coordinates, handlerMethod, this.argumentResolvers);
123126
builder.type(coordinates.getTypeName(), typeBuilder ->
124127
typeBuilder.dataFetcher(coordinates.getFieldName(), dataFetcher));
@@ -128,143 +131,184 @@ public void configure(RuntimeWiring.Builder builder) {
128131
/**
129132
* Scan beans in the ApplicationContext, detect and prepare a map of handler methods.
130133
*/
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)) {
134138
if (beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
135139
continue;
136140
}
137141
Class<?> beanType = null;
138142
try {
139-
beanType = this.applicationContext.getType(beanName);
143+
beanType = context.getType(beanName);
140144
}
141145
catch (Throwable ex) {
142146
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
143147
if (logger.isTraceEnabled()) {
144148
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
145149
}
146150
}
147-
if (beanType == null || !isHandler(beanType)) {
151+
if (beanType == null || !AnnotatedElementUtils.hasAnnotation(beanType, Controller.class)) {
148152
continue;
149153
}
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)) {
153159
throw new IllegalStateException(
154160
"Ambiguous mapping. Cannot map '" + handlerMethod.getBean() + "' method \n" +
155-
handlerMethod + "\nto " + coordinates + ": There is already '" +
156-
existing.getBean() + "' bean method\n" + existing + " mapped.");
161+
handlerMethod + "\nto " + info.getCoordinates() + ": There is already '" +
162+
existing.getHandlerMethod().getBean() + "' bean method\n" + existing + " mapped.");
157163
}
158164
});
159165
}
160-
return result;
166+
return result.values();
161167
}
162168

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();
173172
}
174173

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));
181177

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();
192179

193-
if (logger.isTraceEnabled()) {
194-
logger.trace(formatMappings(userClass, result));
180+
if (logger.isTraceEnabled() && !mappingInfos.isEmpty()) {
181+
logger.trace(formatMappings(userClass, mappingInfos));
195182
}
196183

197-
return result;
184+
return mappingInfos;
198185
}
199186

200187
@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;
206192
}
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();
226202
}
227-
return FieldCoordinates.coordinates(typeName, field);
228203
}
229-
return null;
230-
}
231204

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");
240207
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
241208
HandlerMethodArgumentResolver resolver = this.argumentResolvers.getArgumentResolver(parameter);
242209
if (resolver instanceof SourceMethodArgumentResolver) {
243210
typeName = parameter.getParameterType().getSimpleName();
244211
break;
245212
}
246213
}
247-
Assert.hasText(typeName,
248-
"No parentType specified, and a source/container method argument was also not found: " +
249-
handlerMethod.getShortLogMessage());
250214
}
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);
253221
}
254222

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) {
256231
String formattedType = Arrays.stream(ClassUtils.getPackageName(handlerType).split("\\."))
257232
.map(p -> p.substring(0, 1))
258233
.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();
262237
String methodParameters = Arrays.stream(method.getParameterTypes())
263238
.map(Class::getSimpleName)
264239
.collect(Collectors.joining(",", "(", ")"));
265-
return entry.getKey() + " => " + method.getName() + methodParameters;
240+
return mappingInfo.getCoordinates() + " => " + method.getName() + methodParameters;
266241
})
267242
.collect(Collectors.joining("\n\t", "\n\t" + formattedType + ":" + "\n\t", ""));
268243
}
269244

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+
270314
}

0 commit comments

Comments
 (0)