diff --git a/src/main/java/graphql/schema/CodeRegistryVisitor.java b/src/main/java/graphql/schema/CodeRegistryVisitor.java index 458b77c1dd..66af22f209 100644 --- a/src/main/java/graphql/schema/CodeRegistryVisitor.java +++ b/src/main/java/graphql/schema/CodeRegistryVisitor.java @@ -1,6 +1,17 @@ package graphql.schema; import graphql.Internal; +import graphql.introspection.Introspection; +import graphql.schema.DataFetcher; +import graphql.schema.FieldCoordinates; +import graphql.schema.GraphQLCodeRegistry; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLFieldsContainer; +import graphql.schema.GraphQLInterfaceType; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLTypeVisitorStub; +import graphql.schema.GraphQLUnionType; +import graphql.schema.TypeResolver; import graphql.util.TraversalControl; import graphql.util.TraverserContext; @@ -12,11 +23,12 @@ * This ensure that all fields have data fetchers and that unions and interfaces have type resolvers */ @Internal -class CodeRegistryVisitor extends GraphQLTypeVisitorStub { +public class CodeRegistryVisitor extends GraphQLTypeVisitorStub { private final GraphQLCodeRegistry.Builder codeRegistry; - CodeRegistryVisitor(GraphQLCodeRegistry.Builder codeRegistry) { + public CodeRegistryVisitor(GraphQLCodeRegistry.Builder codeRegistry) { this.codeRegistry = codeRegistry; + Introspection.addCodeForIntrospectionTypes(codeRegistry); } @Override @@ -27,7 +39,7 @@ public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, FieldCoordinates coordinates = coordinates(parentContainerType, node); codeRegistry.dataFetcherIfAbsent(coordinates, dataFetcher); } - + return CONTINUE; } @@ -38,7 +50,7 @@ public TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, Tra codeRegistry.typeResolverIfAbsent(node, typeResolver); } assertTrue(codeRegistry.getTypeResolver(node) != null, - () -> String.format("You MUST provide a type resolver for the interface type '%s'",node.getName())); + () -> String.format("You MUST provide a type resolver for the interface type '%s'", node.getName())); return CONTINUE; } diff --git a/src/main/java/graphql/schema/GraphQLSchema.java b/src/main/java/graphql/schema/GraphQLSchema.java index e935fec98d..5b5316555f 100644 --- a/src/main/java/graphql/schema/GraphQLSchema.java +++ b/src/main/java/graphql/schema/GraphQLSchema.java @@ -8,9 +8,12 @@ import graphql.DirectivesUtil; import graphql.Internal; import graphql.PublicApi; +import graphql.collect.ImmutableKit; import graphql.introspection.Introspection; import graphql.language.SchemaDefinition; import graphql.language.SchemaExtensionDefinition; +import graphql.schema.impl.GraphQLTypeCollectingVisitor; +import graphql.schema.impl.SchemaUtil; import graphql.schema.validation.InvalidSchemaException; import graphql.schema.validation.SchemaValidationError; import graphql.schema.validation.SchemaValidator; @@ -45,13 +48,12 @@ @PublicApi public class GraphQLSchema { - private final GraphQLObjectType queryType; private final GraphQLObjectType mutationType; private final GraphQLObjectType subscriptionType; private final GraphQLObjectType introspectionSchemaType; private final ImmutableSet additionalTypes; - private final GraphQLFieldDefinition intospectionSchemaField; + private final GraphQLFieldDefinition introspectionSchemaField; private final GraphQLFieldDefinition introspectionTypeField; // we don't allow modification of "__typename" - its a scalar private final GraphQLFieldDefinition __typename = Introspection.TypeNameMetaFieldDef; @@ -59,6 +61,7 @@ public class GraphQLSchema { private final DirectivesUtil.DirectivesHolder schemaDirectives; private final SchemaDefinition definition; private final ImmutableList extensionDefinitions; + private final String description; private final GraphQLCodeRegistry codeRegistry; @@ -66,58 +69,64 @@ public class GraphQLSchema { private final ImmutableMap> interfaceNameToObjectTypes; private final ImmutableMap> interfaceNameToObjectTypeNames; - private final String description; - + /* + * This constructs partial GraphQL schema object which has has the schema (query / mutation / subscription) trees + * in it but it does not have the collected types, code registry nor the type references replaced + * + * But it can be traversed to discover all that and filled out later via another constructor. + * + */ @Internal - private GraphQLSchema(Builder builder, boolean afterTransform) { + private GraphQLSchema(Builder builder) { assertNotNull(builder.additionalTypes, () -> "additionalTypes can't be null"); assertNotNull(builder.queryType, () -> "queryType can't be null"); assertNotNull(builder.additionalDirectives, () -> "directives can't be null"); assertNotNull(builder.codeRegistry, () -> "codeRegistry can't be null"); - this.queryType = builder.queryType; this.mutationType = builder.mutationType; this.subscriptionType = builder.subscriptionType; this.additionalTypes = ImmutableSet.copyOf(builder.additionalTypes); this.introspectionSchemaType = builder.introspectionSchemaType; - this.intospectionSchemaField = Introspection.buildSchemaField(builder.introspectionSchemaType); + this.introspectionSchemaField = Introspection.buildSchemaField(builder.introspectionSchemaType); this.introspectionTypeField = Introspection.buildTypeField(builder.introspectionSchemaType); this.directives = new DirectivesUtil.DirectivesHolder(builder.additionalDirectives); this.schemaDirectives = new DirectivesUtil.DirectivesHolder(builder.schemaDirectives); this.definition = builder.definition; this.extensionDefinitions = nonNullCopyOf(builder.extensionDefinitions); - this.codeRegistry = builder.codeRegistry; - // sorted by type name - SchemaUtil schemaUtil = new SchemaUtil(); - this.typeMap = ImmutableMap.copyOf(schemaUtil.allTypes(this, additionalTypes, afterTransform)); - this.interfaceNameToObjectTypes = buildInterfacesToObjectTypes(schemaUtil.groupImplementations(this)); - this.interfaceNameToObjectTypeNames = buildInterfacesToObjectName(interfaceNameToObjectTypes); this.description = builder.description; - } - // This can be removed once we no longer extract legacy code from types such as data fetchers but for now - // we need it to make an efficient copy that does not walk the types twice + this.codeRegistry = null; + this.typeMap = ImmutableKit.emptyMap(); + this.interfaceNameToObjectTypes = ImmutableKit.emptyMap(); + this.interfaceNameToObjectTypeNames = ImmutableKit.emptyMap(); + } + /* + * This constructs a full fledged graphql schema object that has not yet had its type references replaced + * but its otherwise complete + */ @Internal - private GraphQLSchema(GraphQLSchema otherSchema, GraphQLCodeRegistry codeRegistry) { - this.queryType = otherSchema.queryType; - this.mutationType = otherSchema.mutationType; - this.subscriptionType = otherSchema.subscriptionType; - this.introspectionSchemaType = otherSchema.introspectionSchemaType; - this.additionalTypes = otherSchema.additionalTypes; - this.intospectionSchemaField = otherSchema.intospectionSchemaField; - this.introspectionTypeField = otherSchema.introspectionTypeField; - this.directives = otherSchema.directives; - this.schemaDirectives = otherSchema.schemaDirectives; - this.definition = otherSchema.definition; - this.extensionDefinitions = nonNullCopyOf(otherSchema.extensionDefinitions); + public GraphQLSchema(GraphQLSchema partiallyBuiltSchema, + GraphQLCodeRegistry codeRegistry, + ImmutableMap typeMap, + ImmutableMap> interfaceNameToObjectTypes) { + this.queryType = partiallyBuiltSchema.queryType; + this.mutationType = partiallyBuiltSchema.mutationType; + this.subscriptionType = partiallyBuiltSchema.subscriptionType; + this.additionalTypes = ImmutableSet.copyOf(partiallyBuiltSchema.additionalTypes); + this.introspectionSchemaType = partiallyBuiltSchema.introspectionSchemaType; + this.introspectionSchemaField = Introspection.buildSchemaField(partiallyBuiltSchema.introspectionSchemaType); + this.introspectionTypeField = Introspection.buildTypeField(partiallyBuiltSchema.introspectionSchemaType); + this.directives = partiallyBuiltSchema.directives; + this.schemaDirectives = partiallyBuiltSchema.schemaDirectives; + this.definition = partiallyBuiltSchema.definition; + this.extensionDefinitions = partiallyBuiltSchema.extensionDefinitions; + this.description = partiallyBuiltSchema.description; this.codeRegistry = codeRegistry; - - this.typeMap = otherSchema.typeMap; - this.interfaceNameToObjectTypes = otherSchema.interfaceNameToObjectTypes; - this.interfaceNameToObjectTypeNames = otherSchema.interfaceNameToObjectTypeNames; - this.description = otherSchema.description; + this.typeMap = typeMap; + this.interfaceNameToObjectTypes = interfaceNameToObjectTypes; + interfaceNameToObjectTypeNames = buildInterfacesToObjectName(interfaceNameToObjectTypes); } /** @@ -155,7 +164,11 @@ private static GraphQLDirective[] schemaDirectivesArray(GraphQLSchema existingSc return existingSchema.schemaDirectives.getDirectives().toArray(new GraphQLDirective[0]); } - private ImmutableMap> buildInterfacesToObjectTypes(Map> groupImplementations) { + private static List getAllTypesAsList(ImmutableMap typeMap) { + return sortTypes(byNameAsc(), typeMap.values()); + } + + private static ImmutableMap> buildInterfacesToObjectTypes(Map> groupImplementations) { ImmutableMap.Builder> map = ImmutableMap.builder(); for (Map.Entry> e : groupImplementations.entrySet()) { ImmutableList sortedObjectTypes = ImmutableList.copyOf(sortTypes(byNameAsc(), e.getValue())); @@ -164,7 +177,7 @@ private ImmutableMap> buildInterfacesTo return map.build(); } - private ImmutableMap> buildInterfacesToObjectName(ImmutableMap> byInterface) { + private static ImmutableMap> buildInterfacesToObjectName(ImmutableMap> byInterface) { ImmutableMap.Builder> map = ImmutableMap.builder(); for (Map.Entry> e : byInterface.entrySet()) { ImmutableList objectTypeNames = map(e.getValue(), GraphQLObjectType::getName); @@ -181,7 +194,7 @@ public GraphQLCodeRegistry getCodeRegistry() { * @return the special system field called "__schema" */ public GraphQLFieldDefinition getIntrospectionSchemaFieldDefinition() { - return intospectionSchemaField; + return introspectionSchemaField; } /** @@ -284,7 +297,7 @@ public Map getTypeMap() { } public List getAllTypesAsList() { - return sortTypes(byNameAsc(), typeMap.values()); + return getAllTypesAsList(typeMap); } /** @@ -495,18 +508,16 @@ public static class Builder { private GraphQLObjectType introspectionSchemaType = Introspection.__Schema; private GraphQLObjectType subscriptionType; private GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry().build(); - private Set additionalTypes = new LinkedHashSet<>(); private SchemaDefinition definition; private List extensionDefinitions; private String description; // we default these in - private Set additionalDirectives = new LinkedHashSet<>( + private final Set additionalDirectives = new LinkedHashSet<>( asList(Directives.IncludeDirective, Directives.SkipDirective) ); - private List schemaDirectives = new ArrayList<>(); - - private SchemaUtil schemaUtil = new SchemaUtil(); + private final Set additionalTypes = new LinkedHashSet<>(); + private final List schemaDirectives = new ArrayList<>(); public Builder query(GraphQLObjectType.Builder builder) { return query(builder.build()); @@ -673,10 +684,10 @@ public GraphQLSchema build(Set additionalTypes, Set "additionalTypes can't be null"); assertNotNull(additionalDirectives, () -> "additionalDirectives can't be null"); @@ -690,12 +701,28 @@ GraphQLSchema buildImpl(boolean afterTransform) { additionalDirectives.add(Directives.SpecifiedByDirective); } - // grab the legacy code things from types - final GraphQLSchema tempSchema = new GraphQLSchema(this, afterTransform); - codeRegistry = codeRegistry.transform(codeRegistryBuilder -> schemaUtil.extractCodeFromTypes(codeRegistryBuilder, tempSchema)); + // quick build - no traversing + final GraphQLSchema partiallyBuiltSchema = new GraphQLSchema(this); + + GraphQLCodeRegistry.Builder extractedDataFetchers = GraphQLCodeRegistry.newCodeRegistry(codeRegistry); + CodeRegistryVisitor codeRegistryVisitor = new CodeRegistryVisitor(extractedDataFetchers); + GraphQLTypeCollectingVisitor typeCollectingVisitor = new GraphQLTypeCollectingVisitor(); + SchemaUtil.visitPartiallySchema(partiallyBuiltSchema, codeRegistryVisitor, typeCollectingVisitor); + + codeRegistry = extractedDataFetchers.build(); + ImmutableMap allTypes = typeCollectingVisitor.getResult(); + List allTypesAsList = getAllTypesAsList(allTypes); + + ImmutableMap> groupedImplementations = SchemaUtil.groupInterfaceImplementationsByName(allTypesAsList); + ImmutableMap> interfaceNameToObjectTypes = buildInterfacesToObjectTypes(groupedImplementations); + + // this is now build however its contained types are still to be mutated by type reference replacement + final GraphQLSchema finalSchema = new GraphQLSchema(partiallyBuiltSchema, codeRegistry, allTypes, interfaceNameToObjectTypes); + SchemaUtil.replaceTypeReferences(finalSchema); + return validateSchema(finalSchema); + } - GraphQLSchema graphQLSchema = new GraphQLSchema(tempSchema, codeRegistry); - schemaUtil.replaceTypeReferences(graphQLSchema); + private GraphQLSchema validateSchema(GraphQLSchema graphQLSchema) { Collection errors = new SchemaValidator().validateSchema(graphQLSchema); if (errors.size() > 0) { throw new InvalidSchemaException(errors); diff --git a/src/main/java/graphql/schema/SchemaTransformer.java b/src/main/java/graphql/schema/SchemaTransformer.java index fdf532b3ac..cf0ba71974 100644 --- a/src/main/java/graphql/schema/SchemaTransformer.java +++ b/src/main/java/graphql/schema/SchemaTransformer.java @@ -26,8 +26,8 @@ import static graphql.Assert.assertShouldNeverHappen; import static graphql.schema.GraphQLSchemaElementAdapter.SCHEMA_ELEMENT_ADAPTER; import static graphql.schema.SchemaElementChildrenContainer.newSchemaElementChildrenContainer; -import static graphql.schema.StronglyConnectedComponentsTopologicallySorted.getStronglyConnectedComponentsTopologicallySorted; import static graphql.util.NodeZipper.ModificationType.DELETE; +import static graphql.schema.impl.StronglyConnectedComponentsTopologicallySorted.getStronglyConnectedComponentsTopologicallySorted; import static graphql.util.NodeZipper.ModificationType.REPLACE; import static graphql.util.TraversalControl.CONTINUE; import static java.lang.String.format; @@ -544,7 +544,7 @@ public GraphQLSchema rebuildSchema(GraphQLCodeRegistry.Builder codeRegistry) { .withSchemaDirectives(this.schemaDirectives) .codeRegistry(codeRegistry.build()) .description(schema.getDescription()) - .buildImpl(true); + .build(); } } } diff --git a/src/main/java/graphql/schema/SchemaUtil.java b/src/main/java/graphql/schema/SchemaUtil.java deleted file mode 100644 index 5fbcc4afc7..0000000000 --- a/src/main/java/graphql/schema/SchemaUtil.java +++ /dev/null @@ -1,143 +0,0 @@ -package graphql.schema; - - -import com.google.common.collect.ImmutableMap; -import graphql.Internal; -import graphql.introspection.Introspection; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -@Internal -public class SchemaUtil { - - private static final SchemaTraverser TRAVERSER = new SchemaTraverser(); - - - ImmutableMap allTypes(final GraphQLSchema schema, final Set additionalTypes, boolean afterTransform) { - List roots = new ArrayList<>(); - roots.add(schema.getQueryType()); - - if (schema.isSupportingMutations()) { - roots.add(schema.getMutationType()); - } - - if (schema.isSupportingSubscriptions()) { - roots.add(schema.getSubscriptionType()); - } - - if (additionalTypes != null) { - roots.addAll(additionalTypes); - } - - if (schema.getDirectives() != null) { - roots.addAll(schema.getDirectives()); - } - - roots.add(schema.getIntrospectionSchemaType()); - - GraphQLTypeCollectingVisitor visitor = new GraphQLTypeCollectingVisitor(); - SchemaTraverser traverser; - // when collecting all types we never want to follow type references - // When a schema is build first the type references are not replaced, so - // this is not a problem. But when a schema is transformed, - // the type references are actually replaced so we need to make sure we - // use the original type references - if (afterTransform) { - traverser = new SchemaTraverser(schemaElement -> schemaElement.getChildrenWithTypeReferences().getChildrenAsList()); - } else { - traverser = new SchemaTraverser(); - } - traverser.depthFirst(visitor, roots); - Map result = visitor.getResult(); - return ImmutableMap.copyOf(new TreeMap<>(result)); - } - - - /* - * Indexes GraphQLObject types registered with the provided schema by implemented GraphQLInterface name - * - * This helps in accelerates/simplifies collecting types that implement a certain interface - * - * Provided to replace {@link #findImplementations(graphql.schema.GraphQLSchema, graphql.schema.GraphQLInterfaceType)} - * - */ - Map> groupImplementations(GraphQLSchema schema) { - Map> result = new LinkedHashMap<>(); - for (GraphQLType type : schema.getAllTypesAsList()) { - if (type instanceof GraphQLObjectType) { - List interfaces = ((GraphQLObjectType) type).getInterfaces(); - for (GraphQLNamedOutputType interfaceType : interfaces) { - List myGroup = result.computeIfAbsent(interfaceType.getName(), k -> new ArrayList<>()); - myGroup.add((GraphQLObjectType) type); - } - } - } - return ImmutableMap.copyOf(new TreeMap<>(result)); - } - - public Map> groupImplementationsForInterfacesAndObjects(GraphQLSchema schema) { - Map> result = new LinkedHashMap<>(); - for (GraphQLType type : schema.getAllTypesAsList()) { - if (type instanceof GraphQLImplementingType) { - List interfaces = ((GraphQLImplementingType) type).getInterfaces(); - for (GraphQLNamedOutputType interfaceType : interfaces) { - List myGroup = result.computeIfAbsent(interfaceType.getName(), k -> new ArrayList<>()); - myGroup.add((GraphQLImplementingType) type); - } - } - } - return ImmutableMap.copyOf(new TreeMap<>(result)); - } - - /** - * This method is deprecated due to a performance concern. - * - * The Algorithm complexity: O(n^2), where n is number of registered GraphQLTypes - * - * That indexing operation is performed twice per input document: - * 1. during validation - * 2. during execution - * - * We now indexed all types at the schema creation, which has brought complexity down to O(1) - * - * @param schema GraphQL schema - * @param interfaceType an interface type to find implementations for - * - * @return List of object types implementing provided interface - * - * @deprecated use {@link graphql.schema.GraphQLSchema#getImplementations(GraphQLInterfaceType)} instead - */ - @Deprecated - public List findImplementations(GraphQLSchema schema, GraphQLInterfaceType interfaceType) { - List result = new ArrayList<>(); - for (GraphQLType type : schema.getAllTypesAsList()) { - if (!(type instanceof GraphQLObjectType)) { - continue; - } - GraphQLObjectType objectType = (GraphQLObjectType) type; - if ((objectType).getInterfaces().contains(interfaceType)) { - result.add(objectType); - } - } - return result; - } - - void replaceTypeReferences(GraphQLSchema schema) { - final Map typeMap = schema.getTypeMap(); - List roots = new ArrayList<>(typeMap.values()); - roots.addAll(schema.getDirectives()); - SchemaTraverser schemaTraverser = new SchemaTraverser(schemaElement -> schemaElement.getChildrenWithTypeReferences().getChildrenAsList()); - schemaTraverser.depthFirst(new GraphQLTypeResolvingVisitor(typeMap), roots); - } - - void extractCodeFromTypes(GraphQLCodeRegistry.Builder codeRegistry, GraphQLSchema schema) { - Introspection.addCodeForIntrospectionTypes(codeRegistry); - - TRAVERSER.depthFirst(new CodeRegistryVisitor(codeRegistry), schema.getAllTypesAsList()); - } -} diff --git a/src/main/java/graphql/schema/GraphQLTypeCollectingVisitor.java b/src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java similarity index 85% rename from src/main/java/graphql/schema/GraphQLTypeCollectingVisitor.java rename to src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java index 22b579f081..4ac7333fe3 100644 --- a/src/main/java/graphql/schema/GraphQLTypeCollectingVisitor.java +++ b/src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java @@ -1,12 +1,26 @@ -package graphql.schema; +package graphql.schema.impl; +import com.google.common.collect.ImmutableMap; import graphql.AssertException; import graphql.Internal; +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInterfaceType; +import graphql.schema.GraphQLNamedType; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLScalarType; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLType; +import graphql.schema.GraphQLTypeReference; +import graphql.schema.GraphQLTypeVisitorStub; +import graphql.schema.GraphQLUnionType; import graphql.util.TraversalControl; import graphql.util.TraverserContext; import java.util.LinkedHashMap; import java.util.Map; +import java.util.TreeMap; import static java.lang.String.format; @@ -109,7 +123,7 @@ private void assertTypeUniqueness(GraphQLNamedType type, Map getResult() { - return result; + public ImmutableMap getResult() { + return ImmutableMap.copyOf(new TreeMap<>(result)); } } diff --git a/src/main/java/graphql/schema/impl/MultiReadOnlyGraphQLTypeVisitor.java b/src/main/java/graphql/schema/impl/MultiReadOnlyGraphQLTypeVisitor.java new file mode 100644 index 0000000000..8950abd8cc --- /dev/null +++ b/src/main/java/graphql/schema/impl/MultiReadOnlyGraphQLTypeVisitor.java @@ -0,0 +1,211 @@ +package graphql.schema.impl; + +import graphql.Assert; +import graphql.Internal; +import graphql.schema.GraphQLArgument; +import graphql.schema.GraphQLCompositeType; +import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLDirectiveContainer; +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLEnumValueDefinition; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLFieldsContainer; +import graphql.schema.GraphQLInputFieldsContainer; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInputType; +import graphql.schema.GraphQLInterfaceType; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLModifiedType; +import graphql.schema.GraphQLNonNull; +import graphql.schema.GraphQLNullableType; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLOutputType; +import graphql.schema.GraphQLScalarType; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLTypeReference; +import graphql.schema.GraphQLTypeVisitor; +import graphql.schema.GraphQLUnionType; +import graphql.schema.GraphQLUnmodifiedType; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; + +import java.util.List; + +/** + * A delegating type visitor that allows you to call N visitors in a list + * and always continues via {@link TraversalControl#CONTINUE} + */ +@Internal +public class MultiReadOnlyGraphQLTypeVisitor implements GraphQLTypeVisitor { + + private final List visitors; + + public MultiReadOnlyGraphQLTypeVisitor(List visitors) { + this.visitors = visitors; + } + + @Override + public TraversalControl visitGraphQLArgument(GraphQLArgument node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLArgument(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLInterfaceType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLEnumType(GraphQLEnumType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLEnumType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLEnumValueDefinition(GraphQLEnumValueDefinition node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLEnumValueDefinition(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLFieldDefinition(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLDirective(GraphQLDirective node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLDirective(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLInputObjectField(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLInputObjectType(GraphQLInputObjectType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLInputObjectType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLList(GraphQLList node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLList(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLNonNull(GraphQLNonNull node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLNonNull(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLObjectType(GraphQLObjectType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLObjectType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLScalarType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLTypeReference(GraphQLTypeReference node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLTypeReference(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLUnionType(GraphQLUnionType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLUnionType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitBackRef(TraverserContext context) { + visitors.forEach(v -> v.visitBackRef(context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLModifiedType(GraphQLModifiedType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLModifiedType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLCompositeType(GraphQLCompositeType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLCompositeType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLDirectiveContainer(GraphQLDirectiveContainer node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLDirectiveContainer(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLFieldsContainer(GraphQLFieldsContainer node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLFieldsContainer(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLInputFieldsContainer(GraphQLInputFieldsContainer node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLInputFieldsContainer(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLInputType(GraphQLInputType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLInputType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLNullableType(GraphQLNullableType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLNullableType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLOutputType(GraphQLOutputType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLOutputType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLUnmodifiedType(GraphQLUnmodifiedType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLUnmodifiedType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl changeNode(TraverserContext context, GraphQLSchemaElement newChangedNode) { + return Assert.assertShouldNeverHappen("This must be a read only operation"); + } + + @Override + public TraversalControl deleteNode(TraverserContext context) { + return Assert.assertShouldNeverHappen("This must be a read only operation"); + } + + @Override + public TraversalControl insertAfter(TraverserContext context, GraphQLSchemaElement toInsertAfter) { + return Assert.assertShouldNeverHappen("This must be a read only operation"); + } + + @Override + public TraversalControl insertBefore(TraverserContext context, GraphQLSchemaElement toInsertBefore) { + return Assert.assertShouldNeverHappen("This must be a read only operation"); + } +} diff --git a/src/main/java/graphql/schema/impl/SchemaUtil.java b/src/main/java/graphql/schema/impl/SchemaUtil.java new file mode 100644 index 0000000000..0eaf2fb553 --- /dev/null +++ b/src/main/java/graphql/schema/impl/SchemaUtil.java @@ -0,0 +1,99 @@ +package graphql.schema.impl; + + +import com.google.common.collect.ImmutableMap; +import graphql.Internal; +import graphql.schema.GraphQLImplementingType; +import graphql.schema.GraphQLNamedOutputType; +import graphql.schema.GraphQLNamedType; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLSchema; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLType; +import graphql.schema.GraphQLTypeResolvingVisitor; +import graphql.schema.GraphQLTypeVisitor; +import graphql.schema.SchemaTraverser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +@Internal +public class SchemaUtil { + + /** + * Called to visit a partially build schema (during {@link GraphQLSchema} build phases) with a set of visitors + * + * Each visitor is expected to hold its own side effects that might be last used to construct a full schema + * + * @param partiallyBuiltSchema the partially built schema + * @param visitors the visitors to call + */ + + public static void visitPartiallySchema(final GraphQLSchema partiallyBuiltSchema, GraphQLTypeVisitor... visitors) { + List roots = new ArrayList<>(); + roots.add(partiallyBuiltSchema.getQueryType()); + + if (partiallyBuiltSchema.isSupportingMutations()) { + roots.add(partiallyBuiltSchema.getMutationType()); + } + + if (partiallyBuiltSchema.isSupportingSubscriptions()) { + roots.add(partiallyBuiltSchema.getSubscriptionType()); + } + + if (partiallyBuiltSchema.getAdditionalTypes() != null) { + roots.addAll(partiallyBuiltSchema.getAdditionalTypes()); + } + + if (partiallyBuiltSchema.getDirectives() != null) { + roots.addAll(partiallyBuiltSchema.getDirectives()); + } + + roots.add(partiallyBuiltSchema.getIntrospectionSchemaType()); + + GraphQLTypeVisitor visitor = new MultiReadOnlyGraphQLTypeVisitor(Arrays.asList(visitors)); + SchemaTraverser traverser; + traverser = new SchemaTraverser(schemaElement -> schemaElement.getChildrenWithTypeReferences().getChildrenAsList()); + traverser.depthFirst(visitor, roots); + } + + public static ImmutableMap> groupInterfaceImplementationsByName(List allTypesAsList) { + Map> result = new LinkedHashMap<>(); + for (GraphQLType type : allTypesAsList) { + if (type instanceof GraphQLObjectType) { + List interfaces = ((GraphQLObjectType) type).getInterfaces(); + for (GraphQLNamedOutputType interfaceType : interfaces) { + List myGroup = result.computeIfAbsent(interfaceType.getName(), k -> new ArrayList<>()); + myGroup.add((GraphQLObjectType) type); + } + } + } + return ImmutableMap.copyOf(new TreeMap<>(result)); + } + + public Map> groupImplementationsForInterfacesAndObjects(GraphQLSchema schema) { + Map> result = new LinkedHashMap<>(); + for (GraphQLType type : schema.getAllTypesAsList()) { + if (type instanceof GraphQLImplementingType) { + List interfaces = ((GraphQLImplementingType) type).getInterfaces(); + for (GraphQLNamedOutputType interfaceType : interfaces) { + List myGroup = result.computeIfAbsent(interfaceType.getName(), k -> new ArrayList<>()); + myGroup.add((GraphQLImplementingType) type); + } + } + } + return ImmutableMap.copyOf(new TreeMap<>(result)); + } + + public static void replaceTypeReferences(GraphQLSchema schema) { + final Map typeMap = schema.getTypeMap(); + List roots = new ArrayList<>(typeMap.values()); + roots.addAll(schema.getDirectives()); + SchemaTraverser schemaTraverser = new SchemaTraverser(schemaElement -> schemaElement.getChildrenWithTypeReferences().getChildrenAsList()); + schemaTraverser.depthFirst(new GraphQLTypeResolvingVisitor(typeMap), roots); + } +} diff --git a/src/main/java/graphql/schema/StronglyConnectedComponentsTopologicallySorted.java b/src/main/java/graphql/schema/impl/StronglyConnectedComponentsTopologicallySorted.java similarity index 98% rename from src/main/java/graphql/schema/StronglyConnectedComponentsTopologicallySorted.java rename to src/main/java/graphql/schema/impl/StronglyConnectedComponentsTopologicallySorted.java index 0b00094c4a..3ff66789ff 100644 --- a/src/main/java/graphql/schema/StronglyConnectedComponentsTopologicallySorted.java +++ b/src/main/java/graphql/schema/impl/StronglyConnectedComponentsTopologicallySorted.java @@ -1,7 +1,9 @@ -package graphql.schema; +package graphql.schema.impl; import graphql.Assert; import graphql.Internal; +import graphql.schema.GraphQLNamedType; +import graphql.schema.GraphQLSchemaElement; import java.util.ArrayDeque; import java.util.ArrayList; diff --git a/src/main/java/graphql/util/Anonymizer.java b/src/main/java/graphql/util/Anonymizer.java index afa0384e4c..9b7812dfc4 100644 --- a/src/main/java/graphql/util/Anonymizer.java +++ b/src/main/java/graphql/util/Anonymizer.java @@ -66,7 +66,7 @@ import graphql.schema.GraphQLTypeVisitorStub; import graphql.schema.GraphQLUnionType; import graphql.schema.SchemaTransformer; -import graphql.schema.SchemaUtil; +import graphql.schema.impl.SchemaUtil; import graphql.schema.TypeResolver; import graphql.schema.idl.DirectiveInfo; import graphql.schema.idl.ScalarInfo; diff --git a/src/test/groovy/graphql/schema/SchemaUtilTest.groovy b/src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy similarity index 87% rename from src/test/groovy/graphql/schema/SchemaUtilTest.groovy rename to src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy index cdd97689a4..5b39a84aeb 100644 --- a/src/test/groovy/graphql/schema/SchemaUtilTest.groovy +++ b/src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy @@ -1,9 +1,17 @@ -package graphql.schema +package graphql.schema.impl import graphql.AssertException import graphql.DirectivesUtil import graphql.NestedInputSchema import graphql.introspection.Introspection +import graphql.schema.GraphQLArgument +import graphql.schema.GraphQLFieldDefinition +import graphql.schema.GraphQLInputObjectType +import graphql.schema.GraphQLObjectType +import graphql.schema.GraphQLType +import graphql.schema.GraphQLTypeReference +import graphql.schema.GraphQLUnionType +import graphql.schema.impl.SchemaUtil import spock.lang.Specification import static graphql.Scalars.GraphQLBoolean @@ -42,7 +50,9 @@ class SchemaUtilTest extends Specification { def "collectAllTypes"() { when: - Map types = new SchemaUtil().allTypes(starWarsSchema, Collections.emptySet(), false) + def collectingVisitor = new GraphQLTypeCollectingVisitor() + SchemaUtil.visitPartiallySchema(starWarsSchema, collectingVisitor) + Map types = collectingVisitor.getResult() then: types.size() == 17 types == [(droidType.name) : droidType, @@ -66,7 +76,9 @@ class SchemaUtilTest extends Specification { def "collectAllTypesNestedInput"() { when: - Map types = new SchemaUtil().allTypes(NestedInputSchema.createSchema(), Collections.emptySet(), false) + def collectingVisitor = new GraphQLTypeCollectingVisitor() + SchemaUtil.visitPartiallySchema(NestedInputSchema.createSchema(), collectingVisitor) + Map types = collectingVisitor.getResult() Map expected = [(NestedInputSchema.rootType().name) : NestedInputSchema.rootType(), @@ -89,7 +101,10 @@ class SchemaUtilTest extends Specification { def "collect all types defined in directives"() { when: - Map types = new SchemaUtil().allTypes(SchemaWithReferences, Collections.emptySet(), false) + def collectingVisitor = new GraphQLTypeCollectingVisitor() + SchemaUtil.visitPartiallySchema(SchemaWithReferences, collectingVisitor) + Map types = collectingVisitor.getResult() + then: types.size() == 30 types.containsValue(UnionDirectiveInput) @@ -106,13 +121,13 @@ class SchemaUtilTest extends Specification { def "group all types by implemented interface"() { when: - Map> byInterface = new SchemaUtil().groupImplementations(starWarsSchema) + Map> byInterface = SchemaUtil.groupInterfaceImplementationsByName(starWarsSchema.getAllTypesAsList()) then: byInterface.size() == 1 byInterface[characterInterface.getName()].size() == 2 byInterface == [ - (characterInterface.getName()): [ droidType, humanType] + (characterInterface.getName()): [droidType, humanType] ] } @@ -121,16 +136,16 @@ class SchemaUtilTest extends Specification { GraphQLInputObjectType PersonInputType = newInputObject() .name("Person") .field(newInputObjectField() - .name("name") - .type(GraphQLString)) + .name("name") + .type(GraphQLString)) .build() GraphQLFieldDefinition field = newFieldDefinition() .name("find") .type(typeRef("Person")) .argument(newArgument() - .name("ssn") - .type(GraphQLString)) + .name("ssn") + .type(GraphQLString)) .build() GraphQLObjectType PersonService = newObject()