Skip to content

Commit f973984

Browse files
authored
Merge pull request #348 from tristanlins/hotfix-java-proxy
Ignore proxies when scanning for field resolvers.
2 parents 0e4b8a9 + 0aa962a commit f973984

File tree

3 files changed

+74
-4
lines changed

3 files changed

+74
-4
lines changed

src/main/kotlin/com/coxautodev/graphql/tools/FieldResolverScanner.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import org.apache.commons.lang3.reflect.FieldUtils
99
import org.slf4j.LoggerFactory
1010
import java.lang.reflect.Modifier
1111
import java.lang.reflect.ParameterizedType
12+
import java.lang.reflect.Proxy
1213
import java.lang.reflect.Type
1314
import kotlin.reflect.full.valueParameters
1415
import kotlin.reflect.jvm.javaType
@@ -25,7 +26,7 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
2526
private val log = LoggerFactory.getLogger(FieldResolverScanner::class.java)
2627

2728
fun getAllMethods(type: JavaType) =
28-
(type.unwrap().declaredMethods.toList()
29+
(type.unwrap().declaredNonProxyMethods.toList()
2930
+ ClassUtils.getAllInterfaces(type.unwrap()).flatMap { it.methods.toList() }
3031
+ ClassUtils.getAllSuperclasses(type.unwrap()).flatMap { it.methods.toList() })
3132
.asSequence()

src/main/kotlin/com/coxautodev/graphql/tools/Utils.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import graphql.language.ObjectTypeExtensionDefinition
88
import graphql.language.Type
99
import java.lang.reflect.Method
1010
import java.lang.reflect.ParameterizedType
11+
import java.lang.reflect.Proxy
1112

1213
/**
1314
* @author Andrew Potter
@@ -16,6 +17,7 @@ import java.lang.reflect.ParameterizedType
1617
internal typealias GraphQLRootResolver = GraphQLResolver<Void>
1718

1819
internal typealias JavaType = java.lang.reflect.Type
20+
internal typealias JavaMethod = java.lang.reflect.Method
1921
internal typealias GraphQLLangType = graphql.language.Type<*>
2022

2123
internal fun Type<*>.unwrap(): Type<*> = when (this) {
@@ -35,6 +37,15 @@ internal fun JavaType.unwrap(): Class<out Any> =
3537
this as Class<*>
3638
}
3739

40+
41+
internal val Class<*>.declaredNonProxyMethods: List<JavaMethod>
42+
get() {
43+
return when {
44+
Proxy.isProxyClass(this) -> emptyList()
45+
else -> this.declaredMethods.toList()
46+
}
47+
}
48+
3849
/**
3950
* Simple heuristic to check is a method is a trivial data fetcher.
4051
*
@@ -51,4 +62,5 @@ internal fun isTrivialDataFetcher(method: Method): Boolean {
5162

5263
private fun isBooleanGetter(method: Method) = (method.name.startsWith("is")
5364
&& (method.returnType == java.lang.Boolean::class.java)
54-
|| method.returnType == Boolean::class.java)
65+
|| method.returnType == Boolean::class.java)
66+

src/test/kotlin/com/coxautodev/graphql/tools/MethodFieldResolverTest.kt

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ import graphql.schema.Coercing
77
import graphql.schema.GraphQLScalarType
88
import org.junit.Assert
99
import org.junit.Test
10+
import java.lang.reflect.InvocationHandler
11+
import java.lang.reflect.Method
12+
import java.lang.reflect.Proxy
13+
import java.util.*
1014

1115
class MethodFieldResolverTest {
1216

1317
@Test
14-
fun `should handle scalar types as method input argument`() {
18+
fun shouldHandleScalarTypesAsMethodInputArgument() {
1519
val schema = SchemaParser.newParser()
1620
.schemaString("""
1721
scalar CustomScalar
@@ -44,7 +48,7 @@ class MethodFieldResolverTest {
4448
}
4549

4650
@Test
47-
fun `should handle lists of scalar types`() {
51+
fun shouldHandleListsOfScalarTypes() {
4852
val schema = SchemaParser.newParser()
4953
.schemaString("""
5054
scalar CustomScalar
@@ -76,6 +80,55 @@ class MethodFieldResolverTest {
7680
Assert.assertEquals(6, result.getData<Map<String, Any>>()["test"])
7781
}
7882

83+
@Test
84+
fun shouldHandleProxies() {
85+
val invocationHandler = object : InvocationHandler {
86+
override fun invoke(proxy: Any, method: Method, args: Array<out Any>): Any {
87+
return when (method.name) {
88+
"toString" -> "Proxy$" + System.identityHashCode(this)
89+
"hashCode" -> System.identityHashCode(this)
90+
"equals" -> Proxy.isProxyClass(args[0].javaClass)
91+
"test" -> (args[0] as List<*>).map { (it as CustomScalar).value.length }.sum()
92+
else -> UnsupportedOperationException()
93+
}
94+
}
95+
}
96+
97+
val resolver = Proxy.newProxyInstance(
98+
MethodFieldResolverTest::class.java.classLoader,
99+
arrayOf(Resolver::class.java, GraphQLQueryResolver::class.java),
100+
invocationHandler
101+
) as GraphQLQueryResolver
102+
103+
val schema = SchemaParser.newParser()
104+
.schemaString("""
105+
scalar CustomScalar
106+
type Query {
107+
test(input: [CustomScalar]): Int
108+
}
109+
""".trimIndent()
110+
)
111+
.scalars(customScalarType)
112+
.resolvers(resolver)
113+
.build()
114+
.makeExecutableSchema()
115+
116+
val gql = GraphQL.newGraphQL(schema).build()
117+
118+
val result = gql
119+
.execute(ExecutionInput.newExecutionInput()
120+
.query("""
121+
query Test(${"$"}input: [CustomScalar]) {
122+
test(input: ${"$"}input)
123+
}
124+
""".trimIndent())
125+
.variables(mapOf("input" to listOf("Foo", "Bar")))
126+
.context(Object())
127+
.root(Object()))
128+
129+
Assert.assertEquals(6, result.getData<Map<String, Any>>()["test"])
130+
}
131+
79132
/**
80133
* Custom Scalar Class type that doesn't work with Jackson serialization/deserialization
81134
*/
@@ -90,6 +143,10 @@ class MethodFieldResolverTest {
90143
}
91144
}
92145

146+
interface Resolver {
147+
fun test(scalars: List<CustomScalar>): Int
148+
}
149+
93150
private val customScalarType: GraphQLScalarType = GraphQLScalarType.newScalar()
94151
.name("CustomScalar")
95152
.description("customScalar")

0 commit comments

Comments
 (0)