diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowDefinition.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowDefinition.java index a0cc473920..d8d6c25aa5 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowDefinition.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/IntegrationFlowDefinition.java @@ -96,6 +96,7 @@ import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import reactor.core.publisher.Flux; import reactor.util.function.Tuple2; /** @@ -544,7 +545,7 @@ public abstract class IntegrationFlowDefinition B transform(GenericTransformer genericTransformer) { - return this.transform(null, genericTransformer); + return transform(null, genericTransformer); } /** @@ -2866,6 +2867,30 @@ public abstract class IntegrationFlowDefinition B fluxTransform(Function>, ? extends Publisher> fluxFunction) { + if (!(this.currentMessageChannel instanceof FluxMessageChannel)) { + channel(new FluxMessageChannel()); + } + + Publisher> upstream = (Publisher>) this.currentMessageChannel; + + Flux> result = Transformers.transformWithFunction(upstream, fluxFunction); + + FluxMessageChannel downstream = new FluxMessageChannel(); + downstream.subscribeTo((Flux>) (Flux) result); + + this.currentMessageChannel = downstream; + + return addComponent(this.currentMessageChannel); + } /** * Represent an Integration Flow as a Reactive Streams {@link Publisher} bean. diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java index f5a538260c..f6587c17e5 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java @@ -16,8 +16,11 @@ package org.springframework.integration.dsl; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import org.reactivestreams.Publisher; + import org.springframework.core.convert.converter.Converter; import org.springframework.core.serializer.Deserializer; import org.springframework.core.serializer.Serializer; @@ -27,6 +30,7 @@ import org.springframework.integration.codec.Codec; import org.springframework.integration.expression.FunctionExpression; import org.springframework.integration.json.JsonToObjectTransformer; import org.springframework.integration.json.ObjectToJsonTransformer; +import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.json.JsonObjectMapper; import org.springframework.integration.transformer.DecodingTransformer; import org.springframework.integration.transformer.EncodingPayloadTransformer; @@ -41,6 +45,9 @@ import org.springframework.integration.transformer.SyslogToMapTransformer; import org.springframework.messaging.Message; import org.springframework.util.Assert; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + /** * An utility class to provide methods for out-of-the-box * {@link org.springframework.integration.transformer.Transformer}s. @@ -262,4 +269,33 @@ public abstract class Transformers { return new StreamTransformer(charset); } + + @SuppressWarnings("unchecked") + static Flux> transformWithFunction(Publisher> publisher, + Function>, ? extends Publisher> fluxFunction) { + + return Flux.from(publisher) + .flatMap(message -> + Mono.subscriberContext() + .map(ctx -> { + ctx.get(RequestMessageHolder.class).set(message); + return message; + })) + .transform(fluxFunction) + .flatMap(data -> + data instanceof Message + ? Mono.just((Message) data) + : Mono.subscriberContext() + .map(ctx -> ctx.get(RequestMessageHolder.class).get()) + .map(requestMessage -> + MessageBuilder.withPayload(data) + .copyHeaders(requestMessage.getHeaders()) + .build())) + .subscriberContext(ctx -> ctx.put(RequestMessageHolder.class, new RequestMessageHolder())); + } + + private static class RequestMessageHolder extends AtomicReference> { + + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/reactivestreams/ReactiveStreamsTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/reactivestreams/ReactiveStreamsTests.java index 05f8431e54..129c548f41 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/dsl/reactivestreams/ReactiveStreamsTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/reactivestreams/ReactiveStreamsTests.java @@ -33,6 +33,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; +import java.util.stream.Collectors; import org.junit.Test; import org.junit.runner.RunWith; @@ -101,7 +102,7 @@ public class ReactiveStreamsTests { this.messageSource.start(); assertTrue(latch.await(10, TimeUnit.SECONDS)); String[] strings = results.toArray(new String[results.size()]); - assertArrayEquals(new String[] {"A", "B", "C", "D", "E", "F"}, strings); + assertArrayEquals(new String[] { "A", "B", "C", "D", "E", "F" }, strings); this.messageSource.stop(); } @@ -172,6 +173,40 @@ public class ReactiveStreamsTests { } } + @Test + public void testFluxTransform() { + QueueChannel resultChannel = new QueueChannel(); + + IntegrationFlow integrationFlow = f -> f + .split() + .fluxTransform(flux -> flux + .map(Message::getPayload) + .map(String::toUpperCase)) + .aggregate(a -> a + .outputProcessor(group -> group + .getMessages() + .stream() + .map(Message::getPayload) + .map(String.class::cast) + .collect(Collectors.joining(",")))) + .channel(resultChannel); + + IntegrationFlowContext.IntegrationFlowRegistration integrationFlowRegistration = + this.integrationFlowContext + .registration(integrationFlow) + .register(); + + MessageChannel inputChannel = integrationFlowRegistration.getInputChannel(); + inputChannel.send(new GenericMessage<>("a,b,c,d,e")); + + Message receive = resultChannel.receive(10_000); + + assertNotNull(receive); + assertEquals("A,B,C,D,E", receive.getPayload()); + + integrationFlowRegistration.destroy(); + } + @Configuration @EnableIntegration public static class ContextConfiguration {