Skip to content

Commit

Permalink
Merge pull request #904 from jord1e/automatic-dataloader-naming
Browse files Browse the repository at this point in the history
Automatic data loader naming on classes annotated with nameless @DgsDataLoader
  • Loading branch information
srinivasankavitha committed Mar 14, 2022
2 parents 2c682c0 + 5787ef5 commit 86e08f3
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 45 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Netflix, Inc.
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@

package com.netflix.graphql.dgs;

import com.netflix.graphql.dgs.internal.utils.DataLoaderNameUtil;
import org.springframework.stereotype.Component;

import java.lang.annotation.ElementType;
Expand All @@ -32,7 +33,16 @@
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface DgsDataLoader {
String name();

/**
* Used internally by {@link DataLoaderNameUtil#getDataLoaderName(Class, DgsDataLoader)}.
* <p>
* The <strong>value</strong> of this constant may change in future versions,
* and should therefore not be relied upon.
*/
String GENERATE_DATA_LOADER_NAME = "NETFLIX_DGS_GENERATE_DATALOADER_NAME";

String name() default GENERATE_DATA_LOADER_NAME;

boolean caching() default true;

Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Netflix, Inc.
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,6 +19,7 @@ package com.netflix.graphql.dgs
import com.netflix.graphql.dgs.context.DgsContext
import com.netflix.graphql.dgs.exceptions.MultipleDataLoadersDefinedException
import com.netflix.graphql.dgs.exceptions.NoDataLoaderFoundException
import com.netflix.graphql.dgs.internal.utils.DataLoaderNameUtil
import graphql.GraphQLContext
import graphql.cachecontrol.CacheControl
import graphql.execution.ExecutionId
Expand All @@ -29,7 +30,12 @@ import graphql.language.Document
import graphql.language.Field
import graphql.language.FragmentDefinition
import graphql.language.OperationDefinition
import graphql.schema.*
import graphql.schema.DataFetchingEnvironment
import graphql.schema.DataFetchingFieldSelectionSet
import graphql.schema.GraphQLFieldDefinition
import graphql.schema.GraphQLOutputType
import graphql.schema.GraphQLSchema
import graphql.schema.GraphQLType
import org.dataloader.DataLoader
import org.dataloader.DataLoaderRegistry
import java.util.*
Expand All @@ -48,13 +54,14 @@ class DgsDataFetchingEnvironment(private val dfe: DataFetchingEnvironment) : Dat
fun <K, V> getDataLoader(loaderClass: Class<*>): DataLoader<K, V> {
val annotation = loaderClass.getAnnotation(DgsDataLoader::class.java)
return if (annotation != null) {
dfe.getDataLoader(annotation.name)
dfe.getDataLoader(DataLoaderNameUtil.getDataLoaderName(loaderClass, annotation))
} else {
val loaders = loaderClass.fields.filter { it.isAnnotationPresent(DgsDataLoader::class.java) }
if (loaders.size > 1) throw MultipleDataLoadersDefinedException(loaderClass)
val loaderName = loaders
.firstOrNull()?.getAnnotation(DgsDataLoader::class.java)?.name
?: throw NoDataLoaderFoundException(loaderClass)
val loaderField: java.lang.reflect.Field = loaders
.firstOrNull() ?: throw NoDataLoaderFoundException(loaderClass)
val theAnnotation = loaderField.getAnnotation(DgsDataLoader::class.java)
val loaderName = theAnnotation.name
dfe.getDataLoader(loaderName)
}
}
Expand Down
@@ -0,0 +1,22 @@
/*
* Copyright 2021 Netflix, Inc.
*
* 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
*
* http://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 com.netflix.graphql.dgs.exceptions

import java.lang.reflect.Field

class DgsUnnamedDataLoaderOnFieldException(field: Field) :
RuntimeException("Field `${field.name}` in class `${field.declaringClass.name}` was annotated with @DgsDataLoader, but the data loader was not given a proper name")
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Netflix, Inc.
* Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,9 +20,17 @@ import com.netflix.graphql.dgs.DataLoaderInstrumentationExtensionProvider
import com.netflix.graphql.dgs.DgsComponent
import com.netflix.graphql.dgs.DgsDataLoader
import com.netflix.graphql.dgs.DgsDataLoaderRegistryConsumer
import com.netflix.graphql.dgs.exceptions.DgsUnnamedDataLoaderOnFieldException
import com.netflix.graphql.dgs.exceptions.InvalidDataLoaderTypeException
import com.netflix.graphql.dgs.exceptions.UnsupportedSecuredDataLoaderException
import org.dataloader.*
import com.netflix.graphql.dgs.internal.utils.DataLoaderNameUtil
import org.dataloader.BatchLoader
import org.dataloader.BatchLoaderWithContext
import org.dataloader.DataLoader
import org.dataloader.DataLoaderOptions
import org.dataloader.DataLoaderRegistry
import org.dataloader.MappedBatchLoader
import org.dataloader.MappedBatchLoaderWithContext
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.aop.support.AopUtils
Expand All @@ -37,10 +45,12 @@ import javax.annotation.PostConstruct
*/
class DgsDataLoaderProvider(private val applicationContext: ApplicationContext) {

private val batchLoaders = mutableListOf<Pair<BatchLoader<*, *>, DgsDataLoader>>()
private val batchLoadersWithContext = mutableListOf<Pair<BatchLoaderWithContext<*, *>, DgsDataLoader>>()
private val mappedBatchLoaders = mutableListOf<Pair<MappedBatchLoader<*, *>, DgsDataLoader>>()
private val mappedBatchLoadersWithContext = mutableListOf<Pair<MappedBatchLoaderWithContext<*, *>, DgsDataLoader>>()
private data class LoaderHolder<T>(val theLoader: T, val annotation: DgsDataLoader, val name: String)

private val batchLoaders = mutableListOf<LoaderHolder<BatchLoader<*, *>>>()
private val batchLoadersWithContext = mutableListOf<LoaderHolder<BatchLoaderWithContext<*, *>>>()
private val mappedBatchLoaders = mutableListOf<LoaderHolder<MappedBatchLoader<*, *>>>()
private val mappedBatchLoadersWithContext = mutableListOf<LoaderHolder<MappedBatchLoaderWithContext<*, *>>>()

fun buildRegistry(): DataLoaderRegistry {
return buildRegistryWithContextSupplier { null }
Expand All @@ -50,23 +60,22 @@ class DgsDataLoaderProvider(private val applicationContext: ApplicationContext)
val startTime = System.currentTimeMillis()

val dataLoaderRegistry = DataLoaderRegistry()
batchLoaders.forEach { dataLoaderRegistry.register(it.second.name, createDataLoader(it.first, it.second, dataLoaderRegistry)) }
batchLoaders.forEach {
dataLoaderRegistry.register(it.name, createDataLoader(it.theLoader, it.annotation, dataLoaderRegistry))
}
mappedBatchLoaders.forEach {
dataLoaderRegistry.register(
it.second.name,
createDataLoader(it.first, it.second, dataLoaderRegistry)
)
dataLoaderRegistry.register(it.name, createDataLoader(it.theLoader, it.annotation, dataLoaderRegistry))
}
batchLoadersWithContext.forEach {
dataLoaderRegistry.register(
it.second.name,
createDataLoader(it.first, it.second, contextSupplier, dataLoaderRegistry)
it.name,
createDataLoader(it.theLoader, it.annotation, contextSupplier, dataLoaderRegistry)
)
}
mappedBatchLoadersWithContext.forEach {
dataLoaderRegistry.register(
it.second.name,
createDataLoader(it.first, it.second, contextSupplier, dataLoaderRegistry)
it.name,
createDataLoader(it.theLoader, it.annotation, contextSupplier, dataLoaderRegistry)
)
}

Expand Down Expand Up @@ -96,15 +105,16 @@ class DgsDataLoaderProvider(private val applicationContext: ApplicationContext)
val annotation = field.getAnnotation(DgsDataLoader::class.java)
ReflectionUtils.makeAccessible(field)

if (annotation.name == DgsDataLoader.GENERATE_DATA_LOADER_NAME) {
throw DgsUnnamedDataLoaderOnFieldException(field)
}

fun <T : Any> createHolder(t: T): LoaderHolder<T> = LoaderHolder(t, annotation, annotation.name)
when (val get = field.get(dgsComponent)) {
is BatchLoader<*, *> ->
batchLoaders.add(get to annotation)
is BatchLoaderWithContext<*, *> ->
batchLoadersWithContext.add(get to annotation)
is MappedBatchLoader<*, *> ->
mappedBatchLoaders.add(get to annotation)
is MappedBatchLoaderWithContext<*, *> ->
mappedBatchLoadersWithContext.add(get to annotation)
is BatchLoader<*, *> -> batchLoaders.add(createHolder(get))
is BatchLoaderWithContext<*, *> -> batchLoadersWithContext.add(createHolder(get))
is MappedBatchLoader<*, *> -> mappedBatchLoaders.add(createHolder(get))
is MappedBatchLoaderWithContext<*, *> -> mappedBatchLoadersWithContext.add(createHolder(get))
else -> throw InvalidDataLoaderTypeException(dgsComponent::class.java)
}
}
Expand All @@ -116,15 +126,13 @@ class DgsDataLoaderProvider(private val applicationContext: ApplicationContext)
dataLoaders.values.forEach { dgsComponent ->
val javaClass = AopUtils.getTargetClass(dgsComponent)
val annotation = javaClass.getAnnotation(DgsDataLoader::class.java)
fun <T : Any> createHolder(t: T): LoaderHolder<T> =
LoaderHolder(t, annotation, DataLoaderNameUtil.getDataLoaderName(javaClass, annotation))
when (dgsComponent) {
is BatchLoader<*, *> ->
batchLoaders.add(Pair(dgsComponent, annotation))
is BatchLoaderWithContext<*, *> ->
batchLoadersWithContext.add(Pair(dgsComponent, annotation))
is MappedBatchLoader<*, *> ->
mappedBatchLoaders.add(Pair(dgsComponent, annotation))
is MappedBatchLoaderWithContext<*, *> ->
mappedBatchLoadersWithContext.add(Pair(dgsComponent, annotation))
is BatchLoader<*, *> -> batchLoaders.add(createHolder(dgsComponent))
is BatchLoaderWithContext<*, *> -> batchLoadersWithContext.add(createHolder(dgsComponent))
is MappedBatchLoader<*, *> -> mappedBatchLoaders.add(createHolder(dgsComponent))
is MappedBatchLoaderWithContext<*, *> -> mappedBatchLoadersWithContext.add(createHolder(dgsComponent))
else -> throw InvalidDataLoaderTypeException(dgsComponent::class.java)
}
}
Expand Down
@@ -0,0 +1,35 @@
/*
* Copyright 2022 Netflix, Inc.
*
* 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
*
* http://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 com.netflix.graphql.dgs.internal.utils

import com.netflix.graphql.dgs.DgsDataLoader
import com.netflix.graphql.dgs.Internal

@Internal
object DataLoaderNameUtil {

/**
* When the [annotation]'s [DgsDataLoader.name] is equal to [DgsDataLoader.GENERATE_DATA_LOADER_NAME],
* the [clazz]'s [Class.getSimpleName] will be used.
* In all other cases the [DgsDataLoader.name] method will be called on [annotation].
*
* This method does not verify that [annotation] belongs to [clazz] for performance reasons.
*/
fun getDataLoaderName(clazz: Class<*>, annotation: DgsDataLoader): String {
return if (annotation.name == DgsDataLoader.GENERATE_DATA_LOADER_NAME) clazz.simpleName else annotation.name
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2022 Netflix, Inc.
*
* 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
*
* http://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 com.netflix.graphql.dgs;

import org.dataloader.BatchLoader;

import java.util.*;
import java.util.concurrent.CompletableFuture;

@DgsComponent
public class ExampleBatchLoaderWithoutNameFromField {
@DgsDataLoader
public BatchLoader<String, String> batchLoader = keys -> CompletableFuture.supplyAsync(() -> {
List<String> values = new ArrayList<>();
values.add("a");
values.add("b");
values.add("c");
return values;
});

@DgsDataLoader
BatchLoader<String, String> privateBatchLoader = keys -> CompletableFuture.supplyAsync(() -> {
List<String> values = new ArrayList<>();
values.add("a");
values.add("b");
values.add("c");
return values;
});
}

0 comments on commit 86e08f3

Please sign in to comment.