Skip to content

Commit

Permalink
Add temporal-kotlin providing correct support for Kotlin in Async
Browse files Browse the repository at this point in the history
  • Loading branch information
Spikhalskiy committed Aug 14, 2021
1 parent d38065d commit cc8f368
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 142 deletions.
2 changes: 1 addition & 1 deletion temporal-kotlin/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Temporal Kotlin support module

This module added to classpath provides some support for Kotlin specific language features:
- Support for Kotlin
- Support for Kotlin method references for Temporal client stubs passed into Async

## Usage

Expand Down
15 changes: 5 additions & 10 deletions temporal-kotlin/build.gradle
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
}

plugins {
// id 'org.jetbrains.kotlin.jvm' version '1.3.72'
// id 'org.jetbrains.kotlin.jvm' version '1.4.21'
id 'org.jetbrains.kotlin.jvm' version '1.5.21'

id 'org.jlleitschuh.gradle.ktlint' version '10.1.0'
}

Expand Down Expand Up @@ -38,10 +31,12 @@ dependencies {

implementation project(':temporal-sdk')

implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.12.4'
implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: '2.12.4'

testImplementation project(':temporal-testing')
testImplementation project(':temporal-testing-junit4')

testImplementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.12.4'
testImplementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: '2.12.4'
testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.5'
testImplementation group: 'junit', name: 'junit', version: '4.13.2'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2020 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
* use this file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 io.temporal.common.converter

import com.fasterxml.jackson.annotation.JsonAutoDetect
import com.fasterxml.jackson.annotation.PropertyAccessor
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinModule

class KotlinObjectMapperFactory {
companion object {
@JvmStatic
fun new(): ObjectMapper {
val mapper = ObjectMapper()
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
mapper.registerModule(JavaTimeModule())
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
mapper.registerModule(KotlinModule())
return mapper
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2020 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
* use this file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 io.temporal.workflow

import io.temporal.client.WorkflowClientOptions
import io.temporal.client.WorkflowOptions
import io.temporal.common.converter.DefaultDataConverter
import io.temporal.common.converter.KotlinObjectMapperFactory
import io.temporal.common.converter.JacksonJsonPayloadConverter
import io.temporal.internal.async.FunctionWrappingUtil
import io.temporal.internal.sync.AsyncInternal
import io.temporal.testing.TestWorkflowRule
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test

class KotlinAsyncChildWorkflowTest {

@Rule @JvmField
var testWorkflowRule: TestWorkflowRule = TestWorkflowRule.newBuilder()
.setWorkflowTypes(NaiveParentWorkflowImpl::class.java, ChildWorkflowImpl::class.java)
.setWorkflowClientOptions(
WorkflowClientOptions.newBuilder()
.setDataConverter(DefaultDataConverter(JacksonJsonPayloadConverter(KotlinObjectMapperFactory.new())))
.build()
)
.build()

@WorkflowInterface
interface ChildWorkflow {
@WorkflowMethod
fun execute(): Int
}

class ChildWorkflowImpl : ChildWorkflow {
override fun execute(): Int {
return 0
}
}

@WorkflowInterface
interface NaiveParentWorkflow {
@WorkflowMethod
fun execute()
}

class NaiveParentWorkflowImpl : NaiveParentWorkflow {
override fun execute() {
val childWorkflow = Workflow.newChildWorkflowStub(ChildWorkflow::class.java)
assertTrue(
"This has to be true to make Async.function(childWorkflow::execute) work correctly as expected",
AsyncInternal.isAsync(childWorkflow::execute)
)
assertTrue(
"This has to be true to make Async.function(childWorkflow::execute) work correctly as expected",
AsyncInternal.isAsync(FunctionWrappingUtil.temporalJavaFunctionalWrapper(childWorkflow::execute))
)
Async.function(childWorkflow::execute).get()
}
}

@Test
fun asyncChildWorkflowTest() {
val client = testWorkflowRule.workflowClient
val options = WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.taskQueue).build()
val workflowStub = client.newWorkflowStub(NaiveParentWorkflow::class.java, options)
workflowStub.execute()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2020 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
* use this file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 io.temporal.workflow

import io.temporal.client.WorkflowClientOptions
import io.temporal.client.WorkflowOptions
import io.temporal.common.converter.DefaultDataConverter
import io.temporal.common.converter.KotlinObjectMapperFactory
import io.temporal.common.converter.JacksonJsonPayloadConverter
import io.temporal.testing.TestWorkflowRule
import junit.framework.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import java.util.concurrent.atomic.AtomicBoolean

class KotlinAsyncLambdaTest {

companion object {
private val success = AtomicBoolean(false)
}

@Rule @JvmField
var testWorkflowRule: TestWorkflowRule = TestWorkflowRule.newBuilder()
.setWorkflowTypes(NaiveParentWorkflowImpl::class.java)
.setWorkflowClientOptions(
WorkflowClientOptions.newBuilder()
.setDataConverter(DefaultDataConverter(JacksonJsonPayloadConverter(KotlinObjectMapperFactory.new())))
.build()
)
.build()

@WorkflowInterface
interface NaiveParentWorkflow {
@WorkflowMethod
fun execute()
}

class NaiveParentWorkflowImpl : NaiveParentWorkflow {
override fun execute() {
Async.procedure { success.set(true) }.get()
}
}

@Test
fun asyncChildWorkflowTest() {
val client = testWorkflowRule.workflowClient
val options = WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.taskQueue).build()
val workflowStub = client.newWorkflowStub(NaiveParentWorkflow::class.java, options)
workflowStub.execute()
assertTrue(success.get())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@

package io.temporal.internal.async;

import static io.temporal.internal.common.JVMLambdaUtils.getTarget;

import io.temporal.internal.async.spi.MethodReferenceDisassemblyService;
import io.temporal.internal.common.JVMLambdaUtils;
import io.temporal.internal.common.JavaLambdaUtils;
import io.temporal.internal.common.kotlin.KotlinDetector;
import io.temporal.internal.sync.AsyncInternal;
import io.temporal.workflow.ActivityStub;
Expand All @@ -49,8 +47,8 @@ public static boolean isAsync(Object func) {
}

private static boolean isAsyncJava(Object func) {
SerializedLambda lambda = JVMLambdaUtils.toSerializedLambda(func);
Object target = getTarget(lambda);
SerializedLambda lambda = JavaLambdaUtils.toSerializedLambda(func);
Object target = JavaLambdaUtils.getTarget(lambda);
return target instanceof ActivityStub
|| target instanceof ChildWorkflowStub
|| target instanceof ExternalWorkflowStub
Expand All @@ -65,7 +63,8 @@ private static boolean isAsyncKotlin(Object func) {
if (methodReferenceDisassemblyService == null) {
throw new IllegalStateException(
"Kotlin method reference is used with async. "
+ "For Temporal to correctly support async invocation kotlin method references, add temporal-kotlin to classpath");
+ "For Temporal to correctly support async invocation kotlin method references, "
+ "add io.temporal:temporal-kotlin to classpath");
}

Object target = methodReferenceDisassemblyService.getMethodReferenceTarget(func);
Expand Down
Loading

0 comments on commit cc8f368

Please sign in to comment.