Skip to content
This repository was archived by the owner on May 16, 2019. It is now read-only.

Kotlin 1.3 release fixes #33

Merged
merged 2 commits into from
Nov 4, 2018
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
11 changes: 4 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ group 'com.github.pgutkowski'
version '0.3.0-beta'

buildscript {
ext.kotlin_version = '1.3.0-rc-116'
ext.kotlin_version = '1.3.0'

repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
maven {
url "http://dl.bintray.com/kotlin/kotlin-eap"
}
jcenter()
}
dependencies {
Expand Down Expand Up @@ -42,10 +39,10 @@ repositories {
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.30.2-eap13'
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'

compile "com.fasterxml.jackson.core:jackson-databind:2.9.3"
compile "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.3"
compile "com.fasterxml.jackson.core:jackson-databind:2.9.7"
compile "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.7"

compile "com.github.ben-manes.caffeine:caffeine:1.0.0"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,28 @@ import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.github.pgutkowski.kgraphql.configuration.SchemaConfiguration
import kotlinx.coroutines.CommonPool
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers


class SchemaConfigurationDSL {
var useDefaultPrettyPrinter: Boolean = false
var useCachingDocumentParser: Boolean = true
var objectMapper: ObjectMapper = jacksonObjectMapper()
var documentParserCacheMaximumSize : Long = 1000L
var acceptSingleValueAsArray : Boolean = true
var coroutineDispatcher: CoroutineDispatcher = CommonPool
var documentParserCacheMaximumSize: Long = 1000L
var acceptSingleValueAsArray: Boolean = true
var coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default

internal fun update(block : SchemaConfigurationDSL.() -> Unit) = block()
internal fun update(block: SchemaConfigurationDSL.() -> Unit) = block()

internal fun build() : SchemaConfiguration {
internal fun build(): SchemaConfiguration {
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, acceptSingleValueAsArray)
return SchemaConfiguration (
return SchemaConfiguration(
useCachingDocumentParser,
documentParserCacheMaximumSize,
objectMapper,
useDefaultPrettyPrinter,
coroutineDispatcher
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,38 @@ import com.github.pgutkowski.kgraphql.schema.scalar.serializeScalar
import com.github.pgutkowski.kgraphql.schema.structure2.Field
import com.github.pgutkowski.kgraphql.schema.structure2.InputValue
import com.github.pgutkowski.kgraphql.schema.structure2.Type
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.defer
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KProperty1


@Suppress("UNCHECKED_CAST") // For valid structure there is no risk of ClassCastException
class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor, CoroutineScope {

data class ExecutionContext(val variables: Variables, val requestContext: Context)

override val coroutineContext: CoroutineContext = Job()

private val argumentsHandler = ArgumentsHandler(schema)

private val jsonNodeFactory = JsonNodeFactory.instance

private val dispatcher = schema.configuration.coroutineDispatcher

private val objectWriter = schema.configuration.objectMapper.writer().let {
if(schema.configuration.useDefaultPrettyPrinter){
if (schema.configuration.useDefaultPrettyPrinter) {
it.withDefaultPrettyPrinter()
} else {
it
}
}

override suspend fun suspendExecute(plan : ExecutionPlan, variables: VariablesJson, context: Context) : String {
val root = jsonNodeFactory.objectNode()
override suspend fun suspendExecute(plan: ExecutionPlan, variables: VariablesJson, context: Context): String {
val root = jsonNodeFactory.objectNode()
val data = root.putObject("data")
val channel = Channel<Pair<Execution, JsonNode>>()
val jobs = plan
Expand All @@ -71,27 +75,27 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
try {
val (execution, jsonNode) = channel.receive()
resultMap.put(execution, jsonNode)
} catch(e : Exception){
jobs.forEach{ it.cancel() }
} catch (e: Exception) {
jobs.forEach { it.cancel() }
throw e
}
}
channel.close()

for(operation in plan){
for (operation in plan) {
data.set(operation.aliasOrKey, resultMap[operation])
}

return objectWriter.writeValueAsString(root)
}

override fun execute(plan : ExecutionPlan, variables: VariablesJson, context: Context) : String = runBlocking {
override fun execute(plan: ExecutionPlan, variables: VariablesJson, context: Context): String = runBlocking {
suspendExecute(plan, variables, context)
}

private suspend fun <T>writeOperation(ctx: ExecutionContext, node: Execution.Node, operation: FunctionWrapper<T>) : JsonNode {
private suspend fun <T> writeOperation(ctx: ExecutionContext, node: Execution.Node, operation: FunctionWrapper<T>): JsonNode {
node.field.checkAccess(null, ctx.requestContext)
val operationResult : T? = operation.invoke (
val operationResult: T? = operation.invoke(
funName = node.field.name,
receiver = null,
inputValues = node.field.arguments,
Expand All @@ -105,7 +109,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
private suspend fun <T> createUnionOperationNode(ctx: ExecutionContext, parent: T, node: Execution.Union, unionProperty: Field.Union<T>): JsonNode {
node.field.checkAccess(parent, ctx.requestContext)

val operationResult : Any? = unionProperty.invoke(
val operationResult: Any? = unionProperty.invoke(
funName = unionProperty.name,
receiver = parent,
inputValues = node.field.arguments,
Expand All @@ -115,21 +119,21 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {

val returnType = unionProperty.returnType.possibleTypes.find { it.isInstance(operationResult) }

if(returnType == null) throw ExecutionException (
"Unexpected type of union property value, expected one of : ${unionProperty.type.possibleTypes }." +
if (returnType == null) throw ExecutionException(
"Unexpected type of union property value, expected one of : ${unionProperty.type.possibleTypes}." +
" value was $operationResult"
)

return createNode(ctx, operationResult, node, returnType)
}

private suspend fun <T> createNode(ctx: ExecutionContext, value : T?, node: Execution.Node, returnType: Type) : JsonNode {
private suspend fun <T> createNode(ctx: ExecutionContext, value: T?, node: Execution.Node, returnType: Type): JsonNode {
return when {
value == null -> createNullNode(node, returnType)

//check value, not returnType, because this method can be invoked with element value
value is Collection<*> -> {
if(returnType.isList()){
if (returnType.isList()) {
val arrayNode = jsonNodeFactory.arrayNode(value.size)
value.forEach { element -> arrayNode.add(createNode(ctx, element, node, returnType.unwrapList())) }
arrayNode
Expand All @@ -153,7 +157,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
}
}

private fun <T> createSimpleValueNode(returnType: Type, value: T) : JsonNode {
private fun <T> createSimpleValueNode(returnType: Type, value: T): JsonNode {
val unwrapped = returnType.unwrapped()
return when (unwrapped) {
is Type.Scalar<*> -> {
Expand All @@ -175,10 +179,10 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
}
}

private suspend fun <T> createObjectNode(ctx: ExecutionContext, value : T, node: Execution.Node, type: Type): ObjectNode {
private suspend fun <T> createObjectNode(ctx: ExecutionContext, value: T, node: Execution.Node, type: Type): ObjectNode {
val objectNode = jsonNodeFactory.objectNode()
for(child in node.children){
if(child is Execution.Fragment){
for (child in node.children) {
if (child is Execution.Fragment) {
objectNode.setAll(handleFragment(ctx, value, child))
} else {
val (key, jsonNode) = handleProperty(ctx, value, child, type)
Expand All @@ -190,11 +194,11 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {

private suspend fun <T> handleProperty(ctx: ExecutionContext, value: T, child: Execution, type: Type): Pair<String, JsonNode?> {
when (child) {
//Union is subclass of Node so check it first
//Union is subclass of Node so check it first
is Execution.Union -> {
val field = type.unwrapped()[child.key]
?: throw IllegalStateException("Execution unit ${child.key} is not contained by operation return type")
if(field is Field.Union<*>){
if (field is Field.Union<*>) {
return child.aliasOrKey to createUnionOperationNode(ctx, value, child, field as Field.Union<T>)
} else {
throw ExecutionException("Unexpected non-union field for union execution node")
Expand All @@ -215,10 +219,10 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
val expectedType = container.condition.type
val include = determineInclude(ctx, container.directives)

if(include){
if(expectedType.kind == TypeKind.OBJECT || expectedType.kind == TypeKind.INTERFACE){
if(expectedType.isInstance(value)){
return container.elements.map { handleProperty(ctx, value, it, expectedType)}.toMap()
if (include) {
if (expectedType.kind == TypeKind.OBJECT || expectedType.kind == TypeKind.INTERFACE) {
if (expectedType.isInstance(value)) {
return container.elements.map { handleProperty(ctx, value, it, expectedType) }.toMap()
}
} else {
throw IllegalStateException("fragments can be specified on object types, interfaces, and unions")
Expand All @@ -228,18 +232,18 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
return emptyMap()
}

private suspend fun <T> createPropertyNode(ctx: ExecutionContext, parentValue: T, node: Execution.Node, field: Field) : JsonNode? {
private suspend fun <T> createPropertyNode(ctx: ExecutionContext, parentValue: T, node: Execution.Node, field: Field): JsonNode? {
val include = determineInclude(ctx, node.directives)
node.field.checkAccess(parentValue, ctx.requestContext)

if(include){
when(field){
is Field.Kotlin<*,*> -> {
if (include) {
when (field) {
is Field.Kotlin<*, *> -> {
field.kProperty as KProperty1<T, *>
val rawValue = field.kProperty.get(parentValue)
val value : Any?
value = if(field.transformation != null){
field.transformation.invoke (
val value: Any?
value = if (field.transformation != null) {
field.transformation.invoke(
funName = field.name,
receiver = rawValue,
inputValues = field.arguments,
Expand All @@ -263,7 +267,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
}
}

suspend fun <T>handleFunctionProperty(ctx: ExecutionContext, parentValue: T, node: Execution.Node, field: Field.Function<*, *>) : JsonNode {
suspend fun <T> handleFunctionProperty(ctx: ExecutionContext, parentValue: T, node: Execution.Node, field: Field.Function<*, *>): JsonNode {
val result = field.invoke(
funName = field.name,
receiver = parentValue,
Expand All @@ -282,7 +286,8 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
receiver = null,
args = arguments,
ctx = ctx
)?.include ?: throw ExecutionException("Illegal directive implementation returning null result")
)?.include
?: throw ExecutionException("Illegal directive implementation returning null result")
}?.reduce { acc, b -> acc && b } ?: true
}

Expand All @@ -296,7 +301,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
val transformedArgs = argumentsHandler.transformArguments(funName, inputValues, args, ctx.variables, ctx.requestContext)

//exceptions are not caught on purpose to pass up business logic errors
return if(hasReceiver){
return if (hasReceiver) {
suspendInvoke(receiver, *transformedArgs.toTypedArray())
} else {
suspendInvoke(*transformedArgs.toTypedArray())
Expand Down