All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.schema.GraphQLCodeRegistry Maven / Gradle / Ivy

There is a newer version: 230521-nf-execution
Show newest version
package graphql.schema;

import graphql.Assert;
import graphql.Internal;
import graphql.PublicApi;
import graphql.schema.visibility.GraphqlFieldVisibility;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;

import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertValidName;
import static graphql.schema.DataFetcherFactoryEnvironment.newDataFetchingFactoryEnvironment;
import static graphql.schema.FieldCoordinates.coordinates;
import static graphql.schema.visibility.DefaultGraphqlFieldVisibility.DEFAULT_FIELD_VISIBILITY;


/**
 * The {@link graphql.schema.GraphQLCodeRegistry} holds that execution code that is associated with graphql types, namely
 * the {@link graphql.schema.DataFetcher}s associated with fields, the {@link graphql.schema.TypeResolver}s associated with
 * abstract types and the {@link graphql.schema.visibility.GraphqlFieldVisibility}
 * 

* For legacy reasons these code functions can still exist on the original type objects but this will be removed in a future version. Once * removed the type system objects will be able have proper hashCode/equals methods and be checked for proper equality. */ @PublicApi public class GraphQLCodeRegistry { private final Map> dataFetcherMap; private final Map> systemDataFetcherMap; private final Map typeResolverMap; private final GraphqlFieldVisibility fieldVisibility; private final DataFetcherFactory defaultDataFetcherFactory; private GraphQLCodeRegistry(Builder builder) { this.dataFetcherMap = builder.dataFetcherMap; this.systemDataFetcherMap = builder.systemDataFetcherMap; this.typeResolverMap = builder.typeResolverMap; this.fieldVisibility = builder.fieldVisibility; this.defaultDataFetcherFactory = builder.defaultDataFetcherFactory; } /** * @return the {@link graphql.schema.visibility.GraphqlFieldVisibility} */ public GraphqlFieldVisibility getFieldVisibility() { return fieldVisibility; } /** * Returns a data fetcher associated with a field within a container type * * @param parentType the container type * @param fieldDefinition the field definition * * @return the DataFetcher associated with this field. All fields have data fetchers */ public DataFetcher getDataFetcher(GraphQLFieldsContainer parentType, GraphQLFieldDefinition fieldDefinition) { return getDataFetcherImpl(FieldCoordinates.coordinates(parentType, fieldDefinition), fieldDefinition, dataFetcherMap, systemDataFetcherMap, defaultDataFetcherFactory); } /** * Returns a data fetcher associated with a field located at specified coordinates. * * @param coordinates the field coordinates * @param fieldDefinition the field definition * * @return the DataFetcher associated with this field. All fields have data fetchers */ public DataFetcher getDataFetcher(FieldCoordinates coordinates, GraphQLFieldDefinition fieldDefinition) { return getDataFetcherImpl(coordinates, fieldDefinition, dataFetcherMap, systemDataFetcherMap, defaultDataFetcherFactory); } /** * Returns true if the code registry contained a data fetcher at the specified co-ordinates * * @param coordinates the field coordinates * * @return the true if there is a data fetcher at those co-ordinates */ public boolean hasDataFetcher(FieldCoordinates coordinates) { return hasDataFetcherImpl(coordinates, dataFetcherMap, systemDataFetcherMap); } private static DataFetcher getDataFetcherImpl(FieldCoordinates coordinates, GraphQLFieldDefinition fieldDefinition, Map> dataFetcherMap, Map> systemDataFetcherMap, DataFetcherFactory defaultDataFetcherFactory) { assertNotNull(coordinates); assertNotNull(fieldDefinition); DataFetcherFactory dataFetcherFactory = systemDataFetcherMap.get(fieldDefinition.getName()); if (dataFetcherFactory == null) { dataFetcherFactory = dataFetcherMap.get(coordinates); if (dataFetcherFactory == null) { dataFetcherFactory = defaultDataFetcherFactory; } } return dataFetcherFactory.get(newDataFetchingFactoryEnvironment() .fieldDefinition(fieldDefinition) .build()); } private static boolean hasDataFetcherImpl(FieldCoordinates coords, Map> dataFetcherMap, Map> systemDataFetcherMap) { assertNotNull(coords); DataFetcherFactory dataFetcherFactory = systemDataFetcherMap.get(coords.getFieldName()); if (dataFetcherFactory == null) { dataFetcherFactory = dataFetcherMap.get(coords); } return dataFetcherFactory != null; } /** * Returns the type resolver associated with this interface type * * @param interfaceType the interface type * * @return a non null {@link graphql.schema.TypeResolver} */ public TypeResolver getTypeResolver(GraphQLInterfaceType interfaceType) { return getTypeResolverForInterface(interfaceType, typeResolverMap); } /** * Returns the type resolver associated with this union type * * @param unionType the union type * * @return a non null {@link graphql.schema.TypeResolver} */ public TypeResolver getTypeResolver(GraphQLUnionType unionType) { return getTypeResolverForUnion(unionType, typeResolverMap); } private static TypeResolver getTypeResolverForInterface(GraphQLInterfaceType parentType, Map typeResolverMap) { assertNotNull(parentType); TypeResolver typeResolver = typeResolverMap.get(parentType.getName()); if (typeResolver == null) { typeResolver = parentType.getTypeResolver(); } return assertNotNull(typeResolver, () -> "There must be a type resolver for interface " + parentType.getName()); } private static TypeResolver getTypeResolverForUnion(GraphQLUnionType parentType, Map typeResolverMap) { assertNotNull(parentType); TypeResolver typeResolver = typeResolverMap.get(parentType.getName()); if (typeResolver == null) { typeResolver = parentType.getTypeResolver(); } return assertNotNull(typeResolver, () -> "There must be a type resolver for union " + parentType.getName()); } /** * This helps you transform the current {@link graphql.schema.GraphQLCodeRegistry} object into another one by starting a builder with all * the current values and allows you to transform it how you want. * * @param builderConsumer the consumer code that will be given a builder to transform * * @return a new GraphQLCodeRegistry object based on calling build on that builder */ public GraphQLCodeRegistry transform(Consumer builderConsumer) { Builder builder = newCodeRegistry(this); builderConsumer.accept(builder); return builder.build(); } /** * @return a new builder of {@link graphql.schema.GraphQLCodeRegistry} objects */ public static Builder newCodeRegistry() { return new Builder(); } /** * Returns a new builder of {@link graphql.schema.GraphQLCodeRegistry} objects based on the existing one * * @param existingCodeRegistry the existing code registry to use * * @return a new builder of {@link graphql.schema.GraphQLCodeRegistry} objects */ public static Builder newCodeRegistry(GraphQLCodeRegistry existingCodeRegistry) { return new Builder(existingCodeRegistry); } public static class Builder { private final Map> dataFetcherMap = new LinkedHashMap<>(); private final Map> systemDataFetcherMap = new LinkedHashMap<>(); private final Map typeResolverMap = new HashMap<>(); private GraphqlFieldVisibility fieldVisibility = DEFAULT_FIELD_VISIBILITY; private DataFetcherFactory defaultDataFetcherFactory = env -> PropertyDataFetcher.fetching(env.getFieldDefinition().getName()); private boolean changed = false; private Builder() { } private Builder(GraphQLCodeRegistry codeRegistry) { this.systemDataFetcherMap.putAll(codeRegistry.systemDataFetcherMap); this.dataFetcherMap.putAll(codeRegistry.dataFetcherMap); this.typeResolverMap.putAll(codeRegistry.typeResolverMap); this.fieldVisibility = codeRegistry.fieldVisibility; this.defaultDataFetcherFactory = codeRegistry.defaultDataFetcherFactory; } /** * A helper method to track if the builder changes from the point * at which this method was called. * * @return this builder for fluent code */ @Internal public Builder trackChanges() { changed = false; return this; } /** * @return true if the builder has changed since {@link #trackChanges()} was called */ @Internal public boolean hasChanged() { return changed; } private Builder markChanged() { changed = true; return this; } private Builder markChanged(boolean condition) { if (condition) { changed = true; } return this; } /** * Returns a data fetcher associated with a field within a container type * * @param parentType the container type * @param fieldDefinition the field definition * * @return the DataFetcher associated with this field. All fields have data fetchers */ public DataFetcher getDataFetcher(GraphQLFieldsContainer parentType, GraphQLFieldDefinition fieldDefinition) { return getDataFetcherImpl(FieldCoordinates.coordinates(parentType, fieldDefinition), fieldDefinition, dataFetcherMap, systemDataFetcherMap, defaultDataFetcherFactory); } /** * Returns a data fetcher associated with a field located at specified coordinates. * * @param coordinates the field coordinates * @param fieldDefinition the field definition * * @return the DataFetcher associated with this field. All fields have data fetchers */ public DataFetcher getDataFetcher(FieldCoordinates coordinates, GraphQLFieldDefinition fieldDefinition) { return getDataFetcherImpl(coordinates, fieldDefinition, dataFetcherMap, systemDataFetcherMap, defaultDataFetcherFactory); } /** * @return the default data fetcher factory associated with this code registry */ public DataFetcherFactory getDefaultDataFetcherFactory() { return defaultDataFetcherFactory; } /** * Returns true if the code registry contained a data fetcher at the specified co-ordinates * * @param coordinates the field coordinates * * @return the true if there is a data fetcher at those co-ordinates */ public boolean hasDataFetcher(FieldCoordinates coordinates) { return hasDataFetcherImpl(coordinates, dataFetcherMap, systemDataFetcherMap); } /** * Returns the type resolver associated with this interface type * * @param interfaceType the interface type * * @return a non null {@link graphql.schema.TypeResolver} */ public TypeResolver getTypeResolver(GraphQLInterfaceType interfaceType) { return getTypeResolverForInterface(interfaceType, typeResolverMap); } /** * Returns true of a type resolver has been registered for this type name * * @param typeName the name to check * * @return true if there is already a type resolver */ public boolean hasTypeResolver(String typeName) { return typeResolverMap.containsKey(typeName); } /** * Returns the type resolver associated with this union type * * @param unionType the union type * * @return a non null {@link graphql.schema.TypeResolver} */ public TypeResolver getTypeResolver(GraphQLUnionType unionType) { return getTypeResolverForUnion(unionType, typeResolverMap); } /** * Sets the data fetcher for a specific field inside a container type * * @param coordinates the field coordinates * @param dataFetcher the data fetcher code for that field * * @return this builder */ public Builder dataFetcher(FieldCoordinates coordinates, DataFetcher dataFetcher) { assertNotNull(dataFetcher); return dataFetcher(assertNotNull(coordinates), DataFetcherFactories.useDataFetcher(dataFetcher)); } /** * Sets the data fetcher for a specific field inside a container type * * @param parentType the container type * @param fieldDefinition the field definition * @param dataFetcher the data fetcher code for that field * * @return this builder */ public Builder dataFetcher(GraphQLFieldsContainer parentType, GraphQLFieldDefinition fieldDefinition, DataFetcher dataFetcher) { return dataFetcher(FieldCoordinates.coordinates(parentType.getName(), fieldDefinition.getName()), dataFetcher); } /** * Called to place system data fetchers (eg Introspection fields) into the mix * * @param coordinates the field coordinates * @param dataFetcher the data fetcher code for that field * * @return this builder */ public Builder systemDataFetcher(FieldCoordinates coordinates, DataFetcher dataFetcher) { assertNotNull(dataFetcher); assertNotNull(coordinates); coordinates.assertValidNames(); systemDataFetcherMap.put(coordinates.getFieldName(), DataFetcherFactories.useDataFetcher(dataFetcher)); return markChanged(); } /** * Sets the data fetcher factory for a specific field inside a container type * * @param coordinates the field coordinates * @param dataFetcherFactory the data fetcher factory code for that field * * @return this builder */ public Builder dataFetcher(FieldCoordinates coordinates, DataFetcherFactory dataFetcherFactory) { assertNotNull(dataFetcherFactory); assertNotNull(coordinates); coordinates.assertValidNames(); if (coordinates.isSystemCoordinates()) { systemDataFetcherMap.put(coordinates.getFieldName(), dataFetcherFactory); } else { dataFetcherMap.put(coordinates, dataFetcherFactory); } return markChanged(); } /** * Sets the data fetcher factory for a specific field inside a container type ONLY if not mapping has already been made * * @param coordinates the field coordinates * @param dataFetcher the data fetcher code for that field * * @return this builder */ public Builder dataFetcherIfAbsent(FieldCoordinates coordinates, DataFetcher dataFetcher) { if (!hasDataFetcher(coordinates)) { if (coordinates.isSystemCoordinates()) { systemDataFetcher(coordinates, dataFetcher); } else { dataFetcher(coordinates, dataFetcher); } return markChanged(); } return this; } /** * This allows you you to build all the data fetchers for the fields of a container type. * * @param parentTypeName the parent container type * @param fieldDataFetchers the map of field names to data fetchers * * @return this builder */ public Builder dataFetchers(String parentTypeName, Map> fieldDataFetchers) { assertNotNull(fieldDataFetchers); fieldDataFetchers.forEach((fieldName, dataFetcher) -> dataFetcher(coordinates(parentTypeName, fieldName), dataFetcher)); return markChanged(!fieldDataFetchers.isEmpty()); } /** * This is the default data fetcher factory that will be used for fields that do not have specific data fetchers attached. By default * {@link graphql.schema.PropertyDataFetcher} is used but you can have your own default via this method. * * @param defaultDataFetcherFactory the default data fetcher factory used * * @return this builder */ public Builder defaultDataFetcher(DataFetcherFactory defaultDataFetcherFactory) { this.defaultDataFetcherFactory = Assert.assertNotNull(defaultDataFetcherFactory); return markChanged(); } public Builder dataFetchers(GraphQLCodeRegistry codeRegistry) { this.dataFetcherMap.putAll(codeRegistry.dataFetcherMap); return markChanged(!codeRegistry.dataFetcherMap.isEmpty()); } public Builder typeResolver(GraphQLInterfaceType interfaceType, TypeResolver typeResolver) { typeResolverMap.put(interfaceType.getName(), typeResolver); return markChanged(); } public Builder typeResolverIfAbsent(GraphQLInterfaceType interfaceType, TypeResolver typeResolver) { if (!typeResolverMap.containsKey(interfaceType.getName())) { typeResolverMap.put(interfaceType.getName(), typeResolver); return markChanged(); } return this; } public Builder typeResolver(GraphQLUnionType unionType, TypeResolver typeResolver) { typeResolverMap.put(unionType.getName(), typeResolver); return markChanged(); } public Builder typeResolverIfAbsent(GraphQLUnionType unionType, TypeResolver typeResolver) { if (!typeResolverMap.containsKey(unionType.getName())) { typeResolverMap.put(unionType.getName(), typeResolver); return markChanged(); } return markChanged(); } public Builder typeResolver(String typeName, TypeResolver typeResolver) { typeResolverMap.put(assertValidName(typeName), typeResolver); return markChanged(); } public Builder typeResolvers(GraphQLCodeRegistry codeRegistry) { this.typeResolverMap.putAll(codeRegistry.typeResolverMap); return markChanged(!codeRegistry.typeResolverMap.isEmpty()); } public Builder fieldVisibility(GraphqlFieldVisibility fieldVisibility) { this.fieldVisibility = assertNotNull(fieldVisibility); return markChanged(); } public Builder clearDataFetchers() { dataFetcherMap.clear(); return markChanged(); } public Builder clearTypeResolvers() { typeResolverMap.clear(); return markChanged(); } public GraphQLCodeRegistry build() { return new GraphQLCodeRegistry(this); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy