aPureBase / KGraphQL

Pure Kotlin GraphQL implementation
https://kgraphql.io
MIT License
301 stars 58 forks source link

java.lang.IllegalArgumentException: object is not an instance of declaring class #75

Open Strum355 opened 4 years ago

Strum355 commented 4 years ago
package com.test.data

import org.elasticsearch.search.SearchHit

public data class Trace(val traceID: String, val spans: ArrayList<Span>) {
    companion object {
        fun fromSearchHits(traceID: String, hits: Array<SearchHit>): Trace {
            val spansArray = hits.map { Span.fromSearchHit(it) }.toTypedArray<Span>()
            return Trace(traceID, ArrayList(spansArray.sortedBy { it.startTime }))
        }
    }
}

public data class Span(val traceID: String, val spanID: String, val parentSpanID: String?, val duration: Int, val startTime: Long, val operationName: String, val serviceName: String, val logs: ArrayList<LogPoint>?, val tags: ArrayList<Tag>?) {
    companion object {
        fun fromSearchHit(hit: SearchHit): Span {
            val source = hit.sourceAsMap
            val traceID = source.get("traceID") as String
            val spanID = source.get("spanID") as String
            val duration = source.get("duration") as Int
            val startTime = source.get("startTime") as Long
            val operationName = source.get("operationName") as String
            val serviceName = (source.get("process") as Map<String, Any>)["serviceName"] as String
            val parentSpan = (source.get("references") as ArrayList<Map<String, String>>).getOrElse(0) { emptyMap() }["spanID"]
            val logs = (source.get("logs") as ArrayList<LogPoint>?)
            val tags = (source.get("tags") as ArrayList<Tag>?)
            return Span(traceID, spanID, parentSpan, duration, startTime, operationName, serviceName, logs, tags)
        }
    }
}

public data class Tag(val key: String, val type: String, val value: String)

public data class LogPoint(val timestamp: Int, val fields: List<LogPointField>)

public data class LogPointField(val key: String, val value: String)
package com.test.app

...
    val schema = KGraphQL.schema { 
        configure { 
            useDefaultPrettyPrinter = true
        }

        query("findTrace") { 
            resolver { traceID: String ->
                esRepo.getTraceByID(traceID)
            }.withArgs { 
                arg<String> { name = "traceID"}
            }
        }
    }

    routing {
        post("/graphql") {
            try {
                val body = call.receive<GraphQLBody>()
                println(body.query)
                println(body.variables)
                val resp = schema.execute(body.query, Gson().toJson(body.variables))
                call.respond(resp)
            } catch(e: HttpStatusException) {
                call.respondTextWriter(ContentType("application", "json"), e.statusCode) { }
            }
        }
    }
...

Output from println:

query findTrace($traceID: String!) {
  findTrace(traceID: $traceID) {
    traceID
    spans {
      spanID
      tags {
        key
        value
        __typename
      }
      __typename
    }
    __typename
  }
}

{traceID=646851f15cb2dad1}

Output from exception:

2020-02-18 16:22:56.984 [DefaultDispatcher-worker-1] ERROR ktor.application - Unhandled: POST - /graphql
java.lang.IllegalArgumentException: object is not an instance of declaring class
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at kotlin.reflect.jvm.internal.calls.CallerImpl$Method.callMethod(CallerImpl.kt:97)
        at kotlin.reflect.jvm.internal.calls.CallerImpl$Method$Instance.call(CallerImpl.kt:113)
        at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:106)
        at kotlin.reflect.jvm.internal.KProperty1Impl.get(KProperty1Impl.kt:35)
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor.createPropertyNode(ParallelRequestExecutor.kt:269)
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor.handleProperty(ParallelRequestExecutor.kt:232)
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor.createObjectNode(ParallelRequestExecutor.kt:209)
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor.createNode(ParallelRequestExecutor.kt:174)
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor$createNode$valuesMap$1.invokeSuspend(ParallelRequestExecutor.kt:157)
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor$createNode$valuesMap$1.invoke(ParallelRequestExecutor.kt)
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor$toMapAsync$2$invokeSuspend$$inlined$map$lambda$1.invokeSuspend(ParallelRequestExecutor.kt:82)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)
jeggy commented 4 years ago

I'm not able to reproduce this, which version of KGraphQL are you using?

I tried to create a unit test for this, but the test succeeds: https://gist.github.com/jeggy/ac6650a479d03d4e2122898cdb2f2ae4

Could you give a sample of what esRepo.getTraceByID(traceID) does return?

Strum355 commented 4 years ago

Using 0.9.2

Trace(traceID=646851f15cb2dad1, spans=[Span(traceID=646851f15cb2dad1, spanID=32b1133c2e838c56, parentSpanID=646851f15cb2dad1, duration=1701547, startTime=1581974975400889, operationName=banana, serviceName=example1, logs=[], tags=[{type=string, value=sample text, key=_tracestep_stack}, {type=bool, value=true, key=_tracestep_main}, {type=string, value=proto, key=internal.span.format}]), Span(traceID=646851f15cb2dad1, spanID=7381e3787bb621db, parentSpanID=32b1133c2e838c56, duration=1000503, startTime=1581974975401257, operationName=start-req2, serviceName=example2, logs=[], tags=[{type=string, value=sample text, key=_tracestep_stack}, {type=bool, value=false, key=_tracestep_main}, {type=string, value=proto, key=internal.span.format}])])
Strum355 commented 4 years ago

Tried with 0.10.0 just now, same issue

jeggy commented 4 years ago

I've released a new version 0.10.1, could you try with that version and you should get a different exception and paste that exception here?

Strum355 commented 4 years ago
com.apurebase.kgraphql.ExecutionException: Couldn't retrieve 'key' from class {type=string, value=proto, key=internal.span.format}}
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor.createPropertyNode(ParallelRequestExecutor.kt:249)
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor.handleProperty(ParallelRequestExecutor.kt:208)
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor.createObjectNode(ParallelRequestExecutor.kt:185)
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor.createNode(ParallelRequestExecutor.kt:149)
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor$createNode$valuesMap$1.invokeSuspend(ParallelRequestExecutor.kt:131)
        at com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor$createNode$valuesMap$1.invoke(ParallelRequestExecutor.kt)
        at com.apurebase.kgraphql.ExtensionsKt$toMapAsync$2$invokeSuspend$$inlined$map$lambda$1.invokeSuspend(Extensions.kt:46)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)
Strum355 commented 4 years ago

From this exception, Ive discovered that what i believed to be List\<Tag> was actually a HashMap. I wouldve thought that the cast would be checked at runtime, and that i wouldnt be able to assign whats actually a List\<HashMap> to List\<Tag> in a data class, but alas that seems to have been the case here :/ Not sure if this is intended behaviour for Kotlin or a bug Edited as \<xyz> was interpreted as HTML tags Edit 2: Type erasure is the conclusion ive come to, seeing as it was List\<HashMap> :smile:. Leaving the issue open in case you wish to make some internal changes for scenarios such as this

jeggy commented 4 years ago

I'm not really following. So you have your esRepo.getTraceByID(traceID) function which returns a Trace object. But somehow in your Trace.spans[0].tags which is defined as ArrayList<Tag>?, but holds a list( List<HashMap<?,?>>) value?

is that HashMap then a HashMap<String, Any> of something like this:

mapOf(
  "_tracestep_stack" to "sample text",
  "_tracestep_main" to false,
  "internal.span.format" to "proto"
)

I would love to provide a good exception for something like this.

Strum355 commented 4 years ago
fun fromSearchHit(hit: SearchHit): Span {
    val source = hit.sourceAsMap
    ...
    val tags = (source.get("tags") as ArrayList<Tag>?)
}

hit.sourceAsMap returns Map<String, Any>. When i was casting the return of source.get("tags") (of type Any) to ArrayList<Tag>, there is no runtime exception, even though the actual type is Arraylist<HashMap<Any, Any>> (those Any may have been String, but the idea still holds). This is possibly due to Type Erasure, where the generic type is not checked. The JVM sees that the cast from Any to ArrayList is valid, but doesn't check the generic type as it does not exist at runtime