diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FluxedConsumerWrapper.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FluxedConsumerWrapper.java new file mode 100644 index 000000000..8a7e9990a --- /dev/null +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FluxedConsumerWrapper.java @@ -0,0 +1,53 @@ +/* + * Copyright 2018 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 + * + * 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. + */ + +package org.springframework.cloud.stream.function; + +import java.util.function.Consumer; +import java.util.function.Function; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.function.core.FluxWrapper; + +/** + * + * @author Oleg Zhurakousky + * + * @since 2.1 + * + * Will most likely be moved to SCF + */ +class FluxedConsumerWrapper implements Function, Mono>, FluxWrapper>> { + + private final Consumer> consumer; + + FluxedConsumerWrapper(Consumer> consumer) { + this.consumer = consumer; + } + + @Override + public Consumer> getTarget() { + return consumer; + } + + @Override + public Mono apply(Flux t) { + return Mono.fromRunnable(() -> this.consumer.accept(t)); + } + +} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionInvoker.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionInvoker.java index b3a32a73a..c7fdc9945 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionInvoker.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionInvoker.java @@ -18,6 +18,7 @@ package org.springframework.cloud.stream.function; import java.time.Duration; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import java.util.function.Function; import org.apache.commons.logging.Log; @@ -74,13 +75,15 @@ class FunctionInvoker implements Function>, Flux, Flux>) originalUserFunction; + this.userFunction = originalUserFunction instanceof Consumer + ? new FluxedConsumerWrapper<>((Consumer) originalUserFunction) + : (Function, Flux>) originalUserFunction; + Assert.isInstanceOf(Function.class, this.userFunction); this.messageConverter = compositeMessageConverterFactory.getMessageConverterForAllRegistered(); FunctionType functionType = functionInspector.getRegistration(originalUserFunction).getType(); diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/FunctionInvokerTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/FunctionInvokerTests.java index a333067ad..51d16a350 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/FunctionInvokerTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/FunctionInvokerTests.java @@ -17,6 +17,7 @@ package org.springframework.cloud.stream.function; import java.lang.reflect.Field; +import java.util.function.Consumer; import java.util.function.Function; import org.junit.Test; @@ -43,6 +44,7 @@ import org.springframework.messaging.support.GenericMessage; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; /** * @@ -148,6 +150,30 @@ public class FunctionInvokerTests { } } + private static String testWithFluxedConsumerValue; + + @Test + public void testWithFluxedConsumer() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + TestChannelBinderConfiguration.getCompleteConfiguration(MyFunctionsConfiguration.class)) + .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false")) { + + String value = "Hello"; + Message inputMessage = new GenericMessage<>(value); + + StreamFunctionProperties functionProperties = createStreamFunctionProperties(); + + functionProperties.setDefinition("fluxConsumer"); + FunctionInvoker fluxedConsumer = new FunctionInvoker<>(functionProperties, + new FunctionCatalogWrapper(context.getBean(FunctionCatalog.class)), + context.getBean(FunctionInspector.class), context.getBean(CompositeMessageConverterFactory.class)); + + fluxedConsumer.apply(Flux.just(inputMessage)).blockFirst(); + + assertEquals(value, testWithFluxedConsumerValue); + } + } + private StreamFunctionProperties createStreamFunctionProperties() { StreamFunctionProperties functionProperties = new StreamFunctionProperties(); ConsumerProperties consumerProperties = new ConsumerProperties(); @@ -184,6 +210,14 @@ public class FunctionInvokerTests { @EnableAutoConfiguration public static class MyFunctionsConfiguration { + @Bean + public Consumer> fluxConsumer() { + return f -> f.subscribe(v -> { + System.out.println("Consuming flux: " + v); + testWithFluxedConsumerValue = v; + }); + } + @Bean public Function, Message> messageToMessageDifferentType() { return x -> MessageBuilder.withPayload(new Bar()).copyHeaders(x.getHeaders()).build();