diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderContextProviderFactory.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderContextProviderFactory.java new file mode 100644 index 00000000..8696ccc6 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderContextProviderFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.execution; + +import org.dataloader.BatchLoaderContextProvider; + +import graphql.GraphQLContext; + +/** + * Contract to create a {@link BatchLoaderContextProvider} for a given + * {@link GraphQLContext}. + */ +public interface BatchLoaderContextProviderFactory { + + /** + * Create a new {@link BatchLoaderContextProvider} for the given + * {@link GraphQLContext}. + */ + BatchLoaderContextProvider create(GraphQLContext context); +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderRegistry.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderRegistry.java index 2c92f4aa..b3b20749 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderRegistry.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderRegistry.java @@ -21,10 +21,16 @@ import java.util.function.BiFunction; import java.util.function.Consumer; -import graphql.ExecutionInput; +import org.dataloader.BatchLoader; import org.dataloader.BatchLoaderContextProvider; import org.dataloader.BatchLoaderEnvironment; +import org.dataloader.BatchLoaderWithContext; import org.dataloader.DataLoaderOptions; +import org.dataloader.MappedBatchLoader; +import org.dataloader.MappedBatchLoaderWithContext; +import org.springframework.util.Assert; + +import graphql.ExecutionInput; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -52,7 +58,7 @@ public interface BatchLoaderRegistry extends DataLoaderRegistrar { *

When this method is used, the name for the * {@link org.dataloader.DataLoader} is automatically set as defined in * {@link RegistrationSpec#withName(String)}, and likewise, - * {@code @SchemaMapping} handler methods can transparenly locate and + * {@code @SchemaMapping} handler methods can transparently locate and * inject a {@code DataLoader} argument based on the generic type * {@code }. * @@ -79,6 +85,343 @@ public interface BatchLoaderRegistry extends DataLoaderRegistrar { */ RegistrationSpec forName(String name); + /** + * TODO + * + * @param spec + * @return + * @param + * @param + */ + BatchLoaderRegistry register(FunctionBatchLoaderSpec spec); + + /** + * TODO + * + * @param name + * @param loader + * @return + * @param + * @param + */ + default BatchLoaderRegistry register(String name, BiFunction, BatchLoaderEnvironment, Flux> loader) { + return this.register(name, null, loader); + } + + /** + * TODO + * + * @param name + * @param options + * @param loader + * @return + * @param + * @param + */ + BatchLoaderRegistry register(String name, DataLoaderOptions options, BiFunction, BatchLoaderEnvironment, Flux> loader); + + /** + * TODO + * + * @param name + * @param loader + * @return + * @param + * @param + */ + default BatchLoaderRegistry registerMapped(String name, BiFunction, BatchLoaderEnvironment, Mono>> loader) { + return this.registerMapped(name, null, loader); + } + + /** + * TODO + * + * @param name + * @param options + * @param loader + * @return + * @param + * @param + */ + BatchLoaderRegistry registerMapped(String name, DataLoaderOptions options, BiFunction, BatchLoaderEnvironment, Mono>> loader); + + /** + * Registers an externally created {@link BatchLoader}. + * + *

When this method is used, the name for the + * {@link org.dataloader.DataLoader} is automatically set as the + * camelcase version of the {@code loader} class. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the camelcase version of the {@code loader} class + * name. + * + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(BatchLoader loader) { + return this.register(BatchLoaderSpec.create(loader)); + } + + /** + * Registers an externally created {@link BatchLoaderWithContext}. + * + *

When this method is used, the name for the + * {@link org.dataloader.DataLoader} is automatically set as the + * camelcase version of the {@code loader} class. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the camelcase version of the {@code loader} class + * name. + * + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(BatchLoaderWithContext loader) { + return this.register(BatchLoaderSpec.create(loader)); + } + + /** + * Registers an externally created {@link MappedBatchLoader}. + * + *

When this method is used, the name for the + * {@link org.dataloader.DataLoader} is automatically set as the + * camelcase version of the {@code loader} class. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the camelcase version of the {@code loader} class + * name. + * + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(MappedBatchLoader loader) { + return this.register(BatchLoaderSpec.create(loader)); + } + + /** + * Registers an externally created {@link MappedBatchLoaderWithContext}. + * + *

When this method is used, the name for the + * {@link org.dataloader.DataLoader} is automatically set as the + * camelcase version of the {@code loader} class. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the camelcase version of the {@code loader} class + * name. + * + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(MappedBatchLoaderWithContext loader) { + return this.register(BatchLoaderSpec.create(loader)); + } + + /** + * Registers an externally created {@link SpringBatchLoaderWithOptions}. + * + *

When this method is used, the name and options for the + * {@link org.dataloader.DataLoader} are taken from provided {@code loader}. + * If the name is not provided by the {@code loader}, it is automatically + * set as the camelcase version of the {@code loader} class name. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the name provided by the {@code loader}. If the + * {@code loader} does not provide a name, then it must match the camelcase + * version of the {@code loader} class name. + * + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(SpringBatchLoaderWithOptions loader) { + return this.register(BatchLoaderSpec.create(loader)); + } + + /** + * Registers an externally created {@link BatchLoader} using the + * specified {@code name} for the correlating + * {@link org.dataloader.DataLoader}. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the name given here. + * + * @param name the name to use to register a {@code DataLoader} + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(String name, BatchLoader loader) { + return this.register(BatchLoaderSpec.create(name, loader)); + } + + /** + * Registers an externally created {@link BatchLoaderWithContext} using the + * specified {@code name} for the correlating + * {@link org.dataloader.DataLoader} that will be created. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the name given here. + * + * @param name the name to use to register a {@code DataLoader} + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(String name, BatchLoaderWithContext loader) { + return this.register(BatchLoaderSpec.create(name, loader)); + } + + /** + * Registers an externally created {@link MappedBatchLoader} using the + * specified {@code name} for the correlating + * {@link org.dataloader.DataLoader} that will be created. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the name given here. + * + * @param name the name to use to register a {@code DataLoader} + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(String name, MappedBatchLoader loader) { + return this.register(BatchLoaderSpec.create(name, loader)); + } + + /** + * Registers an externally created {@link MappedBatchLoaderWithContext} + * using the specified {@code name} for the correlating + * {@link org.dataloader.DataLoader} that will be created. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the name given here. + * + * @param name the name to use to register a {@code DataLoader} + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(String name, MappedBatchLoaderWithContext loader) { + return this.register(BatchLoaderSpec.create(name, loader)); + } + + /** + * Registers an externally created {@link BatchLoader} using the + * specified {@code name} and {@code options} for the correlating + * {@link org.dataloader.DataLoader}. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the name given here. + * + * @param name the name to use to register a {@code DataLoader} + * @param options the data loader options to use when creating the + * correlating {@link org.dataloader.DataLoader} + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(String name, DataLoaderOptions options, BatchLoader loader) { + Assert.notNull(name, "'name' is required"); + Assert.notNull(loader, "'loader' is required"); + + return this.register(BatchLoaderSpec.create(name, options, loader)); + } + + /** + * Registers an externally created {@link BatchLoaderWithContext} using the + * specified {@code name} and {@code options} for the correlating + * {@link org.dataloader.DataLoader}. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the name given here. + * + * @param name the name to use to register a {@code DataLoader} + * @param options the data loader options to use when creating the + * correlating {@link org.dataloader.DataLoader} + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(String name, DataLoaderOptions options, BatchLoaderWithContext loader) { + Assert.notNull(name, "'name' is required"); + Assert.notNull(loader, "'loader' is required"); + + return this.register(BatchLoaderSpec.create(name, options, loader)); + } + + /** + * Registers an externally created {@link MappedBatchLoader} using the + * specified {@code name} and {@code options} for the correlating + * {@link org.dataloader.DataLoader}. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the name given here. + * + * @param name the name to use to register a {@code DataLoader} + * @param options the data loader options to use when creating the + * correlating {@link org.dataloader.DataLoader} + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(String name, DataLoaderOptions options, MappedBatchLoader loader) { + Assert.notNull(name, "'name' is required"); + Assert.notNull(loader, "'loader' is required"); + + return this.register(BatchLoaderSpec.create(name, options, loader)); + } + + /** + * Registers an externally created {@link MappedBatchLoaderWithContext} using the + * specified {@code name} and {@code options} for the correlating + * {@link org.dataloader.DataLoader}. + * + *

Note: when this method is used, the parameter name + * of a {@code DataLoader} argument in a {@code @SchemaMapping} handler + * method needs to match the name given here. + * + * @param name the name to use to register a {@code DataLoader} + * @param options the data loader options to use when creating the + * correlating {@link org.dataloader.DataLoader} + * @param loader the externally created batch loader to register + * + * @return the registry + */ + default BatchLoaderRegistry register(String name, DataLoaderOptions options, MappedBatchLoaderWithContext loader) { + Assert.notNull(name, "'name' is required"); + Assert.notNull(loader, "'loader' is required"); + + return this.register(BatchLoaderSpec.create(name, options, loader)); + } + + /** + * Registers an externally created {@link BatchLoader} using a + * {@link BatchLoaderSpec} which contains all information needed to create + * and register a {@link org.dataloader.DataLoader}. + * + * @param spec the options to use to create the correlating + * {@code DataLoader} + * + * @return the registry + */ + BatchLoaderRegistry register(BatchLoaderSpec spec); /** * Spec to complete the registration of a batch loading function. diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderSpec.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderSpec.java new file mode 100644 index 00000000..ac4eae31 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderSpec.java @@ -0,0 +1,219 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.execution; + +import org.dataloader.BatchLoader; +import org.dataloader.BatchLoaderWithContext; +import org.dataloader.DataLoaderOptions; +import org.dataloader.MappedBatchLoader; +import org.dataloader.MappedBatchLoaderWithContext; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +/** + * A specification of options available for registering a batch loader + * that is to be used to eventually create a {@link org.dataloader.DataLoader}. + */ +public interface BatchLoaderSpec { + + /** + * @return name to be used for the {@link org.dataloader.DataLoader} + * that will be created for this spec. + */ + @NonNull + String getName(); + + /** + * @return data loader options to be used for the + * {@link org.dataloader.DataLoader} that will be created for this spec. + */ + @Nullable + DataLoaderOptions getDataLoaderOptions(); + + /** + * NOTE: Though the return type is {@link Object}, the + * returned value must be one of the following: + *

+ * + * @return the batch loader to be used for the + * {@link org.dataloader.DataLoader} that will be created for this spec. + */ + @NonNull + Object getLoader(); + + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(BatchLoader loader) { + return new DefaultBatchLoaderSpec(loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(String name, BatchLoader loader) { + return new DefaultBatchLoaderSpec(name, loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(DataLoaderOptions options, BatchLoader loader) { + return new DefaultBatchLoaderSpec(options, loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(String name, DataLoaderOptions options, BatchLoader loader) { + return new DefaultBatchLoaderSpec(name, options, loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(BatchLoaderWithContext loader) { + return new DefaultBatchLoaderSpec(loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(String name, BatchLoaderWithContext loader) { + return new DefaultBatchLoaderSpec(name, loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(DataLoaderOptions options, BatchLoaderWithContext loader) { + return new DefaultBatchLoaderSpec(options, loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(String name, DataLoaderOptions options, BatchLoaderWithContext loader) { + return new DefaultBatchLoaderSpec(name, options, loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(MappedBatchLoader loader) { + return new DefaultBatchLoaderSpec(loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(String name, MappedBatchLoader loader) { + return new DefaultBatchLoaderSpec(name, loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(DataLoaderOptions options, MappedBatchLoader loader) { + return new DefaultBatchLoaderSpec(options, loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(String name, DataLoaderOptions options, MappedBatchLoader loader) { + return new DefaultBatchLoaderSpec(name, options, loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(MappedBatchLoaderWithContext loader) { + return new DefaultBatchLoaderSpec(loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(String name, MappedBatchLoaderWithContext loader) { + return new DefaultBatchLoaderSpec(name, loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(DataLoaderOptions options, MappedBatchLoaderWithContext loader) { + return new DefaultBatchLoaderSpec(options, loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(String name, DataLoaderOptions options, MappedBatchLoaderWithContext loader) { + return new DefaultBatchLoaderSpec(name, options, loader); + } + + /** + * TODO + * + * @param loader + */ + static BatchLoaderSpec create(SpringBatchLoaderWithOptions loader) { + return new DefaultBatchLoaderSpec(loader.getName(), loader.getDataLoaderOptions(), loader); + } +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderWithContextAndOptions.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderWithContextAndOptions.java new file mode 100644 index 00000000..7dd01ac2 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderWithContextAndOptions.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.execution; + +import org.dataloader.BatchLoaderWithContext; + +/** + * Represents a {@link BatchLoaderWithContext} that can be configured with + * {@link org.dataloader.DataLoaderOptions}. + * + * @see SpringBatchLoaderWithOptions + */ +public non-sealed interface BatchLoaderWithContextAndOptions extends BatchLoaderWithContext, SpringBatchLoaderWithOptions { +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderWithOptions.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderWithOptions.java new file mode 100644 index 00000000..dd949ea6 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/BatchLoaderWithOptions.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.execution; + +import org.dataloader.BatchLoader; + +/** + * Represents a {@link BatchLoader} that can be configured with + * {@link org.dataloader.DataLoaderOptions}. + * + * @see SpringBatchLoaderWithOptions + */ +public non-sealed interface BatchLoaderWithOptions extends BatchLoader, SpringBatchLoaderWithOptions { +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistry.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistry.java index 8a52466e..27f8f732 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistry.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultBatchLoaderRegistry.java @@ -27,6 +27,8 @@ import graphql.GraphQLContext; import io.micrometer.context.ContextSnapshot; + +import org.dataloader.BatchLoader; import org.dataloader.BatchLoaderContextProvider; import org.dataloader.BatchLoaderEnvironment; import org.dataloader.BatchLoaderWithContext; @@ -34,6 +36,7 @@ import org.dataloader.DataLoaderFactory; import org.dataloader.DataLoaderOptions; import org.dataloader.DataLoaderRegistry; +import org.dataloader.MappedBatchLoader; import org.dataloader.MappedBatchLoaderWithContext; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -52,10 +55,16 @@ */ public class DefaultBatchLoaderRegistry implements BatchLoaderRegistry { + private static final BatchLoaderContextProvider DEFAULT_NULL_BATCH_LOADER_CONTEXT_PROVIDER = DataLoaderOptions.newOptions().getBatchLoaderContextProvider(); + private final List> loaders = new ArrayList<>(); private final List> mappedLoaders = new ArrayList<>(); + private final List specs = new ArrayList<>(); + + private final BatchLoaderContextProviderFactory defaultBatchLoaderContextProviderFactory; + private final Supplier defaultOptionsSupplier; @@ -63,7 +72,7 @@ public class DefaultBatchLoaderRegistry implements BatchLoaderRegistry { * Default constructor. */ public DefaultBatchLoaderRegistry() { - this(DataLoaderOptions::newOptions); + this(DataLoaderOptions::newOptions, null); } /** @@ -72,10 +81,19 @@ public DefaultBatchLoaderRegistry() { * @since 1.1.0 */ public DefaultBatchLoaderRegistry(Supplier defaultOptionsSupplier) { - Assert.notNull(defaultOptionsSupplier, "'defaultOptionsSupplier' is required"); - this.defaultOptionsSupplier = defaultOptionsSupplier; + this(defaultOptionsSupplier, null); } + /** + * TODO + */ + public DefaultBatchLoaderRegistry(Supplier defaultOptionsSupplier, BatchLoaderContextProviderFactory defaultBatchLoaderContextProviderFactory) { + Assert.notNull(defaultOptionsSupplier, "'defaultOptionsSupplier' is required"); + + this.defaultOptionsSupplier = defaultOptionsSupplier; + this.defaultBatchLoaderContextProviderFactory = (defaultBatchLoaderContextProviderFactory != null ? defaultBatchLoaderContextProviderFactory : ((context) -> (() -> context))); + } + @Override public RegistrationSpec forTypePair(Class keyType, Class valueType) { @@ -87,30 +105,113 @@ public RegistrationSpec forName(String name) { return new DefaultRegistrationSpec<>(name); } - @Override + @Override + public BatchLoaderRegistry register(FunctionBatchLoaderSpec spec) { + Assert.notNull(spec, "'spec' is required"); + + return (spec.isMapped() + ? this.registerMapped(spec.getName(), spec.getDataLoaderOptions(), spec.getMappedLoader()) + : this.register(spec.getName(), spec.getDataLoaderOptions(), spec.getNonMappedLoader())); + } + + @Override + public BatchLoaderRegistry register(String name, DataLoaderOptions options, BiFunction, BatchLoaderEnvironment, Flux> loader) { + Assert.notNull(name, "'name' is required"); + Assert.notNull(loader, "'loader' is required"); + + DefaultRegistrationSpec spec = new DefaultRegistrationSpec<>(name); + spec.withOptions(options); + spec.registerBatchLoader(loader); + + return this; + } + + @Override + public BatchLoaderRegistry registerMapped(String name, DataLoaderOptions options, BiFunction, BatchLoaderEnvironment, Mono>> loader) { + Assert.notNull(name, "'name' is required"); + Assert.notNull(loader, "'loader' is required"); + + DefaultRegistrationSpec spec = new DefaultRegistrationSpec<>(name); + spec.withOptions(options); + spec.registerMappedBatchLoader(loader); + + return this; + } + + @Override + public BatchLoaderRegistry register(BatchLoaderSpec spec) { + Assert.notNull(spec, "'spec' is required"); + this.specs.add(spec); + return this; + } + + @Override public void registerDataLoaders(DataLoaderRegistry registry, GraphQLContext context) { - BatchLoaderContextProvider contextProvider = () -> context; + BatchLoaderContextProvider defaultContextProvider = defaultBatchLoaderContextProviderFactory.create(context); + for (ReactorBatchLoader loader : this.loaders) { - DataLoaderOptions options = loader.getOptions(); - options = options.setBatchLoaderContextProvider(contextProvider); + this.validateDataLoaderNameIsUnique(registry, loader.getName()); + DataLoaderOptions options = createDataLoaderOptions(loader.getOptions()); + options = options.setBatchLoaderContextProvider(defaultContextProvider); DataLoader dataLoader = DataLoaderFactory.newDataLoader(loader, options); - registerDataLoader(loader.getName(), dataLoader, registry); + registry.register(loader.getName(), dataLoader); } + for (ReactorMappedBatchLoader loader : this.mappedLoaders) { - DataLoaderOptions options = loader.getOptions(); - options = options.setBatchLoaderContextProvider(contextProvider); + this.validateDataLoaderNameIsUnique(registry, loader.getName()); + DataLoaderOptions options = createDataLoaderOptions(loader.getOptions()); + options = options.setBatchLoaderContextProvider(defaultContextProvider); DataLoader dataLoader = DataLoaderFactory.newMappedDataLoader(loader, options); - registerDataLoader(loader.getName(), dataLoader, registry); + registry.register(loader.getName(), dataLoader); } - } - private void registerDataLoader(String name, DataLoader dataLoader, DataLoaderRegistry registry) { - if (registry.getDataLoader(name) != null) { - throw new IllegalStateException("More than one DataLoader named '" + name + "'"); - } - registry.register(name, dataLoader); + for (BatchLoaderSpec spec : this.specs) { + this.validateDataLoaderNameIsUnique(registry, spec.getName()); + + Object batchLoader = spec.getLoader(); + DataLoaderOptions options = createDataLoaderOptions(spec, defaultContextProvider); + + DataLoader dataLoader; + + if (batchLoader instanceof BatchLoader bl) { + dataLoader = DataLoaderFactory.newDataLoader(bl, options); + } else if (batchLoader instanceof MappedBatchLoader bl) { + dataLoader = DataLoaderFactory.newMappedDataLoader(bl, options); + } else if (batchLoader instanceof BatchLoaderWithContext bl) { + dataLoader = DataLoaderFactory.newDataLoader(bl, options); + } else if (batchLoader instanceof MappedBatchLoaderWithContext bl) { + dataLoader = DataLoaderFactory.newMappedDataLoader(bl, options); + } else { + throw new IllegalArgumentException("Batch loader provided is not of any batch loader type found in the dataloader library: '" + batchLoader.getClass().getCanonicalName() + "'"); + } + + registry.register(spec.getName(), dataLoader); + } } + private void validateDataLoaderNameIsUnique(DataLoaderRegistry registry, String dataLoaderName) { + if (registry.getDataLoader(dataLoaderName) != null) { + throw new IllegalStateException("More than one DataLoader named '" + dataLoaderName + "'"); + } + } + + private DataLoaderOptions createDataLoaderOptions(BatchLoaderSpec spec, BatchLoaderContextProvider defaultContextProvider) { + var options = this.createDataLoaderOptions(spec.getDataLoaderOptions()); + + var batchLoader = spec.getLoader(); + + if ((batchLoader instanceof BatchLoaderWithContext || batchLoader instanceof MappedBatchLoaderWithContext) + && options.getBatchLoaderContextProvider() == DEFAULT_NULL_BATCH_LOADER_CONTEXT_PROVIDER) { + options.setBatchLoaderContextProvider(defaultContextProvider); + } + + return options; + } + + private DataLoaderOptions createDataLoaderOptions(DataLoaderOptions options) { + return (options == null ? new DataLoaderOptions(defaultOptionsSupplier.get()) : options); + } + private class DefaultRegistrationSpec implements RegistrationSpec { diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultBatchLoaderSpec.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultBatchLoaderSpec.java new file mode 100644 index 00000000..3f99aa30 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultBatchLoaderSpec.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.execution; + +import java.beans.Introspector; + +import org.dataloader.DataLoaderOptions; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * TODO + */ +class DefaultBatchLoaderSpec implements BatchLoaderSpec { + + @NonNull + private final String name; + + @Nullable + private final DataLoaderOptions dataLoaderOptions; + + @NonNull + private final Object loader; + + DefaultBatchLoaderSpec(Object loader) { + this(null, null, loader); + } + + DefaultBatchLoaderSpec(String name, Object loader) { + this(name, null, loader); + } + + DefaultBatchLoaderSpec(DataLoaderOptions dataLoaderOptions, Object loader) { + this(null, dataLoaderOptions, loader); + } + + DefaultBatchLoaderSpec(String name, DataLoaderOptions dataLoaderOptions, Object loader) { + Assert.notNull(loader, "'loader' is required"); + + this.name = (name == null ? Introspector.decapitalize(loader.getClass().getSimpleName()).replaceFirst("BatchLoader$", "DataLoader") : name); + this.dataLoaderOptions = dataLoaderOptions; + this.loader = loader; + } + + @NonNull + @Override + public String getName() { + return this.name; + } + + @Nullable + @Override + public DataLoaderOptions getDataLoaderOptions() { + return this.dataLoaderOptions; + } + + @NonNull + @Override + public Object getLoader() { + return this.loader; + } +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultFunctionBatchLoaderSpec.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultFunctionBatchLoaderSpec.java new file mode 100644 index 00000000..ea5c61b8 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultFunctionBatchLoaderSpec.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.execution; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; + +import org.dataloader.BatchLoaderEnvironment; +import org.dataloader.DataLoaderOptions; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +final class DefaultFunctionBatchLoaderSpec implements FunctionBatchLoaderSpec { + + @NonNull + private final String name; + + @Nullable + private final DataLoaderOptions dataLoaderOptions; + + @NonNull + private final BiFunction loader; + + private final boolean isMapped; + + DefaultFunctionBatchLoaderSpec(String name, BiFunction loader, boolean isMapped) { + this(name, null, loader, isMapped); + } + + DefaultFunctionBatchLoaderSpec(String name, DataLoaderOptions dataLoaderOptions, BiFunction loader, boolean isMapped) { + Assert.notNull(name, "'name' is required"); + Assert.notNull(loader, "'loader' is required"); + + this.name = name; + this.dataLoaderOptions = dataLoaderOptions; + this.loader = loader; + this.isMapped = isMapped; + } + + @NonNull + @Override + public String getName() { + return this.name; + } + + @Nullable + @Override + public DataLoaderOptions getDataLoaderOptions() { + return this.dataLoaderOptions; + } + + @Nullable + @Override + public BiFunction, BatchLoaderEnvironment, Flux> getNonMappedLoader() { + try { + return (BiFunction, BatchLoaderEnvironment, Flux>) this.loader; + } catch (ClassCastException ignored) { + return null; + } + } + + @Nullable + @Override + public BiFunction, BatchLoaderEnvironment, Mono>> getMappedLoader() { + try { + return (BiFunction, BatchLoaderEnvironment, Mono>>) this.loader; + } catch (ClassCastException ignored) { + return null; + } + } + + @Override + public boolean isMapped() { + return this.isMapped; + } +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/FunctionBatchLoaderSpec.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/FunctionBatchLoaderSpec.java new file mode 100644 index 00000000..312c3394 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/FunctionBatchLoaderSpec.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.execution; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; + +import org.dataloader.BatchLoaderEnvironment; +import org.dataloader.DataLoaderOptions; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * Provides a way to create batch loader beans. + * TODO + * + * @param + * @param + */ +public sealed interface FunctionBatchLoaderSpec permits DefaultFunctionBatchLoaderSpec { + + /** + * @return name to be used for the {@link org.dataloader.DataLoader} + * that will be created for this spec. + */ + @NonNull + String getName(); + + /** + * @return data loader options to be used for the + * {@link org.dataloader.DataLoader} that will be created for this spec. + */ + @Nullable + DataLoaderOptions getDataLoaderOptions(); + + /** + * @return TODO + */ + @Nullable + BiFunction, BatchLoaderEnvironment, Flux> getNonMappedLoader(); + + /** + * @return TODO + */ + @Nullable + BiFunction, BatchLoaderEnvironment, Mono>> getMappedLoader(); + + /** + * @return true, if the loader is a mapped loader + */ + boolean isMapped(); + + static FunctionBatchLoaderSpec create(String name, BiFunction, BatchLoaderEnvironment, Flux> loader) { + return new DefaultFunctionBatchLoaderSpec<>(name, loader, false); + } + + static FunctionBatchLoaderSpec create(String name, DataLoaderOptions options, BiFunction, BatchLoaderEnvironment, Flux> loader) { + return new DefaultFunctionBatchLoaderSpec<>(name, options, loader, false); + } + + static FunctionBatchLoaderSpec createMapped(String name, BiFunction, BatchLoaderEnvironment, Mono>> loader) { + return new DefaultFunctionBatchLoaderSpec<>(name, loader, true); + } + + static FunctionBatchLoaderSpec createMapped(String name, DataLoaderOptions options, BiFunction, BatchLoaderEnvironment, Mono>> loader) { + return new DefaultFunctionBatchLoaderSpec<>(name, options, loader, true); + } +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/MappedBatchLoaderWithContextAndOptions.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/MappedBatchLoaderWithContextAndOptions.java new file mode 100644 index 00000000..a5ad1597 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/MappedBatchLoaderWithContextAndOptions.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.execution; + +import org.dataloader.MappedBatchLoaderWithContext; + +/** + * Represents a {@link MappedBatchLoaderWithContext} that can be configured with + * {@link org.dataloader.DataLoaderOptions}. + * + * @see SpringBatchLoaderWithOptions + */ +public non-sealed interface MappedBatchLoaderWithContextAndOptions extends MappedBatchLoaderWithContext, SpringBatchLoaderWithOptions { +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/MappedBatchLoaderWithOptions.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/MappedBatchLoaderWithOptions.java new file mode 100644 index 00000000..c02de740 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/MappedBatchLoaderWithOptions.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.execution; + +import org.dataloader.MappedBatchLoader; + +/** + * Represents a {@link MappedBatchLoader} that can be configured with + * {@link org.dataloader.DataLoaderOptions}. + * + * @see SpringBatchLoaderWithOptions + */ +public non-sealed interface MappedBatchLoaderWithOptions extends MappedBatchLoader, SpringBatchLoaderWithOptions { +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/SpringBatchLoaderWithOptions.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/SpringBatchLoaderWithOptions.java new file mode 100644 index 00000000..b3964574 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/SpringBatchLoaderWithOptions.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.execution; + +import org.dataloader.DataLoaderOptions; + +/** + * TODO + */ +public sealed interface SpringBatchLoaderWithOptions permits BatchLoaderWithContextAndOptions, BatchLoaderWithOptions, MappedBatchLoaderWithContextAndOptions, MappedBatchLoaderWithOptions { + + default String getName() { + return null; + } + + DataLoaderOptions getDataLoaderOptions(); +}