Skip to content

Ignore proxies when scanning for field resolvers. #348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.apache.commons.lang3.reflect.FieldUtils
import org.slf4j.LoggerFactory
import java.lang.reflect.Modifier
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Proxy
import java.lang.reflect.Type
import kotlin.reflect.full.valueParameters
import kotlin.reflect.jvm.javaType
Expand All @@ -25,7 +26,7 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
private val log = LoggerFactory.getLogger(FieldResolverScanner::class.java)

fun getAllMethods(type: JavaType) =
(type.unwrap().declaredMethods.toList()
(type.unwrap().declaredNonProxyMethods.toList()
+ ClassUtils.getAllInterfaces(type.unwrap()).flatMap { it.methods.toList() }
+ ClassUtils.getAllSuperclasses(type.unwrap()).flatMap { it.methods.toList() })
.asSequence()
Expand Down
14 changes: 13 additions & 1 deletion src/main/kotlin/com/coxautodev/graphql/tools/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import graphql.language.ObjectTypeExtensionDefinition
import graphql.language.Type
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Proxy

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

internal typealias JavaType = java.lang.reflect.Type
internal typealias JavaMethod = java.lang.reflect.Method
internal typealias GraphQLLangType = graphql.language.Type<*>

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


internal val Class<*>.declaredNonProxyMethods: List<JavaMethod>
get() {
return when {
Proxy.isProxyClass(this) -> emptyList()
else -> this.declaredMethods.toList()
}
}

/**
* Simple heuristic to check is a method is a trivial data fetcher.
*
Expand All @@ -51,4 +62,5 @@ internal fun isTrivialDataFetcher(method: Method): Boolean {

private fun isBooleanGetter(method: Method) = (method.name.startsWith("is")
&& (method.returnType == java.lang.Boolean::class.java)
|| method.returnType == Boolean::class.java)
|| method.returnType == Boolean::class.java)

Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ import graphql.schema.Coercing
import graphql.schema.GraphQLScalarType
import org.junit.Assert
import org.junit.Test
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import java.util.*

class MethodFieldResolverTest {

@Test
fun `should handle scalar types as method input argument`() {
fun shouldHandleScalarTypesAsMethodInputArgument() {
val schema = SchemaParser.newParser()
.schemaString("""
scalar CustomScalar
Expand Down Expand Up @@ -44,7 +48,7 @@ class MethodFieldResolverTest {
}

@Test
fun `should handle lists of scalar types`() {
fun shouldHandleListsOfScalarTypes() {
val schema = SchemaParser.newParser()
.schemaString("""
scalar CustomScalar
Expand Down Expand Up @@ -76,6 +80,55 @@ class MethodFieldResolverTest {
Assert.assertEquals(6, result.getData<Map<String, Any>>()["test"])
}

@Test
fun shouldHandleProxies() {
val invocationHandler = object : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any>): Any {
return when (method.name) {
"toString" -> "Proxy$" + System.identityHashCode(this)
"hashCode" -> System.identityHashCode(this)
"equals" -> Proxy.isProxyClass(args[0].javaClass)
"test" -> (args[0] as List<*>).map { (it as CustomScalar).value.length }.sum()
else -> UnsupportedOperationException()
}
}
}

val resolver = Proxy.newProxyInstance(
MethodFieldResolverTest::class.java.classLoader,
arrayOf(Resolver::class.java, GraphQLQueryResolver::class.java),
invocationHandler
) as GraphQLQueryResolver

val schema = SchemaParser.newParser()
.schemaString("""
scalar CustomScalar
type Query {
test(input: [CustomScalar]): Int
}
""".trimIndent()
)
.scalars(customScalarType)
.resolvers(resolver)
.build()
.makeExecutableSchema()

val gql = GraphQL.newGraphQL(schema).build()

val result = gql
.execute(ExecutionInput.newExecutionInput()
.query("""
query Test(${"$"}input: [CustomScalar]) {
test(input: ${"$"}input)
}
""".trimIndent())
.variables(mapOf("input" to listOf("Foo", "Bar")))
.context(Object())
.root(Object()))

Assert.assertEquals(6, result.getData<Map<String, Any>>()["test"])
}

/**
* Custom Scalar Class type that doesn't work with Jackson serialization/deserialization
*/
Expand All @@ -90,6 +143,10 @@ class MethodFieldResolverTest {
}
}

interface Resolver {
fun test(scalars: List<CustomScalar>): Int
}

private val customScalarType: GraphQLScalarType = GraphQLScalarType.newScalar()
.name("CustomScalar")
.description("customScalar")
Expand Down