@@ -2,22 +2,23 @@ package graphql.kickstart.tools.resolver
2
2
3
3
import com.fasterxml.jackson.core.type.TypeReference
4
4
import graphql.GraphQLContext
5
- import graphql.TrivialDataFetcher
6
5
import graphql.kickstart.tools.*
7
6
import graphql.kickstart.tools.SchemaParserOptions.GenericWrapper
8
7
import graphql.kickstart.tools.util.JavaType
9
8
import graphql.kickstart.tools.util.coroutineScope
10
- import graphql.kickstart.tools.util.isTrivialDataFetcher
11
9
import graphql.kickstart.tools.util.unwrap
12
10
import graphql.language.*
13
11
import graphql.schema.DataFetcher
14
12
import graphql.schema.DataFetchingEnvironment
13
+ import graphql.schema.GraphQLFieldDefinition
15
14
import graphql.schema.GraphQLTypeUtil.isScalar
15
+ import graphql.schema.LightDataFetcher
16
16
import kotlinx.coroutines.future.future
17
17
import org.slf4j.LoggerFactory
18
18
import java.lang.reflect.InvocationTargetException
19
19
import java.lang.reflect.Method
20
20
import java.util.*
21
+ import java.util.function.Supplier
21
22
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
22
23
import kotlin.reflect.full.valueParameters
23
24
import kotlin.reflect.jvm.javaType
@@ -35,13 +36,9 @@ internal class MethodFieldResolver(
35
36
36
37
private val log = LoggerFactory .getLogger(javaClass)
37
38
38
- private val additionalLastArgument =
39
- try {
40
- (method.kotlinFunction?.valueParameters?.size
41
- ? : method.parameterCount) == (field.inputValueDefinitions.size + getIndexOffset() + 1 )
42
- } catch (e: InternalError ) {
43
- method.parameterCount == (field.inputValueDefinitions.size + getIndexOffset() + 1 )
44
- }
39
+ private val isSuspendFunction = method.isSuspendFunction()
40
+ private val numberOfParameters = method.kotlinFunction?.valueParameters?.size ? : method.parameterCount
41
+ private val hasAdditionalParameter = numberOfParameters == (field.inputValueDefinitions.size + getIndexOffset() + 1 )
45
42
46
43
override fun createDataFetcher (): DataFetcher <* > {
47
44
val args = mutableListOf<ArgumentPlaceholder >()
@@ -53,6 +50,7 @@ internal class MethodFieldResolver(
53
50
54
51
args.add { environment ->
55
52
val source = environment.getSource<Any >()
53
+ ? : throw ResolverError (" Expected source object to not be null!" )
56
54
if (! expectedType.isAssignableFrom(source.javaClass)) {
57
55
throw ResolverError (" Source type (${source.javaClass.name} ) is not expected type (${expectedType.name} )!" )
58
56
}
@@ -97,7 +95,7 @@ internal class MethodFieldResolver(
97
95
}
98
96
99
97
// Add DataFetchingEnvironment/Context argument
100
- if (this .additionalLastArgument ) {
98
+ if (this .hasAdditionalParameter ) {
101
99
when (this .method.parameterTypes.last()) {
102
100
null -> throw ResolverError (" Expected at least one argument but got none, this is most likely a bug with graphql-java-tools" )
103
101
options.contextClass -> args.add { environment ->
@@ -114,15 +112,18 @@ internal class MethodFieldResolver(
114
112
environment.getContext() // TODO: remove deprecated use in next major release
115
113
}
116
114
}
115
+
117
116
GraphQLContext ::class .java -> args.add { environment -> environment.graphQlContext }
118
117
else -> args.add { environment -> environment }
119
118
}
120
119
}
121
120
122
- return if (args.isEmpty() && isTrivialDataFetcher(this .method)) {
123
- TrivialMethodFieldResolverDataFetcher (getSourceResolver(), this .method, args, options)
121
+ return if (numberOfParameters > 0 || isSuspendFunction) {
122
+ // requires arguments and environment or is a suspend function
123
+ MethodFieldResolverDataFetcher (createSourceResolver(), this .method, args, options, isSuspendFunction)
124
124
} else {
125
- MethodFieldResolverDataFetcher (getSourceResolver(), this .method, args, options)
125
+ // if there are no parameters an optimized version of the data fetcher can be used
126
+ LightMethodFieldResolverDataFetcher (createSourceResolver(), this .method, options)
126
127
}
127
128
}
128
129
@@ -139,19 +140,23 @@ internal class MethodFieldResolver(
139
140
return when (type) {
140
141
is ListType -> List ::class .java.isAssignableFrom(this .genericType.getRawClass(genericParameterType))
141
142
&& isConcreteScalarType(environment, type.type, this .genericType.unwrapGenericType(genericParameterType))
143
+
142
144
is TypeName -> environment.graphQLSchema?.getType(type.name)?.let { isScalar(it) && type.name != " ID" }
143
145
? : false
146
+
144
147
is NonNullType -> isConcreteScalarType(environment, type.type, genericParameterType)
145
148
else -> false
146
149
}
147
150
}
148
151
149
152
override fun scanForMatches (): List <TypeClassMatcher .PotentialMatch > {
150
- val unwrappedGenericType = genericType.unwrapGenericType(try {
151
- method.kotlinFunction?.returnType?.javaType ? : method.genericReturnType
152
- } catch (e: InternalError ) {
153
- method.genericReturnType
154
- })
153
+ val unwrappedGenericType = genericType.unwrapGenericType(
154
+ try {
155
+ method.kotlinFunction?.returnType?.javaType ? : method.genericReturnType
156
+ } catch (e: InternalError ) {
157
+ method.genericReturnType
158
+ }
159
+ )
155
160
val returnValueMatch = TypeClassMatcher .PotentialMatch .returnValue(field.type, unwrappedGenericType, genericType, SchemaClassScanner .ReturnValueReference (method))
156
161
157
162
return field.inputValueDefinitions.mapIndexed { i, inputDefinition ->
@@ -183,68 +188,92 @@ internal class MethodFieldResolver(
183
188
override fun toString () = " MethodFieldResolver{method=$method }"
184
189
}
185
190
186
- internal open class MethodFieldResolverDataFetcher (
191
+ internal class MethodFieldResolverDataFetcher (
187
192
private val sourceResolver : SourceResolver ,
188
- method : Method ,
193
+ private val method : Method ,
189
194
private val args : List <ArgumentPlaceholder >,
190
- private val options : SchemaParserOptions
195
+ private val options : SchemaParserOptions ,
196
+ private val isSuspendFunction : Boolean
191
197
) : DataFetcher<Any> {
192
198
193
- private val resolverMethod = method
194
- private val isSuspendFunction = try {
195
- method.kotlinFunction?.isSuspend == true
196
- } catch (e: InternalError ) {
197
- false
198
- }
199
-
200
- private class CompareGenericWrappers {
201
- companion object : Comparator <GenericWrapper > {
202
- override fun compare (w1 : GenericWrapper , w2 : GenericWrapper ): Int = when {
203
- w1.type.isAssignableFrom(w2.type) -> 1
204
- else -> - 1
205
- }
206
- }
207
- }
208
-
209
199
override fun get (environment : DataFetchingEnvironment ): Any? {
210
- val source = sourceResolver(environment)
200
+ val source = sourceResolver.resolve (environment, null )
211
201
val args = this .args.map { it(environment) }.toTypedArray()
212
202
213
203
return if (isSuspendFunction) {
214
204
environment.coroutineScope().future(options.coroutineContextProvider.provide()) {
215
- invokeSuspend(source, resolverMethod , args)?.transformWithGenericWrapper(environment)
205
+ invokeSuspend(source, method , args)?.transformWithGenericWrapper(options.genericWrappers) { environment }
216
206
}
217
207
} else {
218
- invoke(resolverMethod , source, args)?.transformWithGenericWrapper(environment)
208
+ invoke(method , source, args)?.transformWithGenericWrapper(options.genericWrappers) { environment }
219
209
}
220
210
}
221
211
222
- private fun Any.transformWithGenericWrapper (environment : DataFetchingEnvironment ): Any? {
223
- return options.genericWrappers
224
- .asSequence()
225
- .filter { it.type.isInstance(this ) }
226
- .sortedWith(CompareGenericWrappers )
227
- .firstOrNull()
228
- ?.transformer?.invoke(this , environment) ? : this
212
+ /* *
213
+ * Function that returns the object used to fetch the data. It can be a DataFetcher or an entity.
214
+ */
215
+ @Suppress(" unused" )
216
+ fun getWrappedFetchingObject (environment : DataFetchingEnvironment ): Any {
217
+ return sourceResolver.resolve(environment, null )
218
+ }
219
+ }
220
+
221
+ /* *
222
+ * Similar to [MethodFieldResolverDataFetcher] but for light data fetchers which do not require the environment to be supplied unless generic wrappers are used.
223
+ */
224
+ internal class LightMethodFieldResolverDataFetcher (
225
+ private val sourceResolver : SourceResolver ,
226
+ private val method : Method ,
227
+ private val options : SchemaParserOptions
228
+ ) : LightDataFetcher<Any?> {
229
+
230
+ override fun get (fieldDefinition : GraphQLFieldDefinition ? , sourceObject : Any? , environmentSupplier : Supplier <DataFetchingEnvironment >): Any? {
231
+ val source = sourceResolver.resolve(null , sourceObject)
232
+
233
+ return invoke(method, source, emptyArray())?.transformWithGenericWrapper(options.genericWrappers, environmentSupplier)
234
+ }
235
+
236
+ override fun get (environment : DataFetchingEnvironment ): Any? {
237
+ return get(environment.fieldDefinition, sourceResolver.resolve(environment, null )) { environment }
229
238
}
230
239
231
240
/* *
232
- * Function that returns the object used to fetch the data.
233
- * It can be a DataFetcher or an entity.
241
+ * Function that returns the object used to fetch the data. It can be a DataFetcher or an entity.
234
242
*/
235
243
@Suppress(" unused" )
236
- open fun getWrappedFetchingObject (environment : DataFetchingEnvironment ): Any {
237
- return sourceResolver(environment)
244
+ fun getWrappedFetchingObject (environment : DataFetchingEnvironment ): Any {
245
+ return sourceResolver.resolve (environment, null )
238
246
}
239
247
}
240
248
241
- internal class TrivialMethodFieldResolverDataFetcher (
242
- sourceResolver : SourceResolver ,
243
- method : Method ,
244
- args : List <ArgumentPlaceholder >,
245
- options : SchemaParserOptions
246
- ) : MethodFieldResolverDataFetcher(sourceResolver, method, args, options),
247
- TrivialDataFetcher <Any > // just to mark it for tracing and optimizations
249
+ private fun Any.transformWithGenericWrapper (
250
+ genericWrappers : List <GenericWrapper >,
251
+ environmentSupplier : Supplier <DataFetchingEnvironment >
252
+ ): Any {
253
+ return genericWrappers
254
+ .asSequence()
255
+ .filter { it.type.isInstance(this ) }
256
+ .sortedWith(CompareGenericWrappers )
257
+ .firstOrNull()
258
+ ?.transformer?.invoke(this , environmentSupplier.get()) ? : this
259
+ }
260
+
261
+ private class CompareGenericWrappers {
262
+ companion object : Comparator <GenericWrapper > {
263
+ override fun compare (w1 : GenericWrapper , w2 : GenericWrapper ): Int = when {
264
+ w1.type.isAssignableFrom(w2.type) -> 1
265
+ else -> - 1
266
+ }
267
+ }
268
+ }
269
+
270
+ private fun Method.isSuspendFunction (): Boolean {
271
+ return try {
272
+ this .kotlinFunction?.isSuspend == true
273
+ } catch (e: InternalError ) {
274
+ false
275
+ }
276
+ }
248
277
249
278
private suspend inline fun invokeSuspend (target : Any , resolverMethod : Method , args : Array <Any ?>): Any? {
250
279
return suspendCoroutineUninterceptedOrReturn { continuation ->
@@ -256,7 +285,7 @@ private fun invoke(method: Method, instance: Any, args: Array<Any?>): Any? {
256
285
try {
257
286
return method.invoke(instance, * args)
258
287
} catch (e: InvocationTargetException ) {
259
- throw e.cause ? : RuntimeException (" Unknown error occurred while invoking resolver method" )
288
+ throw e.cause ? : RuntimeException (" Unknown error occurred while invoking resolver method" )
260
289
}
261
290
}
262
291
0 commit comments