diff --git a/settings.gradle b/settings.gradle index 8af23c7ca9..811aa851d6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,8 +17,6 @@ include "spring-context" include "spring-context-indexer" include "spring-context-support" include "spring-core" -include "kotlin-coroutines" -project(':kotlin-coroutines').projectDir = file('spring-core/kotlin-coroutines') include "spring-expression" include "spring-instrument" include "spring-jcl" @@ -51,7 +49,7 @@ settings.gradle.projectsLoaded { buildScanPublished { scan -> if (buildDir.exists()) { new File(buildDir, "build-scan-uri.txt").text = "${scan.buildScanUri}\n" - } + } } } } diff --git a/spring-core/kotlin-coroutines/kotlin-coroutines.gradle b/spring-core/kotlin-coroutines/kotlin-coroutines.gradle deleted file mode 100644 index 94d7f15314..0000000000 --- a/spring-core/kotlin-coroutines/kotlin-coroutines.gradle +++ /dev/null @@ -1,35 +0,0 @@ -description = "Spring Core Coroutines support" - -apply plugin: "kotlin" - -configurations { - classesOnlyElements { - canBeConsumed = true - canBeResolved = false - } -} - -artifacts { - classesOnlyElements(compileKotlin.destinationDir) -} - -dependencies { - api("org.jetbrains.kotlin:kotlin-reflect") - api("org.jetbrains.kotlin:kotlin-stdlib") - api("io.projectreactor:reactor-core") - api("org.jetbrains.kotlinx:kotlinx-coroutines-core") - api("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") -} - -eclipse { - project { - buildCommand "org.jetbrains.kotlin.ui.kotlinBuilder" - buildCommand "org.eclipse.jdt.core.javabuilder" - natures "org.jetbrains.kotlin.core.kotlinNature" - natures "org.eclipse.jdt.core.javanature" - linkedResource name: "kotlin_bin", type: "2", locationUri: "org.jetbrains.kotlin.core.filesystem:/" + project.name + "/kotlin_bin" - } - classpath { - containers "org.jetbrains.kotlin.core.KOTLIN_CONTAINER" - } -} diff --git a/spring-core/kotlin-coroutines/src/main/kotlin/org/springframework/core/CoroutinesUtils.kt b/spring-core/kotlin-coroutines/src/main/kotlin/org/springframework/core/CoroutinesUtils.kt deleted file mode 100644 index 3d716bb685..0000000000 --- a/spring-core/kotlin-coroutines/src/main/kotlin/org/springframework/core/CoroutinesUtils.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2002-2021 the original author or authors. - * - * 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. - */ - -@file:JvmName("CoroutinesUtils") -package org.springframework.core - -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.reactive.awaitSingleOrNull -import kotlinx.coroutines.reactor.asFlux - -import kotlinx.coroutines.reactor.mono -import org.reactivestreams.Publisher -import reactor.core.publisher.Mono -import java.lang.reflect.InvocationTargetException -import java.lang.reflect.Method -import kotlin.reflect.full.callSuspend -import kotlin.reflect.jvm.kotlinFunction - -/** - * Convert a [Deferred] instance to a [Mono] one. - * - * @author Sebastien Deleuze - * @since 5.2 - */ -internal fun deferredToMono(source: Deferred) = - mono(Dispatchers.Unconfined) { source.await() } - -/** - * Convert a [Mono] instance to a [Deferred] one. - * - * @author Sebastien Deleuze - * @since 5.2 - */ -@Suppress("DEPRECATION") -@OptIn(DelicateCoroutinesApi::class) -internal fun monoToDeferred(source: Mono) = - GlobalScope.async(Dispatchers.Unconfined) { source.awaitSingleOrNull() } - -/** - * Invoke a suspending function and converts it to [Mono] or [reactor.core.publisher.Flux]. - * - * @author Sebastien Deleuze - * @since 5.2 - */ -@Suppress("UNCHECKED_CAST") -fun invokeSuspendingFunction(method: Method, target: Any, vararg args: Any?): Publisher<*> { - val function = method.kotlinFunction!! - val mono = mono(Dispatchers.Unconfined) { - function.callSuspend(target, *args.sliceArray(0..(args.size-2))).let { if (it == Unit) null else it } - }.onErrorMap(InvocationTargetException::class.java) { it.targetException } - return if (function.returnType.classifier == Flow::class) { - mono.flatMapMany { (it as Flow).asFlux() } - } - else { - mono - } -} diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index d0b294876e..a6e5e309b0 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -13,7 +13,6 @@ def objenesisVersion = "3.2" configurations { cglib objenesis - coroutines } task cglibRepackJar(type: ShadowJar) { @@ -34,16 +33,16 @@ task objenesisRepackJar(type: ShadowJar) { dependencies { cglib("cglib:cglib:${cglibVersion}@jar") objenesis("org.objenesis:objenesis:${objenesisVersion}@jar") - coroutines(project(path: ":kotlin-coroutines", configuration: 'classesOnlyElements')) api(files(cglibRepackJar)) api(files(objenesisRepackJar)) api(project(":spring-jcl")) - compileOnly(project(":kotlin-coroutines")) compileOnly("io.projectreactor.tools:blockhound") optional("net.sf.jopt-simple:jopt-simple") optional("org.aspectj:aspectjweaver") optional("org.jetbrains.kotlin:kotlin-reflect") optional("org.jetbrains.kotlin:kotlin-stdlib") + optional("org.jetbrains.kotlinx:kotlinx-coroutines-core") + optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") optional("io.projectreactor:reactor-core") optional("io.reactivex:rxjava") optional("io.reactivex:rxjava-reactive-streams") @@ -58,7 +57,6 @@ dependencies { testImplementation("com.fasterxml.woodstox:woodstox-core") testImplementation("org.xmlunit:xmlunit-assertj") testImplementation("org.xmlunit:xmlunit-matchers") - testImplementation(project(":kotlin-coroutines")) testImplementation("io.projectreactor.tools:blockhound") testFixturesImplementation("io.projectreactor:reactor-test") testFixturesImplementation("com.google.code.findbugs:jsr305") @@ -91,8 +89,6 @@ jar { from(zipTree(objenesisRepackJar.archivePath)) { include "org/springframework/objenesis/**" } - - from configurations.coroutines } test { diff --git a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java new file mode 100644 index 0000000000..e942ff48ee --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * 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 org.springframework.core; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Objects; + +import kotlin.Unit; +import kotlin.jvm.JvmClassMappingKt; +import kotlin.reflect.KClassifier; +import kotlin.reflect.KFunction; +import kotlin.reflect.full.KCallables; +import kotlin.reflect.jvm.ReflectJvmMapping; +import kotlinx.coroutines.BuildersKt; +import kotlinx.coroutines.CoroutineStart; +import kotlinx.coroutines.Deferred; +import kotlinx.coroutines.Dispatchers; +import kotlinx.coroutines.GlobalScope; +import kotlinx.coroutines.flow.Flow; +import kotlinx.coroutines.reactor.MonoKt; +import kotlinx.coroutines.reactor.ReactorFlowKt; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * Utilities for working with Kotlin Coroutines. + * + * @author Sebastien Deleuze + * @author Phillip Webb + * @since 5.2 + */ +public final class CoroutinesUtils { + + private CoroutinesUtils() { + } + + /** + * Convert a {@link Deferred} instance to a {@link Mono}. + */ + public static Mono deferredToMono(Deferred source) { + return MonoKt.mono(Dispatchers.getUnconfined(), + (scope, continuation) -> source.await(continuation)); + } + + /** + * Convert a {@link Mono} instance to a {@link Deferred}. + */ + public static Deferred monoToDeferred(Mono source) { + return BuildersKt.async(GlobalScope.INSTANCE, Dispatchers.getUnconfined(), + CoroutineStart.DEFAULT, + (scope, continuation) -> MonoKt.awaitSingleOrNull(source, continuation)); + } + + /** + * Invoke a suspending function and converts it to {@link Mono} or + * {@link Flux}. + */ + public static Publisher invokeSuspendingFunction(Method method, Object target, Object... args) { + KFunction function = Objects.requireNonNull(ReflectJvmMapping.getKotlinFunction(method)); + KClassifier classifier = function.getReturnType().getClassifier(); + Mono mono = MonoKt.mono(Dispatchers.getUnconfined(), (scope, continuation) -> + KCallables.callSuspend(function, getSuspendedFunctionArgs(target, args), continuation)) + .filter(result -> !Objects.equals(result, Unit.INSTANCE)) + .onErrorMap(InvocationTargetException.class, InvocationTargetException::getTargetException); + if (classifier.equals(JvmClassMappingKt.getKotlinClass(Flow.class))) { + return mono.flatMapMany(CoroutinesUtils::asFlux); + } + return mono; + } + + private static Object[] getSuspendedFunctionArgs(Object target, Object... args) { + Object[] functionArgs = new Object[args.length]; + functionArgs[0] = target; + System.arraycopy(args, 0, functionArgs, 1, args.length - 1); + return functionArgs; + } + + private static Flux asFlux(Object flow) { + return ReactorFlowKt.asFlux(((Flow) flow)); + } + +} diff --git a/spring-messaging/spring-messaging.gradle b/spring-messaging/spring-messaging.gradle index eed544b526..c0c7270ebd 100644 --- a/spring-messaging/spring-messaging.gradle +++ b/spring-messaging/spring-messaging.gradle @@ -6,7 +6,6 @@ apply plugin: "kotlinx-serialization" dependencies { api(project(":spring-beans")) api(project(":spring-core")) - compileOnly(project(":kotlin-coroutines")) optional(project(":spring-context")) optional(project(":spring-oxm")) optional("io.projectreactor.netty:reactor-netty-http") @@ -19,7 +18,6 @@ dependencies { optional("com.google.protobuf:protobuf-java-util") optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") optional("org.jetbrains.kotlinx:kotlinx-serialization-json") - testImplementation(project(":kotlin-coroutines")) testImplementation(testFixtures(project(":spring-core"))) testImplementation("javax.inject:javax.inject-tck") testImplementation("javax.servlet:javax.servlet-api") diff --git a/spring-r2dbc/spring-r2dbc.gradle b/spring-r2dbc/spring-r2dbc.gradle index 5c60dd7d3a..dfb6b1ece9 100644 --- a/spring-r2dbc/spring-r2dbc.gradle +++ b/spring-r2dbc/spring-r2dbc.gradle @@ -8,12 +8,10 @@ dependencies { api(project(":spring-tx")) api("io.r2dbc:r2dbc-spi") api("io.projectreactor:reactor-core") - compileOnly(project(":kotlin-coroutines")) optional("org.jetbrains.kotlin:kotlin-reflect") optional("org.jetbrains.kotlin:kotlin-stdlib") optional("org.jetbrains.kotlinx:kotlinx-coroutines-core") optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") - testImplementation(project(":kotlin-coroutines")) testImplementation(testFixtures(project(":spring-beans"))) testImplementation(testFixtures(project(":spring-core"))) testImplementation(testFixtures(project(":spring-context"))) diff --git a/spring-tx/spring-tx.gradle b/spring-tx/spring-tx.gradle index c9447732bc..f3ec8c3f3a 100644 --- a/spring-tx/spring-tx.gradle +++ b/spring-tx/spring-tx.gradle @@ -7,7 +7,6 @@ dependencies { api(project(":spring-core")) optional(project(":spring-aop")) optional(project(":spring-context")) // for JCA, @EnableTransactionManagement - optional(project(":kotlin-coroutines")) optional("javax.ejb:javax.ejb-api") optional("javax.interceptor:javax.interceptor-api") optional("javax.resource:javax.resource-api") diff --git a/spring-web/spring-web.gradle b/spring-web/spring-web.gradle index f774f0afb8..716ecb4cec 100644 --- a/spring-web/spring-web.gradle +++ b/spring-web/spring-web.gradle @@ -6,7 +6,6 @@ apply plugin: "kotlinx-serialization" dependencies { api(project(":spring-beans")) api(project(":spring-core")) - compileOnly(project(":kotlin-coroutines")) compileOnly("io.projectreactor.tools:blockhound") optional(project(":spring-aop")) optional(project(":spring-context")) diff --git a/spring-webflux/spring-webflux.gradle b/spring-webflux/spring-webflux.gradle index 7e9e49c69f..b762e09c5c 100644 --- a/spring-webflux/spring-webflux.gradle +++ b/spring-webflux/spring-webflux.gradle @@ -7,7 +7,6 @@ dependencies { api(project(":spring-core")) api(project(":spring-web")) api("io.projectreactor:reactor-core") - compileOnly(project(":kotlin-coroutines")) optional(project(":spring-context")) optional(project(":spring-context-support")) // for FreeMarker support optional("javax.servlet:javax.servlet-api") @@ -27,7 +26,6 @@ dependencies { optional("org.jetbrains.kotlin:kotlin-stdlib") optional("com.google.protobuf:protobuf-java-util") optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") - testImplementation(project(":kotlin-coroutines")) testImplementation(testFixtures(project(":spring-beans"))) testImplementation(testFixtures(project(":spring-core"))) testImplementation(testFixtures(project(":spring-web"))) diff --git a/spring-webmvc/spring-webmvc.gradle b/spring-webmvc/spring-webmvc.gradle index 36ad9f649e..99e70f68f6 100644 --- a/spring-webmvc/spring-webmvc.gradle +++ b/spring-webmvc/spring-webmvc.gradle @@ -37,7 +37,6 @@ dependencies { optional("org.jetbrains.kotlin:kotlin-stdlib") optional("org.reactivestreams:reactive-streams") optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") - testImplementation(project(":kotlin-coroutines")) testImplementation(testFixtures(project(":spring-beans"))) testImplementation(testFixtures(project(":spring-core"))) testImplementation(testFixtures(project(":spring-context")))