-
Notifications
You must be signed in to change notification settings - Fork 152
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add temporal-kotlin providing correct support for Kotlin in Async
- Loading branch information
1 parent
58f02fe
commit e370202
Showing
18 changed files
with
657 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[*.{kt,kts}] | ||
indent_size = 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
Temporal Java SDK | ||
|
||
Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved | ||
|
||
Copyright (c) 2017 Uber Technologies, Inc. All Rights Reserved | ||
|
||
AWS Simple Workflow Flow Library | ||
Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved | ||
|
||
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 | ||
|
||
http://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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Temporal Kotlin support module | ||
|
||
This module added to classpath provides some support for Kotlin specific language features: | ||
- Support for Kotlin method references for Temporal client stubs passed into Async | ||
|
||
## Usage | ||
|
||
Add temporal-kotlin as a dependency to your pom.xml: | ||
``` | ||
<dependency> | ||
<groupId>io.temporal</groupId> | ||
<artifactId>temporal-kotlin</artifactId> | ||
<version>N.N.N</version> | ||
</dependency> | ||
``` | ||
|
||
or to build.gradle: | ||
``` | ||
compile group: 'io.temporal', name: 'temporal-kotlin', version: 'N.N.N' | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
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' | ||
} | ||
|
||
description = '''Temporal Workflow Java SDK Kotlin''' | ||
|
||
ext { | ||
// kotlinVersion = '1.3.72' | ||
// kotlinVersion = '1.4.21' | ||
kotlinVersion = '1.5.21' | ||
} | ||
|
||
compileKotlin { | ||
kotlinOptions { | ||
jvmTarget = JavaVersion.VERSION_1_8 | ||
} | ||
} | ||
|
||
compileTestKotlin { | ||
kotlinOptions { | ||
jvmTarget = JavaVersion.VERSION_1_8 | ||
} | ||
} | ||
|
||
dependencies { | ||
compileOnly group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: kotlinVersion | ||
|
||
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: 'ch.qos.logback', name: 'logback-classic', version: '1.2.5' | ||
testImplementation group: 'junit', name: 'junit', version: '4.13.2' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
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. |
41 changes: 41 additions & 0 deletions
41
temporal-kotlin/src/main/kotlin/io/temporal/common/converter/KotlinObjectMapperFactory.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
...lin/src/main/kotlin/io/temporal/internal/async/KotlinMethodReferenceDisassemblyService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/* | ||
* 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.internal.async | ||
|
||
import io.temporal.internal.async.spi.MethodReferenceDisassemblyService | ||
import io.temporal.workflow.Functions | ||
import kotlin.jvm.internal.CallableReference | ||
|
||
class KotlinMethodReferenceDisassemblyService : MethodReferenceDisassemblyService { | ||
override fun getMethodReferenceTarget(methodReference: Any): Any? { | ||
if (methodReference is CallableReference) { | ||
return unwrapCallableReference(methodReference) | ||
} | ||
|
||
if (methodReference is Functions.TemporalFunctionalInterfaceMarker) { | ||
return unwrapTemporalFunctionalInterfaceInKotlin(methodReference) | ||
} | ||
|
||
return null | ||
} | ||
|
||
/** | ||
* Strategy 1 | ||
* We unwrap simple native Kotlin [CallableReference] (which is used for method references) | ||
*/ | ||
private fun unwrapCallableReference(callableReference: CallableReference): Any { | ||
return callableReference.boundReceiver | ||
} | ||
|
||
/** | ||
* Strategy 2 | ||
* Kotlin bumped into one of [io.temporal.workflow.Async] calls that have one of our [io.temporal.workflow.Functions] | ||
* as a parameter and has to implement/wrap the method reference as one of our function interfaces | ||
*/ | ||
private fun unwrapTemporalFunctionalInterfaceInKotlin(temporalFunction: Functions.TemporalFunctionalInterfaceMarker): Any? { | ||
val declaredFields = temporalFunction.javaClass.declaredFields | ||
if (declaredFields.size != 1) { | ||
return null | ||
} | ||
|
||
val proxiedField = declaredFields[0] | ||
proxiedField.isAccessible = true | ||
val proxiedValue = proxiedField[temporalFunction] | ||
if ("function" == proxiedField.name) { | ||
/** | ||
* Strategy 2.1 | ||
* Kotlin 1.4 and earlier wraps Kotlin's [CallableReference] | ||
* into one of [io.temporal.workflow.Functions] wrappers. | ||
* This will be a generated class handling the Callable Reference in 'function' field. | ||
*/ | ||
return if (proxiedValue is CallableReference) { | ||
unwrapCallableReference(proxiedValue) | ||
} else { | ||
null | ||
} | ||
} else { | ||
/** | ||
* Strategy 2.2 | ||
* Kotlin 1.5 generates one of [io.temporal.workflow.Functions] directly over the target | ||
* without any [CallableReference] in between, in that case our target is persisted directly | ||
* in the single field of the generated Function. | ||
*/ | ||
return proxiedValue | ||
} | ||
} | ||
|
||
override fun getLanguageName(): String { | ||
return MethodReferenceDisassemblyService.KOTLIN | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
...ources/META-INF/services/io.temporal.internal.async.spi.MethodReferenceDisassemblyService
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
io.temporal.internal.async.KotlinMethodReferenceDisassemblyService |
33 changes: 33 additions & 0 deletions
33
temporal-kotlin/src/test/java/io/temporal/internal/async/FunctionWrappingUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
* 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.internal.async; | ||
|
||
import io.temporal.workflow.Functions; | ||
|
||
public class FunctionWrappingUtil { | ||
/** | ||
* We emulate here what happens in Async/AsyncInternal when {@link | ||
* io.temporal.workflow.Async#function(Functions.Func)} accepts {@link Functions.Func<R>} as a | ||
* parameter and kotlin method reference is getting wrapped into {@link Functions.Func<R>} | ||
*/ | ||
public static <R> Functions.Func<R> temporalJavaFunctionalWrapper(Functions.Func<R> function) { | ||
return function; | ||
} | ||
} |
86 changes: 86 additions & 0 deletions
86
temporal-kotlin/src/test/kotlin/io/temporal/workflow/KotlinAsyncChildWorkflowTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.JacksonJsonPayloadConverter | ||
import io.temporal.common.converter.KotlinObjectMapperFactory | ||
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() | ||
} | ||
} |
Oops, something went wrong.