Skip to content

Commit 7a6d4db

Browse files
committed
Use LightDataFetcher where possible
1 parent f19e036 commit 7a6d4db

File tree

5 files changed

+97
-44
lines changed

5 files changed

+97
-44
lines changed

src/main/kotlin/graphql/kickstart/tools/resolver/FieldResolver.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ internal abstract class FieldResolver(
2929
/**
3030
* Add source resolver depending on whether or not this is a resolver method
3131
*/
32-
protected fun getSourceResolver(): SourceResolver {
32+
protected fun createSourceResolver(): SourceResolver {
3333
return if (this.search.source != null) {
34-
{ this.search.source }
34+
// environment object is ignored and can be null in this case
35+
SourceResolver { _ -> this.search.source }
3536
} else {
36-
{ environment ->
37-
val source = environment.getSource<Any>()
37+
SourceResolver { environment ->
38+
val nonNullEnvironment = environment
39+
?: throw ResolverError("Expected environment object to not be null!")
40+
val source = nonNullEnvironment.getSource<Any>()
3841
?: throw ResolverError("Expected source object to not be null!")
3942

4043
if (!this.genericType.isAssignableFrom(source.javaClass)) {
@@ -47,4 +50,7 @@ internal abstract class FieldResolver(
4750
}
4851
}
4952

50-
internal typealias SourceResolver = (DataFetchingEnvironment) -> Any
53+
fun interface SourceResolver {
54+
55+
fun resolve(environment: DataFetchingEnvironment?): Any
56+
}

src/main/kotlin/graphql/kickstart/tools/resolver/MapFieldResolver.kt

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import graphql.kickstart.tools.util.JavaType
88
import graphql.language.FieldDefinition
99
import graphql.schema.DataFetcher
1010
import graphql.schema.DataFetchingEnvironment
11+
import graphql.schema.GraphQLFieldDefinition
12+
import graphql.schema.LightDataFetcher
13+
import java.util.function.Supplier
1114

1215
/**
1316
* @author Nick Weedon
@@ -37,7 +40,7 @@ internal class MapFieldResolver(
3740
}
3841

3942
override fun createDataFetcher(): DataFetcher<*> {
40-
return MapFieldResolverDataFetcher(getSourceResolver(), field.name)
43+
return MapFieldResolverDataFetcher(createSourceResolver(), field.name)
4144
}
4245

4346
override fun scanForMatches(): List<TypeClassMatcher.PotentialMatch> {
@@ -49,15 +52,18 @@ internal class MapFieldResolver(
4952

5053
internal class MapFieldResolverDataFetcher(
5154
private val sourceResolver: SourceResolver,
52-
private val key: String
53-
) : DataFetcher<Any> {
55+
private val key: String,
56+
) : LightDataFetcher<Any> {
5457

55-
override fun get(environment: DataFetchingEnvironment): Any? {
56-
val resolvedSourceObject = sourceResolver(environment)
57-
if (resolvedSourceObject is Map<*, *>) {
58-
return resolvedSourceObject[key]
58+
override fun get(fieldDefinition: GraphQLFieldDefinition, sourceObject: Any, environmentSupplier: Supplier<DataFetchingEnvironment>): Any? {
59+
if (sourceObject is Map<*, *>) {
60+
return sourceObject[key]
5961
} else {
6062
throw RuntimeException("MapFieldResolver attempt to fetch a field from an object instance that was not a map")
6163
}
6264
}
65+
66+
override fun get(environment: DataFetchingEnvironment): Any? {
67+
throw UnsupportedOperationException("This method should not be called as this is a LightDataFetcher.")
68+
}
6369
}

src/main/kotlin/graphql/kickstart/tools/resolver/MethodFieldResolver.kt

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package graphql.kickstart.tools.resolver
22

33
import com.fasterxml.jackson.core.type.TypeReference
44
import graphql.GraphQLContext
5-
import graphql.TrivialDataFetcher
65
import graphql.kickstart.tools.*
76
import graphql.kickstart.tools.SchemaParserOptions.GenericWrapper
87
import graphql.kickstart.tools.util.JavaType
@@ -12,12 +11,15 @@ import graphql.kickstart.tools.util.unwrap
1211
import graphql.language.*
1312
import graphql.schema.DataFetcher
1413
import graphql.schema.DataFetchingEnvironment
14+
import graphql.schema.GraphQLFieldDefinition
1515
import graphql.schema.GraphQLTypeUtil.isScalar
16+
import graphql.schema.LightDataFetcher
1617
import kotlinx.coroutines.future.future
1718
import org.slf4j.LoggerFactory
1819
import java.lang.reflect.InvocationTargetException
1920
import java.lang.reflect.Method
2021
import java.util.*
22+
import java.util.function.Supplier
2123
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
2224
import kotlin.reflect.full.valueParameters
2325
import kotlin.reflect.jvm.javaType
@@ -122,9 +124,9 @@ internal class MethodFieldResolver(
122124
}
123125

124126
return if (args.isEmpty() && isTrivialDataFetcher(this.method)) {
125-
TrivialMethodFieldResolverDataFetcher(getSourceResolver(), this.method, args, options)
127+
LightMethodFieldResolverDataFetcher(createSourceResolver(), this.method, options)
126128
} else {
127-
MethodFieldResolverDataFetcher(getSourceResolver(), this.method, args, options)
129+
MethodFieldResolverDataFetcher(createSourceResolver(), this.method, args, options)
128130
}
129131
}
130132

@@ -203,17 +205,8 @@ internal open class MethodFieldResolverDataFetcher(
203205
false
204206
}
205207

206-
private class CompareGenericWrappers {
207-
companion object : Comparator<GenericWrapper> {
208-
override fun compare(w1: GenericWrapper, w2: GenericWrapper): Int = when {
209-
w1.type.isAssignableFrom(w2.type) -> 1
210-
else -> -1
211-
}
212-
}
213-
}
214-
215208
override fun get(environment: DataFetchingEnvironment): Any? {
216-
val source = sourceResolver(environment)
209+
val source = sourceResolver.resolve(environment)
217210
val args = this.args.map { it(environment) }.toTypedArray()
218211

219212
return if (isSuspendFunction) {
@@ -240,18 +233,59 @@ internal open class MethodFieldResolverDataFetcher(
240233
*/
241234
@Suppress("unused")
242235
open fun getWrappedFetchingObject(environment: DataFetchingEnvironment): Any {
243-
return sourceResolver(environment)
236+
return sourceResolver.resolve(environment)
244237
}
245238
}
246239

247-
// TODO use graphql.schema.LightDataFetcher
248-
internal class TrivialMethodFieldResolverDataFetcher(
249-
sourceResolver: SourceResolver,
250-
method: Method,
251-
args: List<ArgumentPlaceholder>,
252-
options: SchemaParserOptions,
253-
) : MethodFieldResolverDataFetcher(sourceResolver, method, args, options),
254-
TrivialDataFetcher<Any> // just to mark it for tracing and optimizations
240+
/**
241+
* Similar to [MethodFieldResolverDataFetcher] but for light data fetchers which do not require the environment to be supplied unless suspend functions or
242+
* generic wrappers are used.
243+
*/
244+
internal class LightMethodFieldResolverDataFetcher(
245+
private val sourceResolver: SourceResolver,
246+
private val method: Method,
247+
private val options: SchemaParserOptions,
248+
) : LightDataFetcher<Any?> {
249+
250+
private val resolverMethod = method
251+
private val isSuspendFunction = try {
252+
method.kotlinFunction?.isSuspend == true
253+
} catch (e: InternalError) {
254+
false
255+
}
256+
257+
override fun get(fieldDefinition: GraphQLFieldDefinition, sourceObject: Any, environmentSupplier: Supplier<DataFetchingEnvironment>): Any? {
258+
return if (isSuspendFunction) {
259+
environmentSupplier.get().coroutineScope().future(options.coroutineContextProvider.provide()) {
260+
invokeSuspend(sourceObject, resolverMethod, emptyArray())?.transformWithGenericWrapper(environmentSupplier)
261+
}
262+
} else {
263+
invoke(resolverMethod, sourceObject, emptyArray())?.transformWithGenericWrapper(environmentSupplier)
264+
}
265+
}
266+
267+
private fun Any.transformWithGenericWrapper(environment: Supplier<DataFetchingEnvironment>): Any? {
268+
return options.genericWrappers
269+
.asSequence()
270+
.filter { it.type.isInstance(this) }
271+
.sortedWith(CompareGenericWrappers)
272+
.firstOrNull()
273+
?.transformer?.invoke(this, environment.get()) ?: this
274+
}
275+
276+
override fun get(environment: DataFetchingEnvironment): Any? {
277+
throw UnsupportedOperationException("This method should not be called as this is a LightDataFetcher.")
278+
}
279+
}
280+
281+
private class CompareGenericWrappers {
282+
companion object : Comparator<GenericWrapper> {
283+
override fun compare(w1: GenericWrapper, w2: GenericWrapper): Int = when {
284+
w1.type.isAssignableFrom(w2.type) -> 1
285+
else -> -1
286+
}
287+
}
288+
}
255289

256290
private suspend inline fun invokeSuspend(target: Any, resolverMethod: Method, args: Array<Any?>): Any? {
257291
return suspendCoroutineUninterceptedOrReturn { continuation ->

src/main/kotlin/graphql/kickstart/tools/resolver/PropertyFieldResolver.kt

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import graphql.kickstart.tools.TypeClassMatcher
66
import graphql.language.FieldDefinition
77
import graphql.schema.DataFetcher
88
import graphql.schema.DataFetchingEnvironment
9+
import graphql.schema.GraphQLFieldDefinition
10+
import graphql.schema.LightDataFetcher
911
import java.lang.reflect.Field
12+
import java.util.function.Supplier
1013

1114
/**
1215
* @author Andrew Potter
@@ -15,11 +18,11 @@ internal class PropertyFieldResolver(
1518
field: FieldDefinition,
1619
search: FieldResolverScanner.Search,
1720
options: SchemaParserOptions,
18-
private val property: Field
21+
private val property: Field,
1922
) : FieldResolver(field, search, options, property.declaringClass) {
2023

2124
override fun createDataFetcher(): DataFetcher<*> {
22-
return PropertyFieldResolverDataFetcher(getSourceResolver(), property)
25+
return PropertyFieldResolverDataFetcher(createSourceResolver(), property)
2326
}
2427

2528
override fun scanForMatches(): List<TypeClassMatcher.PotentialMatch> {
@@ -28,7 +31,8 @@ internal class PropertyFieldResolver(
2831
field.type,
2932
property.genericType,
3033
genericType,
31-
SchemaClassScanner.FieldTypeReference(property.toString()))
34+
SchemaClassScanner.FieldTypeReference(property.toString())
35+
)
3236
)
3337
}
3438

@@ -37,10 +41,14 @@ internal class PropertyFieldResolver(
3741

3842
internal class PropertyFieldResolverDataFetcher(
3943
private val sourceResolver: SourceResolver,
40-
private val field: Field
41-
) : DataFetcher<Any> {
44+
private val field: Field,
45+
) : LightDataFetcher<Any> {
46+
47+
override fun get(fieldDefinition: GraphQLFieldDefinition, sourceObject: Any, environmentSupplier: Supplier<DataFetchingEnvironment>): Any? {
48+
return field.get(sourceResolver.resolve(null))
49+
}
4250

4351
override fun get(environment: DataFetchingEnvironment): Any? {
44-
return field.get(sourceResolver(environment))
52+
throw UnsupportedOperationException("This method should not be called as this is a LightDataFetcher.")
4553
}
4654
}

src/main/kotlin/graphql/kickstart/tools/util/Utils.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ internal fun getDocumentation(node: AbstractNode<*>, options: SchemaParserOption
6262
}
6363

6464
/**
65-
* Simple heuristic to check is a method is a trivial data fetcher.
65+
* Simple heuristic to check if a method is a trivial data fetcher.
6666
*
6767
* Requirements are:
68-
* prefixed with get
69-
* must have zero parameters
68+
* - prefixed with get
69+
* - must have zero parameters
7070
*/
7171
internal fun isTrivialDataFetcher(method: Method): Boolean {
7272
return (method.parameterCount == 0
@@ -80,4 +80,3 @@ private fun isBooleanGetter(method: Method) = (method.name.startsWith("is")
8080
|| method.returnType == Boolean::class.java)
8181

8282
internal fun String.snakeToCamelCase(): String = split("_").joinToString(separator = "") { it.replaceFirstChar(Char::titlecase) }
83-

0 commit comments

Comments
 (0)