diff --git a/src/main/kotlin/com/coxautodev/graphql/tools/FieldResolverScanner.kt b/src/main/kotlin/com/coxautodev/graphql/tools/FieldResolverScanner.kt index 8ca3a829..b16b5e99 100644 --- a/src/main/kotlin/com/coxautodev/graphql/tools/FieldResolverScanner.kt +++ b/src/main/kotlin/com/coxautodev/graphql/tools/FieldResolverScanner.kt @@ -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 @@ -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() diff --git a/src/main/kotlin/com/coxautodev/graphql/tools/Utils.kt b/src/main/kotlin/com/coxautodev/graphql/tools/Utils.kt index 2efd2562..4677ab88 100644 --- a/src/main/kotlin/com/coxautodev/graphql/tools/Utils.kt +++ b/src/main/kotlin/com/coxautodev/graphql/tools/Utils.kt @@ -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 @@ -16,6 +17,7 @@ import java.lang.reflect.ParameterizedType internal typealias GraphQLRootResolver = GraphQLResolver 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) { @@ -35,6 +37,15 @@ internal fun JavaType.unwrap(): Class = this as Class<*> } + +internal val Class<*>.declaredNonProxyMethods: List + get() { + return when { + Proxy.isProxyClass(this) -> emptyList() + else -> this.declaredMethods.toList() + } + } + /** * Simple heuristic to check is a method is a trivial data fetcher. * @@ -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) \ No newline at end of file + || method.returnType == Boolean::class.java) + diff --git a/src/test/kotlin/com/coxautodev/graphql/tools/MethodFieldResolverTest.kt b/src/test/kotlin/com/coxautodev/graphql/tools/MethodFieldResolverTest.kt index b10074a6..f894fd7e 100644 --- a/src/test/kotlin/com/coxautodev/graphql/tools/MethodFieldResolverTest.kt +++ b/src/test/kotlin/com/coxautodev/graphql/tools/MethodFieldResolverTest.kt @@ -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 @@ -44,7 +48,7 @@ class MethodFieldResolverTest { } @Test - fun `should handle lists of scalar types`() { + fun shouldHandleListsOfScalarTypes() { val schema = SchemaParser.newParser() .schemaString(""" scalar CustomScalar @@ -76,6 +80,55 @@ class MethodFieldResolverTest { Assert.assertEquals(6, result.getData>()["test"]) } + @Test + fun shouldHandleProxies() { + val invocationHandler = object : InvocationHandler { + override fun invoke(proxy: Any, method: Method, args: Array): 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>()["test"]) + } + /** * Custom Scalar Class type that doesn't work with Jackson serialization/deserialization */ @@ -90,6 +143,10 @@ class MethodFieldResolverTest { } } + interface Resolver { + fun test(scalars: List): Int + } + private val customScalarType: GraphQLScalarType = GraphQLScalarType.newScalar() .name("CustomScalar") .description("customScalar")