Skip to content

GraalVM example integration #1742

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 1 commit into from
Apr 11, 2023
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
78 changes: 78 additions & 0 deletions .github/workflows/graalvm-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: GraaLVM Integration Tests

on:
workflow_call:
pull_request:
branches:
- master
paths:
- 'generator/**'
- 'servers/**'
- 'plugins/**'
- 'integration/graalvm/**'

jobs:
graalvm-integration:
timeout-minutes: 20
runs-on: ubuntu-latest
defaults:
run:
working-directory: integration/graalvm
strategy:
matrix:
server: ['ktor-graalvm-server']

steps:
- name: Checkout Repository
uses: actions/checkout@v3

- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1

- name: Setup GraalVM
uses: graalvm/setup-graalvm@v1
with:
version: '22.3.1'
java-version: '17'
components: 'native-image'
native-image-job-reports: 'true'

- name: Set up Gradle cache
uses: gradle/gradle-build-action@v2

- name: Build server
run: ./gradlew :${server}:build :${server}:nativeCompile
env:
server: ${{ matrix.server }}

- name: Build and start the native image
id: start_server
run: |
set -x
echo "starting server"
./${server}/build/native/nativeCompile/${server} &
echo "SERVER_PID=$(echo $!)" >> $GITHUB_OUTPUT
env:
server: ${{ matrix.server }}

- name: Integration Test
run: |
echo "sending a test query"
curl --request POST \
--verbose \
--header 'content-type: application/json' \
--url http://localhost:8080/graphql \
--data '{"query":"query($inputArg: InputOnlyInput){ inputTypeQuery(arg: $inputArg) }","variables":{"inputArg": { "id": 123 }}}' \
> response.json
echo "received GraphQL response"
cat response.json
echo "verifying response"
jq -e '.data.inputTypeQuery == "InputOnly(id=123)"' response.json
- name: Stop server
if: ${{ always() }}
run: |
echo "shutting down server"
kill -9 ${{ steps.start_server.outputs.SERVER_PID }}
20 changes: 20 additions & 0 deletions integration/graalvm/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import java.util.Properties

allprojects {
repositories {
mavenCentral()
mavenLocal {
content {
includeGroup("com.expediagroup")
}
}
}

val properties = Properties()
properties.load(File(rootDir.parentFile.parent, "gradle.properties").inputStream())
for ((key, value) in properties) {
if (!project.ext.has(key.toString())) {
project.ext[key.toString()] = value
}
}
}
6 changes: 6 additions & 0 deletions integration/graalvm/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
group = com.expediagroup.graalvm

org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g
org.gradle.caching=true
org.gradle.parallel=true

1 change: 1 addition & 0 deletions integration/graalvm/gradle/wrapper
1 change: 1 addition & 0 deletions integration/graalvm/gradlew
53 changes: 53 additions & 0 deletions integration/graalvm/ktor-graalvm-server/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import com.expediagroup.graphql.plugin.gradle.graphql
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

@Suppress("DSL_SCOPE_VIOLATION") // TODO: remove once KTIJ-19369 / Gradle#22797 is fixed
plugins {
alias(libs.plugins.kotlin.jvm)
application
alias(libs.plugins.graalvm.native)
id("com.expediagroup.graphql")
}

dependencies {
implementation("com.expediagroup", "graphql-kotlin-ktor-server")
implementation("com.expediagroup", "graphql-kotlin-hooks-provider")
implementation(libs.logback)
implementation(libs.ktor.server.cio)
testImplementation(libs.junit.api)
testImplementation(libs.kotlin.test)
testImplementation(libs.ktor.client.content)
testImplementation(libs.ktor.server.test.host)
}

tasks.test {
useJUnitPlatform()
}

tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "17"
}

application {
mainClass.set("com.expediagroup.graalvm.ktor.ApplicationKt")
}

graalvmNative {
toolchainDetection.set(false)
binaries {
named("main") {
verbose.set(true)
buildArgs.add("--initialize-at-build-time=io.ktor,kotlin,ch.qos.logback,org.slf4j")
buildArgs.add("-H:+ReportExceptionStackTraces")
}
metadataRepository {
enabled.set(true)
}
}
}

graphql {
graalVm {
packages = listOf("com.expediagroup.graalvm.ktor")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2023 Expedia, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.expediagroup.graalvm.ktor

import com.expediagroup.graalvm.ktor.context.CustomContextFactory
import com.expediagroup.graalvm.ktor.hooks.CustomHooks
import com.expediagroup.graalvm.ktor.schema.ArgumentQuery
import com.expediagroup.graalvm.ktor.schema.AsyncQuery
import com.expediagroup.graalvm.ktor.schema.BasicMutation
import com.expediagroup.graalvm.ktor.schema.ContextualQuery
import com.expediagroup.graalvm.ktor.schema.CustomScalarQuery
import com.expediagroup.graalvm.ktor.schema.EnumQuery
import com.expediagroup.graalvm.ktor.schema.ErrorQuery
import com.expediagroup.graalvm.ktor.schema.IdQuery
import com.expediagroup.graalvm.ktor.schema.model.ExampleInterface
import com.expediagroup.graalvm.ktor.schema.model.ExampleUnion
import com.expediagroup.graalvm.ktor.schema.InnerClassQuery
import com.expediagroup.graalvm.ktor.schema.ListQuery
import com.expediagroup.graalvm.ktor.schema.PolymorphicQuery
import com.expediagroup.graalvm.ktor.schema.ScalarQuery
import com.expediagroup.graalvm.ktor.schema.TypesQuery
import com.expediagroup.graalvm.ktor.schema.dataloader.ExampleDataLoader
import com.expediagroup.graalvm.ktor.schema.model.FirstImpl
import com.expediagroup.graalvm.ktor.schema.model.FirstUnionMember
import com.expediagroup.graalvm.ktor.schema.model.SecondImpl
import com.expediagroup.graalvm.ktor.schema.model.SecondUnionMember
import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistryFactory
import com.expediagroup.graphql.server.ktor.GraphQL
import com.expediagroup.graphql.server.ktor.graphQLGetRoute
import com.expediagroup.graphql.server.ktor.graphQLPostRoute
import com.expediagroup.graphql.server.ktor.graphQLSDLRoute
import com.expediagroup.graphql.server.ktor.graphiQLRoute
import io.ktor.server.application.install
import io.ktor.server.cio.CIO
import io.ktor.server.engine.embeddedServer
import io.ktor.server.routing.routing

fun main() {
embeddedServer(CIO, port = 8080, host = "0.0.0.0") {
install(GraphQL) {
schema {
packages = listOf("com.expediagroup.graalvm.ktor")
queries = listOf(
ArgumentQuery(),
AsyncQuery(),
ContextualQuery(),
CustomScalarQuery(),
EnumQuery(),
ErrorQuery(),
IdQuery(),
InnerClassQuery(),
ListQuery(),
PolymorphicQuery(),
ScalarQuery(),
TypesQuery()
)
mutations = listOf(
BasicMutation()
)
hooks = CustomHooks()
typeHierarchy = mapOf(
ExampleInterface::class to listOf(FirstImpl::class, SecondImpl::class),
ExampleUnion::class to listOf(FirstUnionMember::class, SecondUnionMember::class)
)
}
engine {
dataLoaderRegistryFactory = KotlinDataLoaderRegistryFactory(
ExampleDataLoader
)
}
server {
contextFactory = CustomContextFactory()
}
}
routing {
graphQLGetRoute()
graphQLPostRoute()
graphQLSDLRoute()
graphiQLRoute()
}
}.start(wait = true)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2023 Expedia, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.expediagroup.graalvm.ktor.context

import com.expediagroup.graphql.generator.extensions.plus
import com.expediagroup.graphql.server.ktor.DefaultKtorGraphQLContextFactory
import graphql.GraphQLContext
import io.ktor.server.request.ApplicationRequest
import java.util.UUID

class CustomContextFactory : DefaultKtorGraphQLContextFactory() {
override suspend fun generateContext(request: ApplicationRequest): GraphQLContext {
return super.generateContext(request).plus(mapOf("custom" to (request.headers["X-Custom-Header"] ?: UUID.randomUUID().toString())))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2023 Expedia, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.expediagroup.graalvm.ktor.hooks

import com.expediagroup.graphql.generator.hooks.SchemaGeneratorHooks
import com.expediagroup.graphql.plugin.schema.hooks.SchemaGeneratorHooksProvider
import graphql.language.StringValue
import graphql.schema.Coercing
import graphql.schema.CoercingParseLiteralException
import graphql.schema.CoercingParseValueException
import graphql.schema.CoercingSerializeException
import graphql.schema.GraphQLScalarType
import graphql.schema.GraphQLType
import java.util.UUID
import kotlin.reflect.KClass
import kotlin.reflect.KType

class CustomHooks : SchemaGeneratorHooks {
override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier as? KClass<*>) {
UUID::class -> graphqlUUIDType
else -> null
}
}

class CustomHooksProvider : SchemaGeneratorHooksProvider {
override fun hooks(): SchemaGeneratorHooks = CustomHooks()
}

val graphqlUUIDType: GraphQLScalarType = GraphQLScalarType.newScalar()
.name("UUID")
.description("A type representing a formatted java.util.UUID")
.coercing(UUIDCoercing)
.build()

object UUIDCoercing : Coercing<UUID, String> {
override fun parseValue(input: Any): UUID = runCatching {
UUID.fromString(serialize(input))
}.getOrElse {
throw CoercingParseValueException("Expected valid UUID but was $input")
}

override fun parseLiteral(input: Any): UUID {
val uuidString = (input as? StringValue)?.value
return runCatching {
UUID.fromString(uuidString)
}.getOrElse {
throw CoercingParseLiteralException("Expected valid UUID literal but was $uuidString")
}
}

override fun serialize(dataFetcherResult: Any): String = runCatching {
dataFetcherResult.toString()
}.getOrElse {
throw CoercingSerializeException("Data fetcher result $dataFetcherResult cannot be serialized to a String")
}
}
Loading