Skip to content
Merged
27 changes: 27 additions & 0 deletions src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
import com.fasterxml.jackson.databind.JsonNode
import graphql.kickstart.tools.resolver.FieldResolver
import graphql.kickstart.tools.resolver.FieldResolverScanner
import graphql.kickstart.tools.resolver.MethodFieldResolver
import graphql.kickstart.tools.resolver.PropertyFieldResolver
import graphql.kickstart.tools.util.*
import graphql.language.*
import graphql.schema.GraphQLScalarType
import graphql.schema.idl.ScalarInfo
import org.apache.commons.lang3.ClassUtils
import org.apache.commons.lang3.reflect.TypeUtils
import org.slf4j.LoggerFactory
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.lang.reflect.ParameterizedType

/**
Expand Down Expand Up @@ -524,6 +528,7 @@
private fun scanResolverInfoForPotentialMatches(type: ObjectTypeDefinition, resolverInfo: ResolverInfo) {
type.withExtensions(typeExtensions).flatMap { it.fieldDefinitions }.forEach { field ->
val fieldResolver = fieldResolverScanner.findFieldResolver(field, resolverInfo)
warnIfNotCallableOnResolver(type, fieldResolver)

fieldResolversByType.getOrPut(type) { mutableMapOf() }[fieldResolver.field] = fieldResolver

Expand All @@ -533,6 +538,28 @@
}
}

// a JDK dynamic proxy only implements its target's interfaces, so the target's own methods and fields can't be called on it
// TODO throw a FieldResolverError instead in 15.0.0 (#868)

Check warning on line 542 in src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this TODO comment.

See more on https://sonarcloud.io/project/issues?id=graphql-java-kickstart_graphql-java-tools&issues=AaFMwqdp5DhrfaQ-MAg9&open=AaFMwqdp5DhrfaQ-MAg9&pullRequest=853
private fun warnIfNotCallableOnResolver(type: ObjectTypeDefinition, fieldResolver: FieldResolver) {
val source = fieldResolver.search.source ?: return
val member: Member = when (fieldResolver) {
is MethodFieldResolver -> fieldResolver.method
is PropertyFieldResolver -> fieldResolver.property
else -> return
}
if (Modifier.isStatic(member.modifiers) || member.declaringClass.isInstance(source)) {
return
}

// class-based proxies don't delegate field reads to the target, so a field needs a getter too
val fix = if (member is Field) "Add a getter for the field and use class-based proxies" else "Use class-based proxies instead"
log.warn(
"Field '${type.name}.${fieldResolver.field.name}' resolves to ${member.declaringClass.name}.${member.name}, but the resolver is a " +
"${source.javaClass.name}, which isn't a ${member.declaringClass.name}, so every query of the field will fail. This happens " +
"with JDK dynamic proxies, which only implement interfaces. $fix, e.g. proxyTargetClass = true in Spring."
)
}

private fun handleFoundType(match: TypeClassMatcher.Match) = when (match) {
is TypeClassMatcher.ScalarMatch -> handleFoundScalarType(match.type)
is TypeClassMatcher.ValidMatch -> handleFoundType(match.type, match.javaType, match.reference)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal class PropertyFieldResolver(
field: FieldDefinition,
search: FieldResolverScanner.Search,
options: SchemaParserOptions,
private val property: Field
val property: Field
) : FieldResolver(field, search, options) {

override fun createDataFetcher(): DataFetcher<*> {
Expand Down
Loading