Merge spring-integration-kotlin-dsl project

* Migrate all the Kotlin tests to use new Kotlin DSL
* Upgrade to the latest Kotlin
* Generate KDocs
This commit is contained in:
Artem Bilan
2020-03-05 13:08:56 -05:00
parent f332a9446b
commit 8a38a5e829
7 changed files with 1628 additions and 48 deletions

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2020 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.integration.dsl
import org.reactivestreams.Publisher
import org.springframework.integration.core.MessageSource
import org.springframework.integration.endpoint.MessageProducerSupport
import org.springframework.integration.gateway.MessagingGatewaySupport
import org.springframework.messaging.Message
import org.springframework.messaging.MessageChannel
import java.util.function.Consumer
private fun buildIntegrationFlow(flowBuilder: IntegrationFlowBuilder,
flow: (KotlinIntegrationFlowDefinition) -> Unit): IntegrationFlow {
flow(KotlinIntegrationFlowDefinition(flowBuilder))
return flowBuilder.get()
}
/**
* Functional [IntegrationFlow] definition in Kotlin DSL for [IntegrationFlow] lambdas.
*
* @author Artem Bilan
*/
fun integrationFlow(flow: KotlinIntegrationFlowDefinition.() -> Unit) =
IntegrationFlow {
flow(KotlinIntegrationFlowDefinition(it))
}
/**
* Functional [IntegrationFlow] definition in Kotlin DSL for [IntegrationFlows.from] -
* `IntegrationFlows.from(Class<?>, Consumer<GatewayProxySpec>)` factory method.
*
* @author Artem Bilan
*/
inline fun <reified T> integrationFlow(
crossinline gateway: GatewayProxySpec.() -> Unit = {},
flow: KotlinIntegrationFlowDefinition.() -> Unit): IntegrationFlow {
val flowBuilder = IntegrationFlows.from(T::class.java) { gateway(it) }
flow(KotlinIntegrationFlowDefinition(flowBuilder))
return flowBuilder.get()
}
/**
* Functional [IntegrationFlow] definition in Kotlin DSL for [IntegrationFlows.from] -
* `IntegrationFlows.from(String, Boolean)` factory method.
*
* @author Artem Bilan
*/
fun integrationFlow(channelName: String, fixedSubscriber: Boolean = false,
flow: KotlinIntegrationFlowDefinition.() -> Unit) =
buildIntegrationFlow(IntegrationFlows.from(channelName, fixedSubscriber), flow)
/**
* Functional [IntegrationFlow] definition in Kotlin DSL for [IntegrationFlows.from] -
* `IntegrationFlows.from(MessageChannel)` factory method.
*
* @author Artem Bilan
*/
fun integrationFlow(channel: MessageChannel, flow: KotlinIntegrationFlowDefinition.() -> Unit) =
buildIntegrationFlow(IntegrationFlows.from(channel), flow)
/**
* Functional [IntegrationFlow] definition in Kotlin DSL for [IntegrationFlows.from] -
* `IntegrationFlows.from(MessageSource<*>, Consumer<SourcePollingChannelAdapterSpec>)` factory method.
*
* @author Artem Bilan
*/
fun integrationFlow(messageSource: MessageSource<*>,
options: SourcePollingChannelAdapterSpec.() -> Unit = {},
flow: KotlinIntegrationFlowDefinition.() -> Unit) =
buildIntegrationFlow(IntegrationFlows.from(messageSource, Consumer { options(it) }), flow)
/**
* Functional [IntegrationFlow] definition in Kotlin DSL for [IntegrationFlows.from] -
* `IntegrationFlows.from(MessageSourceSpec<*>, Consumer<SourcePollingChannelAdapterSpec>)` factory method.
*
* @author Artem Bilan
*/
fun integrationFlow(messageSource: MessageSourceSpec<*, out MessageSource<*>>,
options: SourcePollingChannelAdapterSpec.() -> Unit = {},
flow: KotlinIntegrationFlowDefinition.() -> Unit) =
buildIntegrationFlow(IntegrationFlows.from(messageSource, options), flow)
/**
* Functional [IntegrationFlow] definition in Kotlin DSL for [IntegrationFlows.from] -
* `IntegrationFlows.from(Supplier<*>, Consumer<SourcePollingChannelAdapterSpec>)` factory method.
*
* @author Artem Bilan
*/
fun integrationFlow(source: () -> Any,
options: SourcePollingChannelAdapterSpec.() -> Unit = {},
flow: KotlinIntegrationFlowDefinition.() -> Unit) =
buildIntegrationFlow(IntegrationFlows.from(source, options), flow)
/**
* Functional [IntegrationFlow] definition in Kotlin DSL for [IntegrationFlows.from] -
* `IntegrationFlows.from(Publisher<out Message<*>>)` factory method.
*
* @author Artem Bilan
*/
fun integrationFlow(publisher: Publisher<out Message<*>>,
flow: KotlinIntegrationFlowDefinition.() -> Unit) =
buildIntegrationFlow(IntegrationFlows.from(publisher), flow)
/**
* Functional [IntegrationFlow] definition in Kotlin DSL for [IntegrationFlows.from] -
* `IntegrationFlows.from(MessagingGatewaySupport)` factory method.
*
* @author Artem Bilan
*/
fun integrationFlow(gateway: MessagingGatewaySupport,
flow: KotlinIntegrationFlowDefinition.() -> Unit) =
buildIntegrationFlow(IntegrationFlows.from(gateway), flow)
/**
* Functional [IntegrationFlow] definition in Kotlin DSL for [IntegrationFlows.from] -
* `IntegrationFlows.from(MessagingGatewaySpec<*, *>)` factory method.
*
* @author Artem Bilan
*/
fun integrationFlow(gatewaySpec: MessagingGatewaySpec<*, *>,
flow: KotlinIntegrationFlowDefinition.() -> Unit) =
buildIntegrationFlow(IntegrationFlows.from(gatewaySpec), flow)
/**
* Functional [IntegrationFlow] definition in Kotlin DSL for [IntegrationFlows.from] -
* `IntegrationFlows.from(MessageProducerSupport)` factory method.
*
* @author Artem Bilan
*/
fun integrationFlow(producer: MessageProducerSupport,
flow: KotlinIntegrationFlowDefinition.() -> Unit) =
buildIntegrationFlow(IntegrationFlows.from(producer), flow)
/**
* Functional [IntegrationFlow] definition in Kotlin DSL for [IntegrationFlows.from] -
* `IntegrationFlows.from(MessageProducerSpec<*, *>)` factory method.
*
* @author Artem Bilan
*/
fun integrationFlow(producerSpec: MessageProducerSpec<*, *>,
flow: KotlinIntegrationFlowDefinition.() -> Unit) =
buildIntegrationFlow(IntegrationFlows.from(producerSpec), flow)

View File

@@ -0,0 +1,335 @@
/*
* Copyright 2020 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.integration.dsl
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isGreaterThanOrEqualTo
import assertk.assertions.isInstanceOf
import assertk.assertions.isNotNull
import assertk.assertions.size
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.BeanFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.integration.channel.FluxMessageChannel
import org.springframework.integration.channel.QueueChannel
import org.springframework.integration.config.EnableIntegration
import org.springframework.integration.core.GenericSelector
import org.springframework.integration.core.MessagingTemplate
import org.springframework.integration.dsl.context.IntegrationFlowContext
import org.springframework.integration.endpoint.MessageProcessorMessageSource
import org.springframework.integration.handler.LoggingHandler
import org.springframework.integration.scheduling.PollerMetadata
import org.springframework.integration.support.MessageBuilder
import org.springframework.integration.test.util.OnlyOnceTrigger
import org.springframework.messaging.Message
import org.springframework.messaging.MessageChannel
import org.springframework.messaging.MessageHeaders
import org.springframework.messaging.PollableChannel
import org.springframework.messaging.support.GenericMessage
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
import reactor.core.publisher.Flux
import reactor.test.StepVerifier
import java.util.*
import java.util.function.Function
/**
* @author Artem Bilan
*/
@SpringJUnitConfig
@DirtiesContext
class KotlinDslTests {
@Autowired
private lateinit var beanFactory: BeanFactory
@Autowired
private lateinit var integrationFlowContext: IntegrationFlowContext
@Autowired
private lateinit var convertFlowInput: MessageChannel
@Test
fun `convert extension`() {
assertThat(this.beanFactory.containsBean("kotlinConverter"))
val replyChannel = QueueChannel()
val date = Date()
val testMessage =
MessageBuilder.withPayload("{\"name\": \"Test\",\"date\": " + date.time + "}")
.setHeader(MessageHeaders.CONTENT_TYPE, "application/json")
.setReplyChannel(replyChannel)
.build()
this.convertFlowInput.send(testMessage)
assertThat(replyChannel.receive(10000)?.payload)
.isNotNull()
.isInstanceOf(TestPojo::class.java)
.isEqualTo(TestPojo("Test", date))
}
@Autowired
@Qualifier("functionGateway")
private lateinit var upperCaseFunction: Function<String, String>
@Test
fun `uppercase function`() {
assertThat(this.upperCaseFunction.apply("test")).isEqualTo("TEST")
}
@Autowired
private lateinit var fromSupplierQueue: PollableChannel
@Test
fun `message source flow`() {
assertThat(this.fromSupplierQueue.receive(10_000)?.payload).isNotNull().isEqualTo("testSource")
}
@Autowired
@Qualifier("functionFlow2.gateway")
private lateinit var lowerCaseFunction: Function<String, String>
@Test
fun `lowercase function`() {
assertThat(this.lowerCaseFunction.apply("TEST2")).isEqualTo("test2")
}
@Autowired
private lateinit var fixedSubscriberInput: MessageChannel
@Test
fun `fixed subscriber channel`() {
assertThat(MessagingTemplate().convertSendAndReceive(this.fixedSubscriberInput, "test", String::class.java))
.isEqualTo("test")
}
@Autowired
private lateinit var fromSupplierQueue2: PollableChannel
@Test
fun `message source flow2`() {
assertThat(this.fromSupplierQueue2.receive(10_000)?.payload).isNotNull().isEqualTo("testSource2")
}
@Autowired
private lateinit var testSupplierResult: PollableChannel
@Test
fun `supplier flow1`() {
assertThat(this.testSupplierResult.receive(10_000)?.payload).isNotNull().isEqualTo("testSupplier")
}
@Autowired
private lateinit var testSupplierResult2: PollableChannel
@Test
fun `supplier flow2`() {
assertThat(this.testSupplierResult2.receive(10_000)?.payload).isNotNull().isEqualTo("testSupplier2")
}
@Test
fun `reactive publisher flow`() {
val fluxChannel = FluxMessageChannel()
val verifyLater =
StepVerifier
.create(Flux.from(fluxChannel).map { it.payload }.cast(Integer::class.java))
.expectNext(Integer(4), Integer(6))
.thenCancel()
.verifyLater()
val publisher = Flux.just(2, 3).map { GenericMessage(it) }
val integrationFlow =
integrationFlow(publisher) {
transform<Message<Int>>({ it.payload * 2 }) { id("foo") }
channel(fluxChannel)
}
val registration = this.integrationFlowContext.registration(integrationFlow).register()
verifyLater.verify()
registration.destroy()
}
@Autowired
@Qualifier("flowLambda.input")
private lateinit var flowLambdaInput: MessageChannel
@Autowired
private lateinit var wireTapChannel: PollableChannel
@Test
fun `flow from lambda`() {
val replyChannel = QueueChannel()
val message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build()
this.flowLambdaInput.send(message)
assertThat(replyChannel.receive(10_000)?.payload).isNotNull().isEqualTo("TEST")
assertThat(this.wireTapChannel.receive(10_000)?.payload).isNotNull().isEqualTo("test")
}
@Autowired
@Qualifier("scatterGatherFlow.input")
private lateinit var scatterGatherFlowInput: MessageChannel
@Test
fun `Scatter-Gather`() {
val replyChannel = QueueChannel()
val request =
MessageBuilder.withPayload("foo")
.setReplyChannel(replyChannel)
.build()
this.scatterGatherFlowInput.send(request)
val bestQuoteMessage = replyChannel.receive(10000)
assertThat(bestQuoteMessage).isNotNull()
val payload = bestQuoteMessage!!.payload
assertThat(payload).isInstanceOf(List::class.java).size().isGreaterThanOrEqualTo(1)
}
@Configuration
@EnableIntegration
class Config {
@Bean(PollerMetadata.DEFAULT_POLLER)
fun defaultPoller() =
Pollers.fixedDelay(100).maxMessagesPerPoll(1).get()
@Bean
fun convertFlow() =
integrationFlow("convertFlowInput") {
convert<TestPojo>()
convert<TestPojo> { id("kotlinConverter") }
handle { m -> (m.headers[MessageHeaders.REPLY_CHANNEL] as MessageChannel).send(m) }
}
@Bean
fun functionFlow() =
integrationFlow<Function<String, String>>({ beanName("functionGateway") }) {
transform<String> { it.toUpperCase() }
split<Message<*>> { it.payload }
split<String>({ it }) { id("splitterEndpoint") }
resequence()
aggregate {
id("aggregator")
outputProcessor { it.one }
}
}
@Bean
fun functionFlow2() =
integrationFlow<Function<*, *>> {
transform<String> { it.toLowerCase() }
route<Message<*>, Any?>({ null }) { defaultOutputToParentFlow() }
route<Message<*>> { m -> m.headers.replyChannel }
}
@Bean
fun messageSourceFlow() =
integrationFlow(MessageProcessorMessageSource { "testSource" },
{ poller { it.trigger(OnlyOnceTrigger()) } }) {
channel { queue("fromSupplierQueue") }
}
@Bean
fun messageSourceFlow2() =
integrationFlow(MessageProcessorMessageSource { "testSource2" }) {
channel { queue("fromSupplierQueue2") }
}
@Bean
fun fixedSubscriberFlow() =
integrationFlow("fixedSubscriberInput", true) {
log<Any>(LoggingHandler.Level.WARN) { it.payload }
transform("payload") { id("spelTransformer") }
}
@Bean
fun flowFromSupplier() =
integrationFlow({ "testSupplier" }) {
channel { queue("testSupplierResult") }
}
@Bean
fun flowFromSupplier2() =
integrationFlow({ "testSupplier2" },
{ poller { it.trigger(OnlyOnceTrigger()) } }) {
filter<Message<*>> { m -> m.payload is String }
channel { queue("testSupplierResult2") }
}
@Bean
fun flowLambda() =
integrationFlow {
filter<String>({ it === "test" }) { id("filterEndpoint") }
wireTap {
channel { queue("wireTapChannel") }
}
delay("delayGroup") { defaultDelay(100) }
transform<String> { it.toUpperCase() }
}
/*
A Java variant for the flow below
@Bean
public IntegrationFlow scatterGatherFlow() {
return f -> f
.scatterGather(scatterer -> scatterer
.applySequence(true)
.recipientFlow(m -> true, sf -> sf.handle((p, h) -> Math.random() * 10))
.recipientFlow(m -> true, sf -> sf.handle((p, h) -> Math.random() * 10))
.recipientFlow(m -> true, sf -> sf.handle((p, h) -> Math.random() * 10)),
gatherer -> gatherer
.releaseStrategy(group ->
group.size() == 3 ||
group.getMessages()
.stream()
.anyMatch(m -> (Double) m.getPayload() > 5)),
scatterGather -> scatterGather
.gatherTimeout(10_000));
}*/
@Bean
fun scatterGatherFlow() =
integrationFlow {
scatterGather(
{
applySequence(true)
recipientFlow(GenericSelector<Any> { true }, integrationFlow { handle<Any> { _, _ -> Math.random() * 10 } })
recipientFlow(GenericSelector<Any> { true }, integrationFlow { handle<Any> { _, _ -> Math.random() * 10 } })
recipientFlow(GenericSelector<Any> { true }, integrationFlow { handle<Any> { _, _ -> Math.random() * 10 } })
},
{
releaseStrategy {
it.size() == 3 || it.messages.stream().anyMatch { it.payload as Double > 5 }
}
})
{
gatherTimeout(10_000)
}
}
}
data class TestPojo(val name: String?, val date: Date?)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -27,8 +27,8 @@ import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.integration.channel.QueueChannel
import org.springframework.integration.config.EnableIntegration
import org.springframework.integration.dsl.IntegrationFlow
import org.springframework.integration.dsl.MessageChannels
import org.springframework.integration.dsl.integrationFlow
import org.springframework.integration.support.MessageBuilder
import org.springframework.messaging.MessageChannel
import org.springframework.messaging.PollableChannel
@@ -99,39 +99,37 @@ class RouterDslTests {
@Bean
fun routerTwoSubFlows() =
IntegrationFlow { f ->
f.split()
.route<Int, Boolean>({ p -> p % 2 == 0 },
{ m ->
m.subFlowMapping(true, { sf -> sf.handle<Int> { p, _ -> p * 2 } })
.subFlowMapping(false, { sf -> sf.handle<Int> { p, _ -> p * 3 } })
})
.aggregate()
.channel { c -> c.queue("routerTwoSubFlowsOutput") }
integrationFlow {
split()
route<Int, Boolean>({ it % 2 == 0 }) {
subFlowMapping(true) { sf -> sf.handle<Int> { p, _ -> p * 2 } }
subFlowMapping(false) { sf -> sf.handle<Int> { p, _ -> p * 3 } }
}
aggregate()
channel { queue("routerTwoSubFlowsOutput") }
}
@Bean
fun splitRouteAggregate() =
IntegrationFlow { f ->
f.split()
.route<Int, Boolean>({ o -> o % 2 == 0 },
{ m ->
m.subFlowMapping(true) { sf -> sf.gateway(oddFlow()) }
.subFlowMapping(false) { sf -> sf.gateway(evenFlow()) }
})
.aggregate()
integrationFlow {
split()
route<Int, Boolean>({ it % 2 == 0 }) {
subFlowMapping(true) { sf -> sf.gateway(oddFlow()) }
subFlowMapping(false) { sf -> sf.gateway(evenFlow()) }
}
aggregate()
}
@Bean
fun oddFlow() =
IntegrationFlow { flow ->
flow.handle<Any> { _, _ -> "odd" }
integrationFlow {
handle<Any> { _, _ -> "odd" }
}
@Bean
fun evenFlow() =
IntegrationFlow { flow ->
flow.handle<Any> { _, _ -> "even" }
integrationFlow {
handle<Any> { _, _ -> "even" }
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018-2019 the original author or authors.
* Copyright 2018-2020 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.
@@ -36,8 +36,10 @@ import org.springframework.integration.channel.DirectChannel
import org.springframework.integration.channel.QueueChannel
import org.springframework.integration.config.EnableIntegration
import org.springframework.integration.dsl.IntegrationFlows
import org.springframework.integration.dsl.integrationFlow
import org.springframework.integration.endpoint.SourcePollingChannelAdapter
import org.springframework.integration.gateway.GatewayProxyFactoryBean
import org.springframework.integration.test.util.OnlyOnceTrigger
import org.springframework.messaging.Message
import org.springframework.messaging.MessageChannel
import org.springframework.messaging.PollableChannel
@@ -179,16 +181,17 @@ class FunctionsTests {
@Bean
fun flowFromSupplier() =
IntegrationFlows.from<String>({ "" }) { e -> e.poller { p -> p.fixedDelay(10).maxMessagesPerPoll(1) } }
.transform<String, String> { "blank" }
.channel { c -> c.queue("fromSupplierQueue") }
.get()
integrationFlow({ "" }, { poller { it.fixedDelay(10).maxMessagesPerPoll(1) } }) {
transform<String> { "blank" }
channel { queue("fromSupplierQueue") }
}
@Bean
fun monoFunctionGateway() =
IntegrationFlows.from(MonoFunction::class.java) { gateway -> gateway.proxyDefaultMethods(true) }
.handle<String>({ p, _ -> Mono.just(p).map(String::toUpperCase) }) { e -> e.async(true) }
.get()
integrationFlow<MonoFunction>({ proxyDefaultMethods(true) }) {
handle<String>({ p, _ -> Mono.just(p).map(String::toUpperCase) }) { async(true) }
}
}
interface MonoFunction : Function<String, Mono<Message<*>>>