Skip to content

Commit 3554357

Browse files
authored
Merge pull request #788 from graphql-java-kickstart/upgrade-graphql-java-to-22-3
Upgrade graphql-java to 22.3
2 parents bac018a + 5a6dca0 commit 3554357

File tree

10 files changed

+143
-132
lines changed

10 files changed

+143
-132
lines changed

pom.xml

+9-8
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
<properties>
1515
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1616
<java.version>11</java.version>
17-
<kotlin.version>1.8.21</kotlin.version>
18-
<kotlin-coroutines.version>1.7.3</kotlin-coroutines.version>
19-
<jackson.version>2.16.0</jackson.version>
20-
<graphql-java.version>21.3</graphql-java.version>
17+
<kotlin.version>2.0.20</kotlin.version>
18+
<kotlin-coroutines.version>1.9.0</kotlin-coroutines.version>
19+
<jackson.version>2.17.0</jackson.version>
20+
<graphql-java.version>22.3</graphql-java.version>
2121
<reactive-streams.version>1.0.4</reactive-streams.version>
2222

2323
<maven.compiler.source>${java.version}</maven.compiler.source>
@@ -83,15 +83,15 @@
8383
<version>3.29.2-GA</version>
8484
<scope>provided</scope>
8585
</dependency>
86-
<!-- Optional for supporting spring proxies -->
86+
<!-- Optional for supporting Spring proxies -->
8787
<dependency>
8888
<groupId>org.springframework</groupId>
8989
<artifactId>spring-aop</artifactId>
9090
<version>5.3.31</version>
9191
<scope>provided</scope>
9292
</dependency>
9393

94-
<!-- Test -->
94+
<!-- Test -->
9595
<dependency>
9696
<groupId>cglib</groupId>
9797
<artifactId>cglib-nodep</artifactId>
@@ -134,8 +134,8 @@
134134
<dependency>
135135
<groupId>org.jetbrains.kotlin</groupId>
136136
<artifactId>kotlin-stdlib</artifactId>
137-
<!--TODO remove this after upgrading kotlin-->
138137
<exclusions>
138+
<!-- kotlinx-coroutines-core-jvm brings more recent version -->
139139
<exclusion>
140140
<groupId>org.jetbrains</groupId>
141141
<artifactId>annotations</artifactId>
@@ -240,7 +240,8 @@
240240
<plugin>
241241
<groupId>org.codehaus.mojo</groupId>
242242
<artifactId>build-helper-maven-plugin</artifactId>
243-
<version>3.5.0</version>
243+
<!-- keep at 3.4.0 for JitPack to work -->
244+
<version>3.4.0</version>
244245
<executions>
245246
<execution>
246247
<id>add-test-source</id>

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

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package graphql.kickstart.tools.resolver
22

33
import graphql.kickstart.tools.*
4-
import graphql.kickstart.tools.GenericType
5-
import graphql.kickstart.tools.ResolverError
6-
import graphql.kickstart.tools.ResolverInfo
7-
import graphql.kickstart.tools.TypeClassMatcher
84
import graphql.kickstart.tools.util.JavaType
95
import graphql.language.FieldDefinition
106
import graphql.schema.DataFetcher
@@ -29,12 +25,15 @@ internal abstract class FieldResolver(
2925
/**
3026
* Add source resolver depending on whether or not this is a resolver method
3127
*/
32-
protected fun getSourceResolver(): SourceResolver {
28+
protected fun createSourceResolver(): SourceResolver {
3329
return if (this.search.source != null) {
34-
{ this.search.source }
30+
SourceResolver { _, _ -> this.search.source }
3531
} else {
36-
{ environment ->
37-
val source = environment.getSource<Any>()
32+
SourceResolver { environment, sourceObject ->
33+
// if source object is known, environment is null as an optimization (LightDataFetcher)
34+
val source = sourceObject
35+
?: environment?.getSource<Any>()
36+
?: throw ResolverError("Expected DataFetchingEnvironment and source object to not be null!")
3837

3938
if (!this.genericType.isAssignableFrom(source.javaClass)) {
4039
throw ResolverError("Expected source object to be an instance of '${this.genericType.getRawClass().name}' but instead got '${source.javaClass.name}'")
@@ -46,4 +45,7 @@ internal abstract class FieldResolver(
4645
}
4746
}
4847

49-
internal typealias SourceResolver = (DataFetchingEnvironment) -> Any
48+
fun interface SourceResolver {
49+
50+
fun resolve(environment: DataFetchingEnvironment?, source: Any?): Any
51+
}

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

+12-6
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> {
@@ -50,14 +53,17 @@ internal class MapFieldResolver(
5053
internal class MapFieldResolverDataFetcher(
5154
private val sourceResolver: SourceResolver,
5255
private val key: String
53-
) : DataFetcher<Any> {
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+
return get(environment.fieldDefinition, sourceResolver.resolve(environment, null)) { environment }
68+
}
6369
}

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

+88-59
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@ 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
98
import graphql.kickstart.tools.util.coroutineScope
10-
import graphql.kickstart.tools.util.isTrivialDataFetcher
119
import graphql.kickstart.tools.util.unwrap
1210
import graphql.language.*
1311
import graphql.schema.DataFetcher
1412
import graphql.schema.DataFetchingEnvironment
13+
import graphql.schema.GraphQLFieldDefinition
1514
import graphql.schema.GraphQLTypeUtil.isScalar
15+
import graphql.schema.LightDataFetcher
1616
import kotlinx.coroutines.future.future
1717
import org.slf4j.LoggerFactory
1818
import java.lang.reflect.InvocationTargetException
1919
import java.lang.reflect.Method
2020
import java.util.*
21+
import java.util.function.Supplier
2122
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
2223
import kotlin.reflect.full.valueParameters
2324
import 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

249278
private 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

Comments
 (0)