Introducing spring-cloud-function-integration

Spring Integration Java DSL, is a tool to compose integration flows programmatically.
We can build the flow not only based on standard EIP components, but also using
protocol-specific channel adapters.
Any generic services also can be used as handler in the flow.
This includes simple lambda operations or functions.

On the other hand Spring Cloud Function provides a `FunctionCatalog`
for registered functions and their compositions & conversions.

With this change we introduce a more high-level DSL to use functions
from catalog directly in the `IntegrationFlow` to gain the best from
both worlds.

* Introduce `spring-cloud-function-integration` module based on
`spring-cloud-function-context` and `spring-boot-starter-integration`
* Expose a `FunctionFlowBuilder` auto-configuration
* Add `FunctionFlowDefinition` to expose `apply()` and `accept()` operators
* Document this new module
This commit is contained in:
abilan
2023-05-08 17:52:21 -04:00
committed by Oleg Zhurakousky
parent f0a49e5be4
commit e0629b5080
10 changed files with 674 additions and 1 deletions

View File

@@ -1,5 +1,5 @@
= Spring Cloud Function Reference Documentation
Mark Fisher, Dave Syer, Oleg Zhurakousky, Anshul Mehra, Dan Dobrin, Chris Bono
Mark Fisher, Dave Syer, Oleg Zhurakousky, Anshul Mehra, Dan Dobrin, Chris Bono, Artem Bilan
*{project-version}*
@@ -11,6 +11,7 @@ The reference documentation consists of the following sections:
<<spring-cloud-function.adoc#,Reference Guide>> :: Spring Cloud Function Reference
https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-cloudevent[Cloud Events] :: Cloud Events
https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-rsocket[RSocket] :: RSocket
<<./spring-integration.adoc#spring-integration,Spring Integration>> :: Spring Integration Framework Interaction
<<aws.adoc#,AWS Adapter>> :: AWS Adapter Reference
<<azure.adoc#, Azure Adapter>> :: Azure Adapter Reference
<<gcp.adoc#, GCP Adapter>> :: GCP Adapter Reference

View File

@@ -0,0 +1,101 @@
[[spring-integration]]
== Spring Integration Interaction
https://spring.io/projects/spring-integration[Spring Integration Framework] extends the Spring programming model to support the well-known Enterprise Integration Patterns.
It enables lightweight messaging within Spring-based applications and supports integration with external systems via declarative adapters.
It also provides a high-level DSL to compose various operations (endpoints) into a logical integration flow.
With a lambda style of this DSL configuration, Spring Integration already has a good level of `java.util.function` interfaces adoption.
The `@MessagingGateway` proxy interface can also be as a `Function` or `Consumer`, which according to the Spring Cloud Function environment can be registered into a function catalog.
See more information in Spring Integration https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#functions-support[ReferenceManual] about its support for functions.
On the other hand, starting with version `4.0.3`, Spring Cloud Function introduces a `spring-cloud-function-integration` module which provides deeper, more cloud-specific and auto-configuration based API for interaction with a `FunctionCatalog` from Spring Integration DSL perspective.
The `FunctionFlowBuilder` is auto-configured and autowired with a `FunctionCatalog` and represents an entry point for function-specific DSL for target `IntegrationFlow` instance.
In addition to standard `IntegrationFlow.from()` factories (for convenience), the `FunctionFlowBuilder` exposes a `fromSupplier(String supplierDefinition)` factory to lookup the target `Supplier` in the provided `FunctionCatalog`.
Then this `FunctionFlowBuilder` leads to the `FunctionFlowDefinition`.
This `FunctionFlowDefinition` is an implementation of the `IntegrationFlowExtension` and exposes `apply(String functionDefinition)` and `accept(String consumerDefinition)` operators to lookup `Function` or `Consumer` from the `FunctionCatalog`, respectively.
See their Javadocs for more information.
The following example demonstrates the `FunctionFlowBuilder` in action alongside with the power of the rest of `IntegrationFlow` API:
[source,java]
----
@Configuration
static class IntegrationConfiguration {
@Bean
Supplier<byte[]> simpleByteArraySupplier() {
return "simple test data"::getBytes;
}
@Bean
Function<String, String> upperCaseFunction() {
return String::toUpperCase;
}
@Bean
BlockingQueue<String> results() {
return new LinkedBlockingQueue<>();
}
@Bean
Consumer<String> simpleStringConsumer(BlockingQueue<String> results) {
return results::add;
}
@Bean
QueueChannel wireTapChannel() {
return new QueueChannel();
}
@Bean
IntegrationFlow someFunctionFlow(FunctionFlowBuilder functionFlowBuilder) {
return functionFlowBuilder
.fromSupplier("simpleByteArraySupplier")
.wireTap("wireTapChannel")
.apply("upperCaseFunction")
.log(LoggingHandler.Level.WARN)
.accept("simpleStringConsumer");
}
}
----
Since the `FunctionCatalog.lookup()` functionality is not limited just to simple function names, a function composition feature can also be used in the mentioned `apply()` and `accept()` operators:
[source,java]
----
@Bean
IntegrationFlow functionCompositionFlow(FunctionFlowBuilder functionFlowBuilder) {
return functionFlowBuilder
.from("functionCompositionInput")
.accept("upperCaseFunction|simpleStringConsumer");
}
----
This API becomes more relevant, when we add into our Spring Cloud applications auto-configuration dependencies for predefined functions.
For example https://spring.io/projects/spring-cloud-stream-applications[Stream Applications] project, in addition to application images, provides artifacts with functions for various integration use-case, e.g. `debezium-supplier`, `elasticsearch-consumer`, `aggregator-function` etc.
The following configuration is based on the `http-supplier`, `spel-function` and `file-consumer`, respectively:
[source,java]
----
@Bean
IntegrationFlow someFunctionFlow(FunctionFlowBuilder functionFlowBuilder) {
return functionFlowBuilder
.fromSupplier("httpSupplier", e -> e.poller(Pollers.trigger(new OnlyOnceTrigger())))
.<Flux<?>>handle((fluxPayload, headers) -> fluxPayload, e -> e.async(true))
.channel(c -> c.flux())
.apply("spelFunction")
.<String, String>transform(String::toUpperCase)
.accept("fileConsumer");
}
----
What we would need else is just to add their configuration into an `application.properties` (if necessary):
[source,properties]
----
http.path-pattern=/testPath
spel.function.expression=new String(payload)
file.consumer.name=test-data.txt
----

View File

@@ -161,6 +161,7 @@
<module>spring-cloud-function-samples</module>
<module>spring-cloud-function-deployer</module>
<module>spring-cloud-function-adapters</module>
<module>spring-cloud-function-integration</module>
<module>spring-cloud-function-rsocket</module>
<module>spring-cloud-function-kotlin</module>
<module>docs</module>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-function-integration</artifactId>
<name>Spring Cloud Function with Spring Integration</name>
<description>Spring Cloud Function with Spring Integration</description>
<parent>
<artifactId>spring-cloud-function-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>4.0.3-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2023-2023 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.cloud.function.integration.dsl;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration;
import org.springframework.context.annotation.Bean;
/**
* The auto-configuration to expose a {@link FunctionFlowBuilder} bean
* based on the auto-configured {@link FunctionCatalog}.
*
* @author Artem Bilan
*
* @since 4.0.3
*/
@AutoConfiguration(after = ContextFunctionCatalogAutoConfiguration.class)
public class FunctionFlowAutoConfiguration {
@Bean
FunctionFlowBuilder functionFlowBuilder(FunctionCatalog functionCatalog) {
return new FunctionFlowBuilder(functionCatalog);
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2023-2023 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.cloud.function.integration.dsl;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.reactivestreams.Publisher;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.dsl.GatewayProxySpec;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlowBuilder;
import org.springframework.integration.dsl.MessageChannelSpec;
import org.springframework.integration.dsl.MessageProducerSpec;
import org.springframework.integration.dsl.MessageSourceSpec;
import org.springframework.integration.dsl.MessagingGatewaySpec;
import org.springframework.integration.dsl.SourcePollingChannelAdapterSpec;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.gateway.MessagingGatewaySupport;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.util.Assert;
/**
* The entry point for starting a {@link FunctionFlowDefinition}.
* Requires a {@link FunctionCatalog} to lookup function instances
* by their names or definitions from respective operators.
* <p>
* In addition to standard {@link IntegrationFlow} {@code from()} overloaded methods (for convenience),
* this class introduces {@link #fromSupplier(String)} factory methods to resolve the target {@link Supplier}
* by its name or function definition from the provided {@link FunctionCatalog}.
* <p>
* This class represents a DSL for functions composition via integration endpoints.
* Extra processing can be done in between functions by the regular {@link IntegrationFlow} operators:
* <pre class="code">
* {@code
* @Bean
* IntegrationFlow someFunctionFlow(FunctionFlowBuilder functionFlowBuilder) {
* return functionFlowBuilder
* .fromSupplier("timeSupplier")
* .apply("spelFunction")
* .log(LoggingHandler.Level.DEBUG, "some.log.category")
* .<String, String>transform(String::toUpperCase)
* .accept("fileConsumer");
* }
* }
* </pre>
*
* @author Artem Bilan
*
* @since 4.0.3
*/
public class FunctionFlowBuilder {
private final FunctionLookupHelper functionLookupHelper;
public FunctionFlowBuilder(FunctionCatalog functionCatalog) {
Assert.notNull(functionCatalog, "'functionCatalog' must not be null");
this.functionLookupHelper = new FunctionLookupHelper(functionCatalog);
}
public FunctionFlowDefinition fromSupplier(String supplierDefinition) {
return fromSupplier(supplierDefinition, null);
}
public FunctionFlowDefinition fromSupplier(String supplierDefinition,
@Nullable Consumer<SourcePollingChannelAdapterSpec> endpointConfigurer) {
return fromSupplier(this.functionLookupHelper.lookupSupplier(supplierDefinition), endpointConfigurer);
}
public <T> FunctionFlowDefinition fromSupplier(Supplier<T> messageSource) {
return fromSupplier(messageSource, null);
}
public <T> FunctionFlowDefinition fromSupplier(Supplier<T> messageSource,
@Nullable Consumer<SourcePollingChannelAdapterSpec> endpointConfigurer) {
return toFunctionFlow(IntegrationFlow.fromSupplier(messageSource, endpointConfigurer));
}
public FunctionFlowDefinition from(MessageChannel messageChannel) {
return toFunctionFlow(IntegrationFlow.from(messageChannel));
}
public FunctionFlowDefinition from(String messageChannelName) {
return from(messageChannelName, false);
}
public FunctionFlowDefinition from(String messageChannelName, boolean fixedSubscriber) {
return toFunctionFlow(IntegrationFlow.from(messageChannelName, fixedSubscriber));
}
public FunctionFlowDefinition from(MessageSourceSpec<?, ? extends MessageSource<?>> messageSourceSpec,
Consumer<SourcePollingChannelAdapterSpec> endpointConfigurer) {
return toFunctionFlow(IntegrationFlow.from(messageSourceSpec, endpointConfigurer));
}
public FunctionFlowDefinition from(MessageSource<?> messageSource) {
return from(messageSource, null);
}
public FunctionFlowDefinition from(MessageSource<?> messageSource,
@Nullable Consumer<SourcePollingChannelAdapterSpec> endpointConfigurer) {
return toFunctionFlow(IntegrationFlow.from(messageSource, endpointConfigurer));
}
public FunctionFlowDefinition from(MessageProducerSupport messageProducer) {
return toFunctionFlow(IntegrationFlow.from(messageProducer));
}
public FunctionFlowDefinition from(MessagingGatewaySupport inboundGateway) {
return toFunctionFlow(IntegrationFlow.from(inboundGateway));
}
public FunctionFlowDefinition from(MessageChannelSpec<?, ?> messageChannelSpec) {
return toFunctionFlow(IntegrationFlow.from(messageChannelSpec));
}
public FunctionFlowDefinition from(MessageProducerSpec<?, ?> messageProducerSpec) {
return toFunctionFlow(IntegrationFlow.from(messageProducerSpec));
}
public FunctionFlowDefinition from(MessageSourceSpec<?, ? extends MessageSource<?>> messageSourceSpec) {
return toFunctionFlow(IntegrationFlow.from(messageSourceSpec));
}
public FunctionFlowDefinition from(MessagingGatewaySpec<?, ?> inboundGatewaySpec) {
return toFunctionFlow(IntegrationFlow.from(inboundGatewaySpec));
}
public FunctionFlowDefinition from(Class<?> serviceInterface) {
return from(serviceInterface, null);
}
public FunctionFlowDefinition from(Class<?> serviceInterface,
@Nullable Consumer<GatewayProxySpec> endpointConfigurer) {
return toFunctionFlow(IntegrationFlow.from(serviceInterface, endpointConfigurer));
}
public FunctionFlowDefinition from(Publisher<? extends Message<?>> publisher) {
return toFunctionFlow(IntegrationFlow.from(publisher));
}
private FunctionFlowDefinition toFunctionFlow(IntegrationFlowBuilder from) {
FunctionFlowDefinition functionFlow = new FunctionFlowDefinition(this.functionLookupHelper);
from.channel(functionFlow.getInputChannel());
functionFlow.addUpstreamComponents(from.get().getIntegrationComponents());
return functionFlow;
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2023-2023 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.cloud.function.integration.dsl;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlowExtension;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
/**
* The {@link IntegrationFlowExtension} implementation for Spring Cloud Function domain.
* Adds operators for functions and consumers and overloaded versions based on their names
* or definitions resolved from the provided {@link org.springframework.cloud.function.context.FunctionCatalog}.
*
* @author Artem Bilan
*
* @since 4.0.3
*/
public final class FunctionFlowDefinition extends IntegrationFlowExtension<FunctionFlowDefinition> {
private final FunctionLookupHelper functionLookupHelper;
FunctionFlowDefinition(FunctionLookupHelper functionLookupHelper) {
this.functionLookupHelper = functionLookupHelper;
}
MessageChannel getInputChannel() {
return getCurrentMessageChannel();
}
void addUpstreamComponents(Map<Object, String> components) {
addComponents(components);
}
/**
* Configure a {@link org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper}
* as a handler in the endpoint by its definition from the
* {@link org.springframework.cloud.function.context.FunctionCatalog}.
* @param functionDefinition the function definition in the function catalog.
* @return the current flow builder.
*/
public FunctionFlowDefinition apply(String functionDefinition) {
return apply(this.functionLookupHelper.lookupFunction(functionDefinition));
}
/**
* Configure a {@link Function} as a handler in the endpoint.
* @param function the {@link Function} to use.
* @return the current flow builder.
*/
public FunctionFlowDefinition apply(Function<Message<?>, ?> function) {
return handle(Message.class, (message, headers) -> function.apply(message));
}
/**
* Configure a {@link org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper}
* as a one-way handler in the final endpoint by its definition from the
* {@link org.springframework.cloud.function.context.FunctionCatalog}.
* @param consumerDefinition the consumer definition in the function catalog.
* @return the current flow builder.
*/
public IntegrationFlow accept(String consumerDefinition) {
return accept(this.functionLookupHelper.lookupConsumer(consumerDefinition));
}
/**
* Configure a {@link Consumer} as a one-way handler in the final endpoint.
* @param consumer the {@link Consumer} to use.
* @return the current flow builder.
*/
public IntegrationFlow accept(Consumer<Message<?>> consumer) {
return handle(consumer::accept)
.get();
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2023-2023 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.cloud.function.integration.dsl;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.cloud.function.context.FunctionCatalog;
/**
* The helper class to lookup functions from the catalog in lazy manner and cache their instances.
*
* @author Artem Bilan
*
* @since 4.0.3
*/
public class FunctionLookupHelper {
private final FunctionCatalog functionCatalog;
FunctionLookupHelper(FunctionCatalog functionCatalog) {
this.functionCatalog = functionCatalog;
}
<P> Supplier<P> lookupSupplier(String functionDefinition) {
return () ->
memoize(() -> this.functionCatalog.<Supplier<P>>lookup(Supplier.class, functionDefinition))
.get()
.get();
}
<P> Function<P, ?> lookupFunction(String functionDefinition) {
return (p) ->
memoize(() -> this.functionCatalog.<Function<P, ?>>lookup(Function.class, functionDefinition))
.get()
.apply(p);
}
<P> Consumer<P> lookupConsumer(String consumerDefinition) {
return (p) ->
memoize(() -> this.functionCatalog.<Consumer<P>>lookup(Consumer.class, consumerDefinition))
.get()
.accept(p);
}
/**
* The delegate {@link Supplier#get()} is called exactly once and the result is cached.
* @param <T> Generic type of supplied value
* @param delegate The actual Supplier
* @return The memoized Supplier
*/
private static <T> Supplier<T> memoize(Supplier<? extends T> delegate) {
AtomicReference<T> value = new AtomicReference<>();
return () -> {
T val = value.get();
if (val == null) {
synchronized (value) {
val = value.get();
if (val == null) {
val = Objects.requireNonNull(delegate.get());
value.set(val);
}
}
}
return val;
};
}
}

View File

@@ -0,0 +1 @@
org.springframework.cloud.function.integration.dsl.FunctionFlowAutoConfiguration

View File

@@ -0,0 +1,136 @@
/*
* Copyright 2023-2023 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.cloud.function.integration.dsl;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.PollerSpec;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.handler.LoggingHandler;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.integration.test.util.OnlyOnceTrigger;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Artem Bilan
*
* @since 4.0.3
*/
@SpringBootTest
@DirtiesContext
public class FunctionFlowTests {
@Autowired
QueueChannel wireTapChannel;
@Autowired
BlockingQueue<String> results;
@Test
void fromSupplierOverFunctionToConsumer() throws InterruptedException {
String result = results.poll(10, TimeUnit.SECONDS);
assertThat(result).isEqualTo("SIMPLE TEST DATA");
Message<?> receive = wireTapChannel.receive(10_000);
assertThat(receive).isNotNull()
.extracting(Message::getPayload)
.isEqualTo("simple test data".getBytes());
}
@Autowired
MessageChannel functionCompositionInput;
@Test
void fromChannelToFunctionComposition() throws InterruptedException {
this.functionCompositionInput.send(new GenericMessage<>("compose this"));
String result = results.poll(10, TimeUnit.SECONDS);
assertThat(result).isEqualTo("COMPOSE THIS");
}
@EnableAutoConfiguration
@Configuration(proxyBeanMethods = false)
static class TestIntegrationConfiguration {
@Bean(PollerMetadata.DEFAULT_POLLER)
PollerSpec defaultPoller() {
return Pollers.trigger(new OnlyOnceTrigger());
}
@Bean
Supplier<byte[]> simpleByteArraySupplier() {
return "simple test data"::getBytes;
}
@Bean
Function<String, String> upperCaseFunction() {
return String::toUpperCase;
}
@Bean
BlockingQueue<String> results() {
return new LinkedBlockingQueue<>();
}
@Bean
Consumer<String> simpleStringConsumer(BlockingQueue<String> results) {
return results::add;
}
@Bean
QueueChannel wireTapChannel() {
return new QueueChannel();
}
@Bean
IntegrationFlow someFunctionFlow(FunctionFlowBuilder functionFlowBuilder) {
return functionFlowBuilder
.fromSupplier("simpleByteArraySupplier")
.wireTap("wireTapChannel")
.apply("upperCaseFunction")
.log(LoggingHandler.Level.WARN, FunctionFlowTests.class.getName())
.accept("simpleStringConsumer");
}
@Bean
IntegrationFlow functionCompositionFlow(FunctionFlowBuilder functionFlowBuilder) {
return functionFlowBuilder
.from("functionCompositionInput")
.accept("upperCaseFunction|simpleStringConsumer");
}
}
}