@@ -2,22 +2,23 @@ package graphql.kickstart.tools.resolver
22
33import com.fasterxml.jackson.core.type.TypeReference
44import graphql.GraphQLContext
5- import graphql.TrivialDataFetcher
65import graphql.kickstart.tools.*
76import graphql.kickstart.tools.SchemaParserOptions.GenericWrapper
87import graphql.kickstart.tools.util.JavaType
98import graphql.kickstart.tools.util.coroutineScope
10- import graphql.kickstart.tools.util.isTrivialDataFetcher
119import graphql.kickstart.tools.util.unwrap
1210import graphql.language.*
1311import graphql.schema.DataFetcher
1412import graphql.schema.DataFetchingEnvironment
13+ import graphql.schema.GraphQLFieldDefinition
1514import graphql.schema.GraphQLTypeUtil.isScalar
15+ import graphql.schema.LightDataFetcher
1616import kotlinx.coroutines.future.future
1717import org.slf4j.LoggerFactory
1818import java.lang.reflect.InvocationTargetException
1919import java.lang.reflect.Method
2020import java.util.*
21+ import java.util.function.Supplier
2122import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
2223import kotlin.reflect.full.valueParameters
2324import kotlin.reflect.jvm.javaType
@@ -35,13 +36,9 @@ internal class MethodFieldResolver(
3536
3637 private val log = LoggerFactory .getLogger(javaClass)
3738
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 )
4542
4643 override fun createDataFetcher (): DataFetcher <* > {
4744 val args = mutableListOf<ArgumentPlaceholder >()
@@ -53,6 +50,7 @@ internal class MethodFieldResolver(
5350
5451 args.add { environment ->
5552 val source = environment.getSource<Any >()
53+ ? : throw ResolverError (" Expected source object to not be null!" )
5654 if (! expectedType.isAssignableFrom(source.javaClass)) {
5755 throw ResolverError (" Source type (${source.javaClass.name} ) is not expected type (${expectedType.name} )!" )
5856 }
@@ -97,7 +95,7 @@ internal class MethodFieldResolver(
9795 }
9896
9997 // Add DataFetchingEnvironment/Context argument
100- if (this .additionalLastArgument ) {
98+ if (this .hasAdditionalParameter ) {
10199 when (this .method.parameterTypes.last()) {
102100 null -> throw ResolverError (" Expected at least one argument but got none, this is most likely a bug with graphql-java-tools" )
103101 options.contextClass -> args.add { environment ->
@@ -114,15 +112,18 @@ internal class MethodFieldResolver(
114112 environment.getContext() // TODO: remove deprecated use in next major release
115113 }
116114 }
115+
117116 GraphQLContext ::class .java -> args.add { environment -> environment.graphQlContext }
118117 else -> args.add { environment -> environment }
119118 }
120119 }
121120
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)
124124 } 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)
126127 }
127128 }
128129
@@ -139,19 +140,23 @@ internal class MethodFieldResolver(
139140 return when (type) {
140141 is ListType -> List ::class .java.isAssignableFrom(this .genericType.getRawClass(genericParameterType))
141142 && isConcreteScalarType(environment, type.type, this .genericType.unwrapGenericType(genericParameterType))
143+
142144 is TypeName -> environment.graphQLSchema?.getType(type.name)?.let { isScalar(it) && type.name != " ID" }
143145 ? : false
146+
144147 is NonNullType -> isConcreteScalarType(environment, type.type, genericParameterType)
145148 else -> false
146149 }
147150 }
148151
149152 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+ )
155160 val returnValueMatch = TypeClassMatcher .PotentialMatch .returnValue(field.type, unwrappedGenericType, genericType, SchemaClassScanner .ReturnValueReference (method))
156161
157162 return field.inputValueDefinitions.mapIndexed { i, inputDefinition ->
@@ -183,68 +188,92 @@ internal class MethodFieldResolver(
183188 override fun toString () = " MethodFieldResolver{method=$method }"
184189}
185190
186- internal open class MethodFieldResolverDataFetcher (
191+ internal class MethodFieldResolverDataFetcher (
187192 private val sourceResolver : SourceResolver ,
188- method : Method ,
193+ private val method : Method ,
189194 private val args : List <ArgumentPlaceholder >,
190- private val options : SchemaParserOptions
195+ private val options : SchemaParserOptions ,
196+ private val isSuspendFunction : Boolean
191197) : DataFetcher<Any> {
192198
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-
209199 override fun get (environment : DataFetchingEnvironment ): Any? {
210- val source = sourceResolver(environment)
200+ val source = sourceResolver.resolve (environment, null )
211201 val args = this .args.map { it(environment) }.toTypedArray()
212202
213203 return if (isSuspendFunction) {
214204 environment.coroutineScope().future(options.coroutineContextProvider.provide()) {
215- invokeSuspend(source, resolverMethod , args)?.transformWithGenericWrapper(environment)
205+ invokeSuspend(source, method , args)?.transformWithGenericWrapper(options.genericWrappers) { environment }
216206 }
217207 } else {
218- invoke(resolverMethod , source, args)?.transformWithGenericWrapper(environment)
208+ invoke(method , source, args)?.transformWithGenericWrapper(options.genericWrappers) { environment }
219209 }
220210 }
221211
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 }
229238 }
230239
231240 /* *
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.
234242 */
235243 @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 )
238246 }
239247}
240248
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+ }
248277
249278private suspend inline fun invokeSuspend (target : Any , resolverMethod : Method , args : Array <Any ?>): Any? {
250279 return suspendCoroutineUninterceptedOrReturn { continuation ->
@@ -256,7 +285,7 @@ private fun invoke(method: Method, instance: Any, args: Array<Any?>): Any? {
256285 try {
257286 return method.invoke(instance, * args)
258287 } 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" )
260289 }
261290}
262291
0 commit comments