Skip to content

Commit 23bf1fd

Browse files
committed
Expand interface into object type mappings
See gh-871
1 parent af72628 commit 23bf1fd

File tree

5 files changed

+409
-16
lines changed

5 files changed

+409
-16
lines changed

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

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.ArrayList;
2222
import java.util.Arrays;
2323
import java.util.Collection;
24+
import java.util.Iterator;
2425
import java.util.LinkedHashSet;
2526
import java.util.List;
2627
import java.util.Map;
@@ -34,11 +35,16 @@
3435
import java.util.stream.Collectors;
3536

3637
import graphql.execution.DataFetcherResult;
38+
import graphql.language.ObjectTypeDefinition;
39+
import graphql.language.Type;
40+
import graphql.language.TypeDefinition;
41+
import graphql.language.TypeName;
3742
import graphql.schema.DataFetcher;
3843
import graphql.schema.DataFetchingEnvironment;
3944
import graphql.schema.FieldCoordinates;
4045
import graphql.schema.GraphQLCodeRegistry;
4146
import graphql.schema.idl.RuntimeWiring;
47+
import graphql.schema.idl.TypeDefinitionRegistry;
4248
import org.dataloader.DataLoader;
4349
import org.reactivestreams.Publisher;
4450
import reactor.core.publisher.Flux;
@@ -72,6 +78,8 @@
7278
import org.springframework.stereotype.Controller;
7379
import org.springframework.util.Assert;
7480
import org.springframework.util.ClassUtils;
81+
import org.springframework.util.LinkedMultiValueMap;
82+
import org.springframework.util.MultiValueMap;
7583
import org.springframework.util.StringUtils;
7684
import org.springframework.validation.DataBinder;
7785

@@ -118,6 +126,8 @@ public class AnnotatedControllerConfigurer
118126

119127
private final List<HandlerMethodArgumentResolver> customArgumentResolvers = new ArrayList<>(8);
120128

129+
private final InterfaceMappingHelper interfaceMappingHelper = new InterfaceMappingHelper();
130+
121131
@Nullable
122132
private ValidationHelper validationHelper;
123133

@@ -133,6 +143,11 @@ public void addCustomArgumentResolver(HandlerMethodArgumentResolver resolver) {
133143
this.customArgumentResolvers.add(resolver);
134144
}
135145

146+
@Override
147+
public void setTypeDefinitionRegistry(TypeDefinitionRegistry registry) {
148+
this.interfaceMappingHelper.setTypeDefinitionRegistry(registry);
149+
}
150+
136151
/**
137152
* Configure an initializer that configures the {@link DataBinder} before the binding process.
138153
* @param consumer the data binder initializer
@@ -228,19 +243,16 @@ private void addSortMethodArgumentResolver(HandlerMethodArgumentResolverComposit
228243

229244
@Override
230245
public void configure(RuntimeWiring.Builder runtimeWiringBuilder) {
231-
detectHandlerMethods().forEach((info) -> {
232-
DataFetcher<?> dataFetcher;
233-
if (!info.isBatchMapping()) {
234-
dataFetcher = new SchemaMappingDataFetcher(
235-
info, getArgumentResolvers(), this.validationHelper, getExceptionResolver(), getExecutor());
236-
}
237-
else {
238-
dataFetcher = registerBatchLoader(info);
239-
}
240-
FieldCoordinates coordinates = info.getCoordinates();
241-
runtimeWiringBuilder.type(coordinates.getTypeName(), (typeBuilder) ->
242-
typeBuilder.dataFetcher(coordinates.getFieldName(), dataFetcher));
243-
});
246+
247+
Set<DataFetcherMappingInfo> allInfos = detectHandlerMethods();
248+
Set<DataFetcherMappingInfo> subTypeInfos = this.interfaceMappingHelper.removeInterfaceMappings(allInfos);
249+
250+
allInfos.forEach((info) -> registerDataFetcher(info, runtimeWiringBuilder));
251+
252+
RuntimeWiring wiring = runtimeWiringBuilder.build();
253+
subTypeInfos = this.interfaceMappingHelper.filterExistingMappings(subTypeInfos, wiring.getDataFetchers());
254+
255+
subTypeInfos.forEach((info) -> registerDataFetcher(info, runtimeWiringBuilder));
244256
}
245257

246258
@Override
@@ -313,6 +325,20 @@ protected HandlerMethod getHandlerMethod(DataFetcherMappingInfo mappingInfo) {
313325
return mappingInfo.getHandlerMethod();
314326
}
315327

328+
private void registerDataFetcher(DataFetcherMappingInfo info, RuntimeWiring.Builder runtimeWiringBuilder) {
329+
DataFetcher<?> dataFetcher;
330+
if (!info.isBatchMapping()) {
331+
dataFetcher = new SchemaMappingDataFetcher(
332+
info, getArgumentResolvers(), this.validationHelper, getExceptionResolver(), getExecutor());
333+
}
334+
else {
335+
dataFetcher = registerBatchLoader(info);
336+
}
337+
FieldCoordinates coordinates = info.getCoordinates();
338+
runtimeWiringBuilder.type(coordinates.getTypeName(), (typeBuilder) ->
339+
typeBuilder.dataFetcher(coordinates.getFieldName(), dataFetcher));
340+
}
341+
316342
private DataFetcher<Object> registerBatchLoader(DataFetcherMappingInfo info) {
317343
if (!info.isBatchMapping()) {
318344
throw new IllegalArgumentException("Not a @BatchMapping method: " + info);
@@ -506,6 +532,9 @@ public String toString() {
506532
}
507533

508534

535+
/**
536+
* {@link DataFetcher} that uses a DataLoader.
537+
*/
509538
static class BatchMappingDataFetcher implements DataFetcher<Object>, SelfDescribingDataFetcher<Object> {
510539

511540
private final DataFetcherMappingInfo mappingInfo;
@@ -538,4 +567,51 @@ public Object get(DataFetchingEnvironment env) {
538567
}
539568
}
540569

570+
571+
/**
572+
* Helper to expand schema interface mappings into object type mappings.
573+
*/
574+
private static final class InterfaceMappingHelper {
575+
576+
private final MultiValueMap<String, String> interfaceMappings = new LinkedMultiValueMap<>();
577+
578+
void setTypeDefinitionRegistry(TypeDefinitionRegistry registry) {
579+
for (TypeDefinition<?> definition : registry.types().values()) {
580+
if (definition instanceof ObjectTypeDefinition objectDefinition) {
581+
for (Type<?> type : objectDefinition.getImplements()) {
582+
this.interfaceMappings.add(((TypeName) type).getName(), objectDefinition.getName());
583+
}
584+
}
585+
}
586+
}
587+
588+
Set<DataFetcherMappingInfo> removeInterfaceMappings(Set<DataFetcherMappingInfo> infos) {
589+
Set<DataFetcherMappingInfo> subTypeMappings = new LinkedHashSet<>();
590+
Iterator<DataFetcherMappingInfo> it = infos.iterator();
591+
while (it.hasNext()) {
592+
DataFetcherMappingInfo info = it.next();
593+
List<String> names = this.interfaceMappings.get(info.getTypeName());
594+
if (names != null) {
595+
for (String name : names) {
596+
subTypeMappings.add(new DataFetcherMappingInfo(name, info));
597+
}
598+
it.remove();
599+
}
600+
}
601+
return subTypeMappings;
602+
}
603+
604+
@SuppressWarnings("rawtypes")
605+
Set<DataFetcherMappingInfo> filterExistingMappings(
606+
Set<DataFetcherMappingInfo> infos, Map<String, Map<String, DataFetcher>> dataFetchers) {
607+
608+
return infos.stream()
609+
.filter((info) -> {
610+
Map<String, DataFetcher> registrations = dataFetchers.get(info.getTypeName());
611+
return (registrations == null || !registrations.containsKey(info.getFieldName()));
612+
})
613+
.collect(Collectors.toSet());
614+
}
615+
}
616+
541617
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ public DataFetcherMappingInfo(
5252
this.handlerMethod = handlerMethod;
5353
}
5454

55+
public DataFetcherMappingInfo(String typeName, DataFetcherMappingInfo info) {
56+
this.coordinates = FieldCoordinates.coordinates(typeName, info.getCoordinates().getFieldName());
57+
this.batchMapping = info.batchMapping;
58+
this.maxBatchSize = info.maxBatchSize;
59+
this.handlerMethod = info.handlerMethod;
60+
}
61+
5562

5663
/**
5764
* The field to bind the controller method to.
@@ -60,6 +67,21 @@ public FieldCoordinates getCoordinates() {
6067
return this.coordinates;
6168
}
6269

70+
/**
71+
* Shortcut for the typeName from the coordinates.
72+
*/
73+
public String getTypeName() {
74+
return this.coordinates.getTypeName();
75+
}
76+
77+
78+
/**
79+
* Shortcut for the fieldName from the coordinates.
80+
*/
81+
public String getFieldName() {
82+
return this.coordinates.getFieldName();
83+
}
84+
6385
/**
6486
* Whether it is an {@link BatchMapping} method or not in which case it is
6587
* an {@link SchemaMapping} method.

spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultSchemaResourceGraphQlSourceBuilder.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ protected GraphQLSchema initGraphQlSchema() {
152152
logger.debug("Loaded GraphQL schema resources: (" + resources + ")");
153153
}
154154

155-
RuntimeWiring runtimeWiring = initRuntimeWiring();
155+
RuntimeWiring runtimeWiring = initRuntimeWiring(registry);
156156
updateForCustomRootOperationTypeNames(registry, runtimeWiring);
157157

158158
TypeResolver typeResolver = initTypeResolver();
@@ -198,9 +198,12 @@ private TypeDefinitionRegistry parse(Resource schemaResource) {
198198
}
199199
}
200200

201-
private RuntimeWiring initRuntimeWiring() {
201+
private RuntimeWiring initRuntimeWiring(TypeDefinitionRegistry typeRegistry) {
202202
RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();
203-
this.runtimeWiringConfigurers.forEach((configurer) -> configurer.configure(builder));
203+
this.runtimeWiringConfigurers.forEach((configurer) -> {
204+
configurer.setTypeDefinitionRegistry(typeRegistry);
205+
configurer.configure(builder);
206+
});
204207

205208
List<WiringFactory> factories = new ArrayList<>();
206209
WiringFactory factory = builder.build().getWiringFactory();

spring-graphql/src/main/java/org/springframework/graphql/execution/RuntimeWiringConfigurer.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.List;
2020

2121
import graphql.schema.idl.RuntimeWiring;
22+
import graphql.schema.idl.TypeDefinitionRegistry;
2223
import graphql.schema.idl.WiringFactory;
2324

2425
/**
@@ -30,6 +31,15 @@
3031
*/
3132
public interface RuntimeWiringConfigurer {
3233

34+
/**
35+
* Provides the configurer access to the {@link TypeDefinitionRegistry}.
36+
* @param registry the registry
37+
* @since 1.3.0
38+
*/
39+
default void setTypeDefinitionRegistry(TypeDefinitionRegistry registry) {
40+
// no-op
41+
}
42+
3343
/**
3444
* Apply changes to the {@link RuntimeWiring.Builder} such as registering
3545
* {@link graphql.schema.DataFetcher}s, custom scalar types, and more.

0 commit comments

Comments
 (0)