Skip to content

Commit 56ca50e

Browse files
committed
Support for @BatchMapping methods
See gh-130
1 parent fbe8735 commit 56ca50e

File tree

6 files changed

+862
-44
lines changed

6 files changed

+862
-44
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2002-2021 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+
* https://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 org.springframework.graphql.data.method;
17+
18+
import java.util.Collection;
19+
import java.util.Map;
20+
21+
import org.dataloader.BatchLoaderEnvironment;
22+
import reactor.core.publisher.Flux;
23+
import reactor.core.publisher.Mono;
24+
25+
import org.springframework.core.CollectionFactory;
26+
import org.springframework.core.MethodParameter;
27+
import org.springframework.lang.Nullable;
28+
import org.springframework.util.Assert;
29+
30+
/**
31+
* An extension of {@link HandlerMethod} for annotated handler methods adapted
32+
* to a batch loader function with a list of values and {@link BatchLoaderEnvironment}
33+
* as their input.
34+
*
35+
* @author Rossen Stoyanchev
36+
* @since 1.0.0
37+
*/
38+
public class BatchLoadHandlerMethod extends InvocableHandlerMethodSupport {
39+
40+
41+
public BatchLoadHandlerMethod(HandlerMethod handlerMethod) {
42+
super(handlerMethod);
43+
}
44+
45+
46+
/**
47+
* Invoke the underlying batch loading method, resolving its arguments from
48+
* the given keys for batch loading and the {@link BatchLoaderEnvironment}.
49+
*
50+
* @param keys the batch loading keys
51+
* @param environment the environment available to batch loaders
52+
* @return a {@code Flux} of values or a {@code Mono} with map of key-value pairs.
53+
*/
54+
@Nullable
55+
public <K> Object invoke(Collection<K> keys, BatchLoaderEnvironment environment) {
56+
57+
MethodParameter[] parameters = getMethodParameters();
58+
Assert.notEmpty(parameters, "Batch loading methods should have at least " +
59+
"one argument with the List of parent objects: " + getBridgedMethod().toGenericString());
60+
61+
Object[] args = new Object[parameters.length];
62+
for (int i = 0; i < parameters.length; i++) {
63+
args[i] = resolveArgument(parameters[i], keys, environment);
64+
}
65+
66+
Object result;
67+
try {
68+
result = doInvoke(args);
69+
}
70+
catch (Exception ex) {
71+
throw new IllegalStateException("...", ex);
72+
}
73+
74+
if (result != null) {
75+
if (result instanceof Collection) {
76+
return Flux.fromIterable((Collection<?>) result);
77+
}
78+
else if (result instanceof Map) {
79+
return Mono.just(result);
80+
}
81+
}
82+
83+
return result;
84+
}
85+
86+
public <K> Object resolveArgument(
87+
MethodParameter parameter, Collection<K> keys, BatchLoaderEnvironment environment) {
88+
89+
Class<?> parameterType = parameter.getParameterType();
90+
91+
if (Collection.class.isAssignableFrom(parameterType)) {
92+
if (parameterType.isInstance(keys)) {
93+
return keys;
94+
}
95+
Class<?> elementType = parameter.nested().getNestedParameterType();
96+
Collection<K> collection = CollectionFactory.createCollection(parameterType, elementType, keys.size());
97+
collection.addAll(keys);
98+
return collection;
99+
}
100+
101+
if (parameterType.isInstance(environment)) {
102+
return environment;
103+
}
104+
105+
throw new IllegalStateException(formatArgumentError(parameter, "Unexpected argument type."));
106+
}
107+
108+
}

spring-graphql/src/main/java/org/springframework/graphql/data/method/DataFetcherHandlerMethod.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public HandlerMethodArgumentResolverComposite getResolvers() {
6161

6262
/**
6363
* Invoke the method after resolving its argument values in the context of
64-
* the given environment.
64+
* the given {@link DataFetchingEnvironment}.
6565
* <p>Argument values are commonly resolved through
6666
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
6767
* The {@code providedArgs} parameter however may supply argument values to
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2002-2021 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+
* https://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 org.springframework.graphql.data.method.annotation;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
import org.springframework.core.annotation.AliasFor;
25+
26+
/**
27+
* Annotation for handler methods that batch load field values, given a list
28+
* of source/parent values. For example:
29+
*
30+
* <pre class="code">
31+
* &#064;BatchMapping
32+
* public Flux&lt;Author&gt; author(List&lt;Book&gt; books) {
33+
* // ...
34+
* }
35+
* </pre>
36+
*
37+
* <p>The annotated method is registered as a batch loading function and along
38+
* with it, a {@link graphql.schema.DataFetcher} for the field is registered
39+
* transparently that looks up the field through the registered
40+
* {@code DataLoader}.
41+
*
42+
* <p>Effectively, a shortcut for:
43+
*
44+
* <pre class="code">
45+
* &#064;Controller
46+
* public class BookController {
47+
*
48+
* public BookController(BatchLoaderRegistry registry) {
49+
* registry.forTypePair(Long.class, Author.class).registerBatchLoader((ids, env) -> ...);
50+
* }
51+
*
52+
* &#064;SchemaMapping
53+
* public Author author(Book book, DataLoader&lt;Long, Author&gt; dataLoader) {
54+
* return dataLoader.load(book.getAuthorId());
55+
* }
56+
*
57+
* }
58+
* </pre>
59+
*
60+
* @author Rossen Stoyanchev
61+
* @since 1.0.0
62+
*/
63+
@Target({ElementType.TYPE, ElementType.METHOD})
64+
@Retention(RetentionPolicy.RUNTIME)
65+
@Documented
66+
public @interface BatchMapping {
67+
68+
/**
69+
* Customize the name of the GraphQL field to bind to.
70+
* <p>By default, if not specified, this is initialized from the method name.
71+
*/
72+
@AliasFor("value")
73+
String field() default "";
74+
75+
/**
76+
* Effectively an alias for {@link #field()}.
77+
*/
78+
@AliasFor("field")
79+
String value() default "";
80+
81+
/**
82+
* Customizes the name of the parent/container type for the GraphQL field.
83+
* <p>By default, if not specified, it is derived from the class name of the
84+
* List of source/parent values injected into the handler method.
85+
* <p>This value for this attribute can be initialized from a class-level
86+
* {@link SchemaMapping @SchemaMapping}. When used on both levels, the one
87+
* on the method level overrides the one at the class level.
88+
*/
89+
String typeName() default "";
90+
91+
}

0 commit comments

Comments
 (0)