diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java index 259c452b..a30b602c 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java @@ -98,13 +98,15 @@ public final class SchemaMappingInspector { private SchemaMappingInspector( GraphQLSchema schema, Map> dataFetchers, - List classResolvers, Function classNameFunction) { + InterfaceUnionLookup interfaceUnionLookup) { Assert.notNull(schema, "GraphQLSchema is required"); Assert.notNull(dataFetchers, "DataFetcher map is required"); + Assert.notNull(interfaceUnionLookup, "InterfaceUnionLookup is required"); + this.schema = schema; this.dataFetchers = dataFetchers; - this.interfaceUnionLookup = new InterfaceUnionLookup(schema, dataFetchers, classResolvers, classNameFunction); + this.interfaceUnionLookup = interfaceUnionLookup; } @@ -374,16 +376,12 @@ public final class SchemaMappingInspector { /** * Default implementation of {@link Initializer}. */ - private static class DefaultInitializer implements Initializer { + private static final class DefaultInitializer implements Initializer { private Function classNameFunction = GraphQLObjectType::getName; private final List classResolvers = new ArrayList<>(); - DefaultInitializer() { - this.classResolvers.add((objectType, interfaceOrUnionType) -> Collections.emptyList()); - } - @Override public Initializer classNameFunction(Function function) { this.classNameFunction = function; @@ -398,8 +396,17 @@ public final class SchemaMappingInspector { @Override public SchemaReport inspect(GraphQLSchema schema, Map> fetchers) { - return new SchemaMappingInspector( - schema, fetchers, this.classResolvers, this.classNameFunction).getOrCreateReport(); + + ReflectionClassResolver reflectionResolver = + ReflectionClassResolver.create(schema, fetchers, this.classNameFunction); + + List resolvers = new ArrayList<>(this.classResolvers); + resolvers.add(reflectionResolver); + + InterfaceUnionLookup lookup = InterfaceUnionLookup.create(schema, resolvers); + + SchemaMappingInspector inspector = new SchemaMappingInspector(schema, fetchers, lookup); + return inspector.getOrCreateReport(); } } @@ -427,18 +434,19 @@ public final class SchemaMappingInspector { * the GraphQL object type, and then prepends a prefixes such as a package * name and/or an outer class name. */ - private static class ReflectionClassResolver implements ClassResolver { + private static final class ReflectionClassResolver implements ClassResolver { + + private static final Predicate PACKAGE_PREDICATE = (name) -> !name.startsWith("java."); private final Function classNameFunction; - private final MultiValueMap classPrefixes = new LinkedMultiValueMap<>(); + private final MultiValueMap classPrefixes; - ReflectionClassResolver(Function classNameFunction) { - this.classNameFunction = classNameFunction; - } + private ReflectionClassResolver( + Function nameFunction, MultiValueMap prefixes) { - void addClassPrefix(String interfaceOrUnionTypeName, String classPrefix) { - this.classPrefixes.add(interfaceOrUnionTypeName, classPrefix); + this.classNameFunction = nameFunction; + this.classPrefixes = prefixes; } @Override @@ -455,49 +463,16 @@ public final class SchemaMappingInspector { } return Collections.emptyList(); } - } - - /** - * Provides methods to look up GraphQL Object and Java type pairs associated - * with GraphQL interface and union types. - */ - private static class InterfaceUnionLookup { - - private static final Predicate PACKAGE_PREDICATE = (name) -> !name.startsWith("java."); - - private static final LinkedMultiValueMap EMPTY_MULTI_VALUE_MAP = new LinkedMultiValueMap<>(0); - - /** Interface or union type name to implementing or member GraphQL-Java types pairs. */ - private final Map> mappings = new LinkedHashMap<>(); - - InterfaceUnionLookup( + /** + * Create a resolver that is aware of packages associated with controller + * methods mapped to unions and interfaces. + */ + public static ReflectionClassResolver create( GraphQLSchema schema, Map> dataFetchers, - List classResolvers, Function classNameFunction) { - - addReflectionClassResolver(schema, dataFetchers, classNameFunction, classResolvers); - - for (GraphQLNamedType type : schema.getAllTypesAsList()) { - if (type instanceof GraphQLUnionType union) { - for (GraphQLNamedOutputType member : union.getTypes()) { - addTypeMapping(union, (GraphQLObjectType) member, classResolvers); - } - } - else if (type instanceof GraphQLObjectType objectType) { - for (GraphQLNamedOutputType interfaceType : objectType.getInterfaces()) { - addTypeMapping(interfaceType, objectType, classResolvers); - } - } - } - } - - private static void addReflectionClassResolver( - GraphQLSchema schema, Map> dataFetchers, - Function classNameFunction, List classResolvers) { - - ReflectionClassResolver resolver = new ReflectionClassResolver(classNameFunction); - classResolvers.add(resolver); + Function classNameFunction) { + MultiValueMap classPrefixes = new LinkedMultiValueMap<>(); for (Map.Entry> typeEntry : dataFetchers.entrySet()) { String typeName = typeEntry.getKey(); GraphQLType parentType = schema.getType(typeName); @@ -517,23 +492,87 @@ public final class SchemaMappingInspector { Class clazz = pair.resolvableType().resolve(Object.class); if (PACKAGE_PREDICATE.test(clazz.getPackageName())) { int index = clazz.getName().indexOf(clazz.getSimpleName()); - resolver.addClassPrefix(outputTypeName, clazz.getName().substring(0, index)); + classPrefixes.add(outputTypeName, clazz.getName().substring(0, index)); } else if (fieldEntry.getValue() instanceof SelfDescribingDataFetcher sddf) { if (sddf.getReturnType().getSource() instanceof MethodParameter param) { clazz = param.getDeclaringClass(); int index = clazz.getName().indexOf(clazz.getSimpleName()); - resolver.addClassPrefix(outputTypeName, clazz.getName().substring(0, index)); + classPrefixes.add(outputTypeName, clazz.getName().substring(0, index)); } } } } } + return new ReflectionClassResolver(classNameFunction, classPrefixes); + } + } + + + /** + * Lookup for GraphQL Object and Java type pairs that are associated with + * GraphQL union and interface types. + */ + private static final class InterfaceUnionLookup { + + private static final LinkedMultiValueMap EMPTY_MAP = new LinkedMultiValueMap<>(0); + + /** Interface or union type name to implementing or member GraphQL-Java types pairs. */ + private final Map> mappings; + + private InterfaceUnionLookup(Map> mappings) { + this.mappings = mappings; } - private void addTypeMapping( + /** + * Resolve the implementation GraphQL and Java type pairs for the interface. + * @param interfaceType the interface type to resolve type pairs for + * @return {@code MultiValueMap} with one or more pairs, possibly one + * pair with {@link ResolvableType#NONE}. + */ + MultiValueMap resolveInterface(GraphQLInterfaceType interfaceType) { + return this.mappings.getOrDefault(interfaceType.getName(), EMPTY_MAP); + } + + /** + * Resolve the member GraphQL and Java type pairs for the union. + * @param unionType the union type to resolve type pairs for + * @return {@code MultiValueMap} with one or more pairs, possibly one + * pair with {@link ResolvableType#NONE}. + */ + MultiValueMap resolveUnion(GraphQLUnionType unionType) { + return this.mappings.getOrDefault(unionType.getName(), EMPTY_MAP); + } + + /** + * Resolve the class for every union member and interface implementation type, + * and create a lookup instance. + */ + public static InterfaceUnionLookup create( + GraphQLSchema schema, List classResolvers) { + + Map> mappings = new LinkedHashMap<>(); + + for (GraphQLNamedType type : schema.getAllTypesAsList()) { + if (type instanceof GraphQLUnionType union) { + for (GraphQLNamedOutputType member : union.getTypes()) { + addTypeMapping(union, (GraphQLObjectType) member, classResolvers, mappings); + } + } + else if (type instanceof GraphQLObjectType objectType) { + for (GraphQLNamedOutputType interfaceType : objectType.getInterfaces()) { + addTypeMapping(interfaceType, objectType, classResolvers, mappings); + } + } + } + + return new InterfaceUnionLookup(mappings); + } + + private static void addTypeMapping( GraphQLNamedOutputType interfaceOrUnionType, GraphQLObjectType objectType, - List classResolvers) { + List classResolvers, + Map> mappings) { List resolvableTypes = new ArrayList<>(); @@ -554,30 +593,9 @@ public final class SchemaMappingInspector { for (ResolvableType resolvableType : resolvableTypes) { String name = interfaceOrUnionType.getName(); - this.mappings.computeIfAbsent(name, (n) -> new LinkedMultiValueMap<>()).add(objectType, resolvableType); + mappings.computeIfAbsent(name, (n) -> new LinkedMultiValueMap<>()).add(objectType, resolvableType); } } - - /** - * Resolve the implementation GraphQL and Java type pairs for the interface. - * @param interfaceType the interface type to resolve type pairs for - * @return {@code MultiValueMap} with one or more pairs, possibly one - * pair with {@link ResolvableType#NONE}. - */ - MultiValueMap resolveInterface(GraphQLInterfaceType interfaceType) { - return this.mappings.getOrDefault(interfaceType.getName(), EMPTY_MULTI_VALUE_MAP); - } - - /** - * Resolve the member GraphQL and Java type pairs for the union. - * @param unionType the union type to resolve type pairs for - * @return {@code MultiValueMap} with one or more pairs, possibly one - * pair with {@link ResolvableType#NONE}. - */ - MultiValueMap resolveUnion(GraphQLUnionType unionType) { - return this.mappings.getOrDefault(unionType.getName(), EMPTY_MULTI_VALUE_MAP); - } - }