GH-8626: Provide cleaner transform() DSL (#8653)

* GH-8626: Provide cleaner `transform()` DSL

Fixes https://github.com/spring-projects/spring-integration/issues/8626

* Add missed `transform(String beanName, @Nullable String methodName)` API
* Introduce a `TransformerSpec` to expose a strict API to configure
transformer variants.
* Introduce `transformWith(Consumer<TransformerSpec>)` as a single point
of all possible transformer and its endpoint options
* Deprecate those `IntegrationFlowDefinition.transform()` variants
which are harder to configure as several lambda arguments

This change will make Kotlin & Groovy DSL more readable and straightforward

* * Use new `transformWith()` in tests where a deprecated API still used
* Add JavaDocs to `TransformerSpec`
* Fix generic types for `BaseIntegrationFlowDefinition.transformWith()` -
they make sense exactly on the `TransformerSpec.transformer()` only
* Apply `transformWith()` for Groovy DSL
* Introduce a new `ClassUtils.isLambda(Object candidate)`
and add a check for Groovy `Closure`
* Fix `GroovyIntegrationFlowDefinition.createConfigurerIfAny()` to propagate a `Consumer` argument
down to the `Closure`

* * Rename `TransformerSpec -> TransformerEndpointSpec` for better context
meaning of the class
* Introduce `KotlinTransformerEndpointSpec` as an extension of the `TransformerEndpointSpec`
to have an `inline fun <reified P> transformer(crossinline function: (P) -> Any)`
for Kotlin style
* Add `KotlinIntegrationFlowDefinition.transformWith(KotlinTransformerEndpointSpec)`
* Deprecate Kotlin methods which are covered by the mentioned `transformWith()`
* Fix tests to use new API
* Mentioned the change in the doc
This commit is contained in:
Artem Bilan
2023-06-26 12:01:13 -04:00
committed by GitHub
parent ceb3daec8e
commit 070c1c6e60
20 changed files with 593 additions and 61 deletions

View File

@@ -538,12 +538,18 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
/**
* Populate the {@code Transformer} EI Pattern specific {@link MessageHandler} implementation
* for the SpEL {@link Expression}.
* Shortcut for:
* <pre class="code">
* {@code
* .transformWith((transformerSpec) -> transformerSpec.expression(expression))
* }
* </pre>
* @param expression the {@code Transformer} {@link Expression}.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @see ExpressionEvaluatingTransformer
*/
public B transform(String expression) {
return transform(expression, (Consumer<GenericEndpointSpec<MessageTransformingHandler>>) null);
return transformWith((transformerSpec) -> transformerSpec.expression(expression));
}
/**
@@ -552,8 +558,10 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
* @param expression the {@code Transformer} {@link Expression}.
* @param endpointConfigurer the {@link Consumer} to provide integration endpoint options.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @deprecated since 6.2 in favor of {@link #transformWith(Consumer)}.
* @see ExpressionEvaluatingTransformer
*/
@Deprecated(since = "6.2", forRemoval = true)
public B transform(String expression,
@Nullable Consumer<GenericEndpointSpec<MessageTransformingHandler>> endpointConfigurer) {
@@ -566,9 +574,15 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
/**
* Populate the {@code MessageTransformingHandler} for the {@link MethodInvokingTransformer}
* to invoke the discovered service method at runtime.
* Shortcut for:
* <pre class="code">
* {@code
* .transformWith((transformerSpec) -> transformerSpec.ref(service))
* }
* </pre>
* @param service the service to use.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @see ExpressionEvaluatingTransformer
* @see MethodInvokingTransformer
*/
public B transform(Object service) {
return transform(service, null);
@@ -577,13 +591,36 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
/**
* Populate the {@code MessageTransformingHandler} for the {@link MethodInvokingTransformer}
* to invoke the service method at runtime.
* <pre class="code">
* {@code
* .transformWith((transformerSpec) -> transformerSpec.ref(service).method(methodName))
* }
* </pre>
* @param service the service to use.
* @param methodName the method to invoke.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @see MethodInvokingTransformer
*/
public B transform(Object service, @Nullable String methodName) {
return transform(service, methodName, null);
return transformWith((transformerSpec) -> transformerSpec.ref(service).method(methodName));
}
/**
* Populate the {@code MessageTransformingHandler} for the {@link MethodInvokingTransformer}
* to invoke the bean method at runtime.
* <pre class="code">
* {@code
* .transformWith((transformerSpec) -> transformerSpec.refName(beanName).method(methodName))
* }
* </pre>
* @param beanName the name for bean to resolve lazily.
* @param methodName the method to invoke.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @since 6.2
* @see MethodInvokingTransformer
*/
public B transform(String beanName, @Nullable String methodName) {
return transformWith((transformerSpec) -> transformerSpec.refName(beanName).method(methodName));
}
/**
@@ -593,8 +630,10 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
* @param methodName the method to invoke.
* @param endpointConfigurer the {@link Consumer} to provide integration endpoint options.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @see ExpressionEvaluatingTransformer
* @deprecated since 6.2 in favor of {@link #transformWith(Consumer)}.
* @see MethodInvokingTransformer
*/
@Deprecated(since = "6.2", forRemoval = true)
public B transform(Object service, @Nullable String methodName,
@Nullable Consumer<GenericEndpointSpec<MessageTransformingHandler>> endpointConfigurer) {
@@ -617,12 +656,18 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
* .transform(Scripts.script("classpath:myScript.py").variable("foo", bar()))
* }
* </pre>
* Shortcut for:
* <pre class="code">
* {@code
* .transformWith((transformerSpec) -> transformerSpec.processor(messageProcessorSpec))
* }
* </pre>
* @param messageProcessorSpec the {@link MessageProcessorSpec} to use.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @see MethodInvokingTransformer
*/
public B transform(MessageProcessorSpec<?> messageProcessorSpec) {
return transform(messageProcessorSpec, (Consumer<GenericEndpointSpec<MessageTransformingHandler>>) null);
return transformWith((transformerSpec) -> transformerSpec.processor(messageProcessorSpec));
}
/**
@@ -638,8 +683,10 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
* @param messageProcessorSpec the {@link MessageProcessorSpec} to use.
* @param endpointConfigurer the {@link Consumer} to provide integration endpoint options.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @deprecated since 6.2 in favor of {@link #transformWith(Consumer)}.
* @see MethodInvokingTransformer
*/
@Deprecated(since = "6.2", forRemoval = true)
public B transform(MessageProcessorSpec<?> messageProcessorSpec,
@Nullable Consumer<GenericEndpointSpec<MessageTransformingHandler>> endpointConfigurer) {
@@ -679,7 +726,8 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
* @see LambdaMessageProcessor
*/
public <P, T> B transform(@Nullable Class<P> expectedType, GenericTransformer<P, T> genericTransformer) {
return transform(expectedType, genericTransformer, null);
return transformWith((transformerSpec) ->
transformerSpec.transformer(genericTransformer).expectedType(expectedType));
}
/**
@@ -714,10 +762,12 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
* @param <P> the payload type - 'transform from', or {@code Message.class}.
* @param <T> the target type - 'transform to'.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @deprecated since 6.2 in favor of {@link #transformWith(Consumer)}
* @see MethodInvokingTransformer
* @see LambdaMessageProcessor
* @see GenericEndpointSpec
*/
@Deprecated(since = "6.2", forRemoval = true)
public <P, T> B transform(@Nullable Class<P> expectedType, GenericTransformer<P, T> genericTransformer,
@Nullable Consumer<GenericEndpointSpec<MessageTransformingHandler>> endpointConfigurer) {
@@ -730,6 +780,16 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
.handle(new MessageTransformingHandler(transformer), endpointConfigurer);
}
/**
* Populate a {@link MessageTransformingHandler} into the endpoint with provided {@link TransformerEndpointSpec} options.
* One of the 'expression', 'ref', 'refName', 'processor' or 'function' must be provided.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @since 6.2
*/
public B transformWith(Consumer<TransformerEndpointSpec> transformerConfigurer) {
return register(new TransformerEndpointSpec(), transformerConfigurer);
}
/**
* Populate a {@link MessageFilter} with {@link MessageSelector} for the provided SpEL expression.
* @param expression the SpEL expression.
@@ -2787,7 +2847,7 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
}
protected <S extends ConsumerEndpointSpec<? super S, ? extends MessageHandler>> B register(S endpointSpec,
@Nullable Consumer<S> endpointConfigurer) {
@Nullable Consumer<? super S> endpointConfigurer) {
if (endpointConfigurer != null) {
endpointConfigurer.accept(endpointSpec);

View File

@@ -50,6 +50,12 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* Populate the {@link MessageTransformingHandler} instance for the provided
* {@link GenericTransformer}. Use {@link #transform(Class, GenericTransformer)} if
* you need to access the entire message.
* Shortcut for:
* <pre class="code">
* {@code
* .transformWith((transformerSpec) -> transformerSpec.function(genericTransformer))
* }
* </pre>
* @param genericTransformer the {@link GenericTransformer} to populate.
* @param <S> the source type - 'transform from'.
* @param <T> the target type - 'transform to'.
@@ -58,7 +64,7 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* @see org.springframework.integration.handler.LambdaMessageProcessor
*/
public <S, T> B transform(GenericTransformer<S, T> genericTransformer) {
return transform(null, genericTransformer);
return transformWith((transformerSpec) -> transformerSpec.transformer(genericTransformer));
}
@@ -66,18 +72,21 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* Populate the {@link MessageTransformingHandler} instance for the provided
* {@link GenericTransformer}. In addition, accept options for the integration endpoint
* using {@link GenericEndpointSpec}. Use
* {@link #transform(Class, GenericTransformer, Consumer)} if you need to access the
* entire message.
* {@code .transform((transformerSpec) -> transformerSpec.function(genericTransformer).expectedType(Message.class))}
* if you need to access the entire message.
* @param genericTransformer the {@link GenericTransformer} to populate.
* @param endpointConfigurer the {@link Consumer} to provide integration endpoint
* options.
* @param <S> the source type - 'transform from'.
* @param <T> the target type - 'transform to'.
* @return the current {@link IntegrationFlowDefinition}.
* @deprecated since 6.2 in favor of {@link #transformWith(Consumer)}
* @see org.springframework.integration.transformer.MethodInvokingTransformer
* @see org.springframework.integration.handler.LambdaMessageProcessor
* @see GenericEndpointSpec
*/
@Deprecated(since = "6.2", forRemoval = true)
@SuppressWarnings("removal")
public <S, T> B transform(GenericTransformer<S, T> genericTransformer,
Consumer<GenericEndpointSpec<MessageTransformingHandler>> endpointConfigurer) {
@@ -108,7 +117,7 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* Populate a {@link org.springframework.integration.filter.MessageFilter}
* with {@link org.springframework.integration.filter.MethodInvokingSelector}
* for the provided {@link GenericSelector}.
* In addition accept options for the integration endpoint using {@link FilterEndpointSpec}.
* In addition, accept options for the integration endpoint using {@link FilterEndpointSpec}.
* Typically used with a Java 8 Lambda expression:
* <pre class="code">
* {@code
@@ -152,7 +161,7 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
* Populate a {@link ServiceActivatingHandler} for the
* {@link org.springframework.integration.handler.MethodInvokingMessageProcessor}
* to invoke the provided {@link GenericHandler} at runtime.
* In addition accept options for the integration endpoint using {@link GenericEndpointSpec}.
* In addition, accept options for the integration endpoint using {@link GenericEndpointSpec}.
* Typically used with a Java 8 Lambda expression:
* <pre class="code">
* {@code
@@ -177,7 +186,7 @@ public abstract class IntegrationFlowDefinition<B extends IntegrationFlowDefinit
/**
* Populate the {@link MethodInvokingSplitter} to evaluate the provided
* {@link Function} at runtime.
* In addition accept options for the integration endpoint using {@link GenericEndpointSpec}.
* In addition, accept options for the integration endpoint using {@link GenericEndpointSpec}.
* Typically used with a Java 8 Lambda expression:
* <pre class="code">
* {@code

View File

@@ -0,0 +1,227 @@
/*
* 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.integration.dsl;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.expression.Expression;
import org.springframework.integration.core.GenericTransformer;
import org.springframework.integration.handler.BeanNameMessageProcessor;
import org.springframework.integration.handler.LambdaMessageProcessor;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.transformer.ExpressionEvaluatingTransformer;
import org.springframework.integration.transformer.MessageTransformingHandler;
import org.springframework.integration.transformer.MethodInvokingTransformer;
import org.springframework.integration.transformer.Transformer;
import org.springframework.integration.util.ClassUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* A {@link ConsumerEndpointSpec} for a {@link MessageTransformingHandler} options.
* One of the {@link #expression(String)}, {@link #ref(Object)}, {@link #refName(String)},
* {@link #processor(MessageProcessorSpec)} or {@link #transformer(GenericTransformer)} must be provided.
*
* @author Artem Bilan
*
* @since 6.2
*/
public class TransformerEndpointSpec extends ConsumerEndpointSpec<TransformerEndpointSpec, MessageTransformingHandler> {
private final AtomicBoolean transformerSet = new AtomicBoolean();
private Expression expression;
private Object ref;
private String refName;
@Nullable
private String method;
private GenericTransformer<?, ?> transformer;
@Nullable
private Class<?> expectedType;
private MessageProcessorSpec<?> processor;
protected TransformerEndpointSpec() {
super(new MessageTransformingHandler());
}
/**
* Provide an expression to use an {@link ExpressionEvaluatingTransformer} for the target handler.
* @param expression the SpEL expression to use.
* @return the TransformerSpec
*/
public TransformerEndpointSpec expression(String expression) {
return expression(PARSER.parseExpression(expression));
}
/**
* Provide an expression to use an {@link ExpressionEvaluatingTransformer} for the target handler.
* @param expression the SpEL expression to use.
* @return the TransformerSpec
*/
public TransformerEndpointSpec expression(Expression expression) {
assertTransformerSet();
this.expression = expression;
return this;
}
/**
* Provide a service to use a {@link MethodInvokingTransformer} for the target handler.
* @param ref the service to call as a transformer POJO.
* @return the TransformerSpec
*/
public TransformerEndpointSpec ref(Object ref) {
assertTransformerSet();
this.ref = ref;
return this;
}
/**
* Provide a bean name to use a {@link MethodInvokingTransformer}
* (based on {@link BeanNameMessageProcessor}) for the target handler.
* @param refName the bean name for service to call as a transformer POJO.
* @return the TransformerSpec
*/
public TransformerEndpointSpec refName(String refName) {
assertTransformerSet();
this.refName = refName;
return this;
}
/**
* Provide a service method name to call. Optional.
* Use only together with {@link #ref(Object)} or {@link #refName(String)}.
* @param method the service method name to call.
* @return the TransformerSpec
*/
public TransformerEndpointSpec method(@Nullable String method) {
this.method = method;
return this;
}
/**
* Provide a {@link GenericTransformer} as a direct delegate for {@link MessageTransformingHandler}.
* @param transformer the {@link GenericTransformer} instance to use.
* @param <P> the input type.
* @param <T> the output type.
* @return the TransformerSpec
*/
public <P, T> TransformerEndpointSpec transformer(GenericTransformer<P, T> transformer) {
assertTransformerSet();
this.transformer = transformer;
return this;
}
/**
* Set a {@link GenericTransformer} input argument type.
* Can be a {@link org.springframework.messaging.Message}.
* Ignored for all other transformers, but {@link #transformer(GenericTransformer)}.
* @param expectedType the {@link GenericTransformer} input argument type.
* @param <P> the type ot expect.
* @return the spec.
*/
public <P> TransformerEndpointSpec expectedType(@Nullable Class<P> expectedType) {
this.expectedType = expectedType;
return this;
}
/**
* Provide a {@link MessageProcessorSpec} as a factory for {@link MethodInvokingTransformer} delegate.
* @param processor the {@link MessageProcessorSpec} to use.
* @return the TransformerSpec
*/
public TransformerEndpointSpec processor(MessageProcessorSpec<?> processor) {
assertTransformerSet();
this.processor = processor;
return this;
}
private void assertTransformerSet() {
Assert.isTrue(this.transformerSet.compareAndSet(false, true), this::assertMessage);
}
private String assertMessage() {
String currentTransformerValue = null;
if (this.expression != null) {
currentTransformerValue = "'expression'=" + this.expression;
}
else if (this.ref != null) {
currentTransformerValue = "'ref'=" + this.ref;
}
else if (this.refName != null) {
currentTransformerValue = "'refName'=" + this.refName;
}
else if (this.transformer != null) {
currentTransformerValue = "'transformer'=" + this.transformer;
}
else if (this.processor != null) {
currentTransformerValue = "'processor'=" + this.processor;
}
return "Only one of the 'expression', 'ref', 'refName', 'processor' or 'transformer' can be set. " +
"Current one is " + currentTransformerValue;
}
@Override
public Map<Object, String> getComponentsToRegister() {
Transformer transformer;
if (this.expression != null) {
transformer = new ExpressionEvaluatingTransformer(this.expression);
}
else if (this.ref != null) {
if (this.method != null) {
transformer = new MethodInvokingTransformer(this.ref, this.method);
}
else {
transformer = new MethodInvokingTransformer(this.ref);
}
}
else if (this.refName != null) {
transformer = new MethodInvokingTransformer(new BeanNameMessageProcessor<>(this.refName, this.method));
}
else if (this.processor != null) {
MessageProcessor<?> targetProcessor = this.processor.getObject();
this.componentsToRegister.put(targetProcessor, null);
transformer = new MethodInvokingTransformer(targetProcessor);
}
else if (this.transformer != null) {
transformer = wrapToTransformerIfAny();
}
else {
throw new IllegalStateException(
"One of the 'expression', 'ref', 'refName', 'processor' or 'transformer' must be provided.");
}
this.handler.setTransformer(transformer);
this.componentsToRegister.put(transformer, null);
return super.getComponentsToRegister();
}
private Transformer wrapToTransformerIfAny() {
return this.transformer instanceof Transformer ? (Transformer) this.transformer :
(ClassUtils.isLambda(this.transformer)
? new MethodInvokingTransformer(new LambdaMessageProcessor(this.transformer, this.expectedType))
: new MethodInvokingTransformer(this.transformer, ClassUtils.TRANSFORMER_TRANSFORM_METHOD));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-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.
@@ -41,7 +41,16 @@ import org.springframework.util.Assert;
*/
public class MessageTransformingHandler extends AbstractReplyProducingMessageHandler implements ManageableLifecycle {
private final Transformer transformer;
private Transformer transformer;
/**
* Create a {@link MessageTransformingHandler} instance.
* The target delegate {@link Transformer} must be provided then via setter.
* @since 6.2
*/
public MessageTransformingHandler() {
setRequiresReply(true);
}
/**
* Create a {@link MessageTransformingHandler} instance that delegates to
@@ -49,11 +58,20 @@ public class MessageTransformingHandler extends AbstractReplyProducingMessageHan
* @param transformer The transformer.
*/
public MessageTransformingHandler(Transformer transformer) {
this();
Assert.notNull(transformer, "transformer must not be null");
this.transformer = transformer;
this.setRequiresReply(true);
}
/**
* Provide a {@link Transformer} delegate.
* @param transformer the {@link Transformer} to use.
* @since 6.2
*/
public void setTransformer(Transformer transformer) {
Assert.notNull(transformer, "transformer must not be null");
this.transformer = transformer;
}
@Override
public String getComponentType() {
@@ -77,6 +95,7 @@ public class MessageTransformingHandler extends AbstractReplyProducingMessageHan
@Override
protected void doInit() {
Assert.notNull(this.transformer, "transformer must not be null");
BeanFactory beanFactory = getBeanFactory();
if (beanFactory != null && this.transformer instanceof BeanFactoryAware) {
((BeanFactoryAware) this.transformer).setBeanFactory(beanFactory);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-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.
@@ -17,6 +17,7 @@
package org.springframework.integration.util;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -223,6 +224,19 @@ public abstract class ClassUtils {
return PRIMITIVE_WRAPPER_TYPE_MAP.get(clazz);
}
/**
* Check if object is Java, Kotlin or Groovy lambda.
* @param candidate the {@link Object} to check.
* @return true if object is a Java, Kotlin or Groovy lambda.
* @since 6.2
*/
public static boolean isLambda(Object candidate) {
Class<?> aClass = candidate.getClass();
return isLambda(aClass) ||
(Proxy.isProxyClass(aClass) // Groovy Closure is a Lambda in Java terms
&& Proxy.getInvocationHandler(candidate).getClass().getSimpleName().equals("ConvertedClosure"));
}
/**
* Check if class is Java or Kotlin lambda.
* @param aClass the {@link Class} to check.

View File

@@ -92,9 +92,16 @@ class KotlinIntegrationFlowDefinition(@PublishedApi internal val delegate: Integ
}
/**
* Inline function for [IntegrationFlowDefinition.transform] providing a `transform<MyTypeIn, MyTypeOut>()` variant
* Inline function for [IntegrationFlowDefinition.transform] providing a `transform<MyTypeIn>()` variant
* with reified generic type.
*/
@Deprecated("since 6.2",
ReplaceWith("""
transformWith {
transformer<Type> { }
id("value")
}"""))
@Suppress("DEPRECATION", "REMOVAL")
inline fun <reified P> transform(
crossinline function: (P) -> Any,
crossinline configurer: GenericEndpointSpec<MessageTransformingHandler>.() -> Unit
@@ -103,6 +110,16 @@ class KotlinIntegrationFlowDefinition(@PublishedApi internal val delegate: Integ
this.delegate.transform(P::class.java, { function(it) }) { configurer(it) }
}
/**
* Inline function for [IntegrationFlowDefinition.transformWith]
* providing a `transform<MyTypeIn>()` variant
* with reified generic type.
* @since 6.2
*/
fun transformWith(configurer: KotlinTransformerEndpointSpec.() -> Unit) {
this.delegate.register(KotlinTransformerEndpointSpec(), configurer)
}
/**
* Inline function for [IntegrationFlowDefinition.split] providing a `split<MyTypeIn>()` variant
* with reified generic type.
@@ -305,6 +322,13 @@ class KotlinIntegrationFlowDefinition(@PublishedApi internal val delegate: Integ
* for the provided `Transformer` instance.
* @since 5.3.1
*/
@Deprecated("since 6.2",
ReplaceWith("""
transformWith {
transformer(transformer)
id("value")
}"""))
@Suppress("DEPRECATION", "REMOVAL")
fun transform(
transformer: Transformer,
endpointConfigurer: GenericEndpointSpec<MessageTransformingHandler>.() -> Unit = {}
@@ -313,10 +337,26 @@ class KotlinIntegrationFlowDefinition(@PublishedApi internal val delegate: Integ
this.delegate.transform(transformer) { endpointConfigurer(it) }
}
/**
* Populate the [Transformer] EI Pattern specific [MessageHandler] implementation
* for the provided [Transformer] instance.
* @since 6.2
*/
fun transform(transformer: Transformer) {
this.delegate.transform(transformer)
}
/**
* Populate the [Transformer] EI Pattern specific [MessageHandler] implementation
* for the SpEL [Expression].
*/
@Deprecated("since 6.2",
ReplaceWith("""
transformWith {
expression("value")
id("value")
}"""))
@Suppress("DEPRECATION", "REMOVAL")
fun transform(
expression: String,
endpointConfigurer: GenericEndpointSpec<MessageTransformingHandler>.() -> Unit = {}
@@ -325,6 +365,16 @@ class KotlinIntegrationFlowDefinition(@PublishedApi internal val delegate: Integ
this.delegate.transform(expression, endpointConfigurer)
}
/**
* Populate the [Transformer] EI Pattern specific [MessageHandler] implementation
* for the SpEL [Expression].
* @since 6.2
*/
fun transform(expression: String) {
this.delegate.transform(expression)
}
/**
* Populate the [MessageTransformingHandler] for the [MethodInvokingTransformer]
* to invoke the service method at runtime.
@@ -337,6 +387,14 @@ class KotlinIntegrationFlowDefinition(@PublishedApi internal val delegate: Integ
* Populate the [MessageTransformingHandler] for the [MethodInvokingTransformer]
* to invoke the service method at runtime.
*/
@Deprecated("since 6.2",
ReplaceWith("""
transformWith {
ref("value")
method("value")
id("value")
}"""))
@Suppress("DEPRECATION", "REMOVAL")
fun transform(
service: Any, methodName: String?,
endpointConfigurer: GenericEndpointSpec<MessageTransformingHandler>.() -> Unit
@@ -350,6 +408,13 @@ class KotlinIntegrationFlowDefinition(@PublishedApi internal val delegate: Integ
* [org.springframework.integration.handler.MessageProcessor] from provided [MessageProcessorSpec].
* In addition, accept options for the integration endpoint using [GenericEndpointSpec].
*/
@Deprecated("since 6.2",
ReplaceWith("""
transformWith {
processor("value")
id("value")
}"""))
@Suppress("DEPRECATION", "REMOVAL")
fun transform(
messageProcessorSpec: MessageProcessorSpec<*>,
endpointConfigurer: GenericEndpointSpec<MessageTransformingHandler>.() -> Unit = {}
@@ -358,6 +423,15 @@ class KotlinIntegrationFlowDefinition(@PublishedApi internal val delegate: Integ
this.delegate.transform(messageProcessorSpec, endpointConfigurer)
}
/**
* Populate the [MessageTransformingHandler] instance for the
* [org.springframework.integration.handler.MessageProcessor] from provided [MessageProcessorSpec].
* @since 6.2
*/
fun transform(messageProcessorSpec: MessageProcessorSpec<*>) {
this.delegate.transform(messageProcessorSpec)
}
/**
* Populate a [MessageFilter] with [MessageSelector] for the provided SpEL expression.
* In addition, accept options for the integration endpoint using [KotlinFilterEndpointSpec]:
@@ -566,7 +640,7 @@ class KotlinIntegrationFlowDefinition(@PublishedApi internal val delegate: Integ
delay {
messageGroupId(groupId)
}"""))
@Suppress("DEPRECATION")
@Suppress("DEPRECATION", "REMOVAL")
fun delay(groupId: String, endpointConfigurer: DelayerEndpointSpec.() -> Unit = {}) {
this.delegate.delay(groupId, endpointConfigurer)
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 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.integration.dsl
import org.springframework.integration.transformer.MessageTransformingHandler
/**
* A [TransformerEndpointSpec] wrapped for Kotlin DSL.
*
* @property delegate the [TransformerEndpointSpec] this instance is delegating to.
*
* @author Artem Bilan
*
* @since 6.2
*/
class KotlinTransformerEndpointSpec : TransformerEndpointSpec() {
/**
* Provide a Kotlin function as a direct delegate for [MessageTransformingHandler].
* @param function the function instance to use.
* @param <P> the input type.
*/
inline fun <reified P> transformer(crossinline function: (P) -> Any) {
expectedType(P::class.java)
transformer<P, Any> { function(it) }
}
}

View File

@@ -64,6 +64,7 @@ import org.springframework.integration.dsl.MessageChannels;
import org.springframework.integration.dsl.PollerSpec;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.dsl.QueueChannelSpec;
import org.springframework.integration.dsl.TransformerEndpointSpec;
import org.springframework.integration.dsl.Transformers;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.EventDrivenConsumer;
@@ -671,17 +672,24 @@ public class IntegrationFlowTests {
.fixedSubscriberChannel()
.<String, Integer>transform(Integer::parseInt)
.transform(Foo::new)
.transform(new PayloadSerializingTransformer(),
c -> c.autoStartup(false).id("payloadSerializingTransformer"))
.transformWith(this::payloadSerializingTransformer)
.channel(MessageChannels.queue(new SimpleMessageStore(), "fooQueue"))
.transform(Transformers.deserializer(Foo.class.getName()))
.<Foo, Integer>transform(f -> f.value)
.filter("true", e -> e.id("expressionFilter"))
.channel(publishSubscribeChannel())
.transform((Integer p) -> p * 2, c -> c.advice(this.expressionAdvice()))
.transformWith(t -> t
.transformer((Integer p) -> p * 2)
.advice(expressionAdvice()))
.get();
}
private void payloadSerializingTransformer(TransformerEndpointSpec spec) {
spec.transformer(new PayloadSerializingTransformer())
.autoStartup(false)
.id("payloadSerializingTransformer");
}
@Bean
public MessageChannel publishSubscribeChannel() {
return new PublishSubscribeChannel();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2022 the original author or authors.
* Copyright 2016-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.
@@ -190,7 +190,8 @@ public class ManualFlowTests {
IntegrationFlow myFlow = f -> f
.<String, String>transform(String::toUpperCase)
.channel(MessageChannels.queue())
.transform("Hello, "::concat, e -> e
.transformWith(t -> t
.transformer("Hello, "::concat)
.poller(p -> p
.fixedDelay(10)
.maxMessagesPerPoll(1)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2022 the original author or authors.
* Copyright 2016-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.
@@ -262,8 +262,10 @@ public class ReactiveStreamsTests {
return IntegrationFlow
.from("inputChannel")
.split(s -> s.delimiters(","))
.<String, Integer>transform(Integer::parseInt,
e -> e.reactive(flux -> flux.publishOn(Schedulers.parallel())).id("reactiveTransformer"))
.transformWith(t -> t
.<String, Integer>transformer(Integer::parseInt)
.reactive(flux -> flux.publishOn(Schedulers.parallel()))
.id("reactiveTransformer"))
.channel(MessageChannels.queue())
.log()
.toReactivePublisher();

View File

@@ -460,10 +460,11 @@ public class TransformerTests {
@Bean
public IntegrationFlow transformFlowWithError() {
return f -> f
.transform(p -> {
throw new RuntimeException("intentional");
},
e -> e.advice(expressionAdvice()))
.transformWith((t) ->
t.transformer(p -> {
throw new RuntimeException("intentional");
})
.advice(expressionAdvice()))
.log();
}

View File

@@ -161,7 +161,10 @@ class KotlinDslTests {
val integrationFlow =
integrationFlow(publisher) {
transform<Message<Int>>({ it.payload * 2 }) { id("foo") }
transformWith {
transformer<Message<Int>> { it.payload * 2 }
id("foo")
}
channel(fluxChannel)
}
@@ -249,7 +252,10 @@ class KotlinDslTests {
@Bean
fun functionFlow() =
integrationFlow<Function<ByteArray, String>>({ beanName("functionGateway") }) {
transform(Transformers.objectToString()) { id("objectToStringTransformer") }
transformWith {
transformer(Transformers.objectToString())
id("objectToStringTransformer")
}
transform<String> { it.uppercase() }
split<Message<*>> { it.payload }
split<String>({ it }) { id("splitterEndpoint") }
@@ -292,7 +298,10 @@ class KotlinDslTests {
fun fixedSubscriberFlow() =
integrationFlow("fixedSubscriberInput", true) {
log<Any>(LoggingHandler.Level.WARN) { it.payload }
transform("payload") { id("spelTransformer") }
transformWith {
expression("payload")
id("spelTransformer")
}
}
@Bean

View File

@@ -448,8 +448,9 @@ public class FileTests {
.from(Files.inboundAdapter(directory).recursive(true),
e -> e.poller(p -> p.fixedDelay(1000))
.id(directory.getName() + ".adapter"))
.transform(Files.toStringTransformer(),
e -> e.id(directory.getName() + ".transformer"))
.transformWith(t -> t
.transformer(Files.toStringTransformer())
.id(directory.getName() + ".transformer"))
.channel(this.dynamicAdaptersResult)
.get();
this.beanFactory.initializeBean(integrationFlow, directory.getName());

View File

@@ -47,6 +47,7 @@ import org.springframework.integration.dsl.ResequencerSpec
import org.springframework.integration.dsl.RouterSpec
import org.springframework.integration.dsl.ScatterGatherSpec
import org.springframework.integration.dsl.SplitterEndpointSpec
import org.springframework.integration.dsl.TransformerEndpointSpec
import org.springframework.integration.dsl.WireTapSpec
import org.springframework.integration.filter.MethodInvokingSelector
import org.springframework.integration.handler.BridgeHandler
@@ -289,8 +290,10 @@ class GroovyIntegrationFlowDefinition {
* for the SpEL {@link org.springframework.expression.Expression}.
* @param expression the {@code Transformer} {@link org.springframework.expression.Expression}.
* @param endpointConfigurer the {@link Consumer} to provide integration endpoint options.
* @param endpointConfigurer the {@link Consumer} to provide integration endpoint options.
* @see org.springframework.integration.transformer.ExpressionEvaluatingTransformer
*/
@Deprecated(since = '6.2', forRemoval = true)
GroovyIntegrationFlowDefinition transform(
String expression,
@DelegatesTo(value = GenericEndpointSpec<MessageTransformingHandler>, strategy = Closure.DELEGATE_FIRST)
@@ -308,8 +311,10 @@ class GroovyIntegrationFlowDefinition {
* @param service the service to use.
* @param methodName the method to invoke.
* @param endpointConfigurer the {@link Consumer} to provide integration endpoint options.
* @deprecated since 6.2 in favor of {@link #transform(Closure)}
* @see ExpressionEvaluatingTransformer
*/
@Deprecated(since = '6.2', forRemoval = true)
GroovyIntegrationFlowDefinition transform(
Object service, String methodName = null,
@DelegatesTo(value = GenericEndpointSpec<MessageTransformingHandler>, strategy = Closure.DELEGATE_FIRST)
@@ -326,8 +331,10 @@ class GroovyIntegrationFlowDefinition {
* In addition accept options for the integration endpoint using {@link GenericEndpointSpec}.
* @param messageProcessorSpec the {@link MessageProcessorSpec} to use.
* @param endpointConfigurer the {@link Consumer} to provide integration endpoint options.
* @deprecated since 6.2 in favor of {@link #transform(Closure)}
* @see MethodInvokingTransformer
*/
@Deprecated(since = '6.2', forRemoval = true)
GroovyIntegrationFlowDefinition transform(
MessageProcessorSpec<?> messageProcessorSpec,
@DelegatesTo(value = GenericEndpointSpec<MessageTransformingHandler>, strategy = Closure.DELEGATE_FIRST)
@@ -338,6 +345,24 @@ class GroovyIntegrationFlowDefinition {
this
}
/**
* Populate the {@link MessageTransformingHandler} instance for the
* {@link org.springframework.integration.handler.MessageProcessor} from provided {@link MessageProcessorSpec}.
* In addition accept options for the integration endpoint using {@link GenericEndpointSpec}.
* @param messageProcessorSpec the {@link MessageProcessorSpec} to use.
* @param transformerConfigurer the {@link Consumer} to provide integration endpoint options.
* @see MethodInvokingTransformer
* @since 6.2
*/
GroovyIntegrationFlowDefinition transform(
@DelegatesTo(value = TransformerEndpointSpec, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType.class, options = 'org.springframework.integration.dsl.TransformerEndpointSpec')
Closure<?> transformerConfigurer) {
this.delegate.transformWith createConfigurerIfAny(transformerConfigurer)
this
}
/**
* Populate the {@link MessageTransformingHandler} instance
* for the provided {@code payloadType} to convert at runtime.
@@ -367,7 +392,9 @@ class GroovyIntegrationFlowDefinition {
* @param endpointConfigurer the {@link Consumer} to provide integration endpoint options.
* @param < P > the payload type - 'transform from', or {@code Message.class}.
* @param < T > the target type - 'transform to'.
* @deprecated since 6.2 in favor of {@link #transform(Closure)}
*/
@Deprecated(since = '6.2', forRemoval = true)
<P, T> GroovyIntegrationFlowDefinition transform(
GenericTransformer<P, T> genericTransformer,
@DelegatesTo(value = GenericEndpointSpec<MessageTransformingHandler>, strategy = Closure.DELEGATE_FIRST)
@@ -389,7 +416,9 @@ class GroovyIntegrationFlowDefinition {
* @param endpointConfigurer the {@link Consumer} to provide integration endpoint options.
* @param < P > the payload type - 'transform from', or {@code Message.class}.
* @param < T > the target type - 'transform to'.
* @deprecated since 6.2 in favor of {@link #transform(Closure)}
*/
@Deprecated(since = '6.2', forRemoval = true)
<P, T> GroovyIntegrationFlowDefinition transform(
Class<P> expectedType,
GenericTransformer<P, T> genericTransformer,
@@ -1336,7 +1365,7 @@ class GroovyIntegrationFlowDefinition {
return {
closure.delegate = it
closure.resolveStrategy = Closure.DELEGATE_FIRST
closure()
closure(it)
} as Consumer<T>
}
null

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022 the original author or authors.
* Copyright 2022-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.
@@ -130,11 +130,14 @@ class GroovyDslTests {
def publisher = Flux.just(2, 3).map { new GenericMessage<>(it) }
def integrationFlow =
integrationFlow(publisher)
{
transform Message<Integer>, { it.payload * 2 }, { id 'foo' }
channel fluxChannel
}
integrationFlow(publisher) {
transform {
it.<Message<Integer>, Integer>transformer { it.payload * 2 }
expectedType Message<Integer>
id 'foo'
}
channel fluxChannel
}
def registration = this.integrationFlowContext.registration(integrationFlow).register()
@@ -217,7 +220,7 @@ class GroovyDslTests {
assert groovyTestService.result.get() == 'TEST'
}
@Configuration
@Configuration(proxyBeanMethods = false)
@EnableIntegration
static class Config {
@@ -240,7 +243,9 @@ class GroovyDslTests {
requestReplyFlow() {
integrationFlow {
fluxTransform { it.map { it } }
transform String, { it.toUpperCase() }
transform {
transformer { it.toUpperCase() }
}
}
}
@@ -257,8 +262,13 @@ class GroovyDslTests {
integrationFlow Function<byte[], String>,
{ beanName 'functionGateway' },
{
transform Transformers.objectToString(), { id 'objectToStringTransformer' }
transform String, { it.toUpperCase() }
transform {
transformer Transformers.objectToString()
id 'objectToStringTransformer'
}
transform {
transformer { it.toUpperCase() }
}
split Message<?>, { it.payload }
split Object, { it }, { id 'splitterEndpoint' }
resequence()
@@ -314,11 +324,13 @@ class GroovyDslTests {
wireTap integrationFlow {
channel { queue 'wireTapChannel' }
}
delay {
delay {
messageGroupId 'delayGroup'
defaultDelay 100
}
transform String, { it.toUpperCase() }
transform {
transformer { it.toUpperCase() }
}
}
}

View File

@@ -212,7 +212,9 @@ public class WebFluxObservationPropagationTests {
.payloadExpression("#requestParams.name[0]")
.requestChannel(webFluxRequestChannel)
.id("webFluxGateway"))
.<String, String>transform(String::toLowerCase, e -> e.id("testTransformer"))
.transformWith(t -> t
.<String, String>transformer(String::toLowerCase)
.id("testTransformer"))
.get();
}

View File

@@ -283,8 +283,10 @@ The following example demonstrates how to change the publishing thread from the
public IntegrationFlow reactiveEndpointFlow() {
return IntegrationFlow
.from("inputChannel")
.<String, Integer>transform(Integer::parseInt,
e -> e.reactive(flux -> flux.publishOn(Schedulers.parallel())))
.transformWith(t -> t
.<String, Integer>transformer(Integer::parseInt)
.reactive(flux -> flux.publishOn(Schedulers.parallel()))
)
.get();
}
----
@@ -304,9 +306,13 @@ Each of them has generic arguments, so it lets you configure an endpoint and eve
@Bean
public IntegrationFlow flow2() {
return IntegrationFlow.from(this.inputChannel)
.transform(new PayloadSerializingTransformer(),
c -> c.autoStartup(false).id("payloadSerializingTransformer"))
.transform((Integer p) -> p * 2, c -> c.advice(this.expressionAdvice()))
.transformWith(t -> t
.transformer(new PayloadSerializingTransformer())
.autoStartup(false)
.id("payloadSerializingTransformer"))
.transformWith(t -> t
.transformer((Integer p) -> p * 2)
.advice(expressionAdvice()))
.get();
}
----
@@ -361,6 +367,10 @@ Nevertheless, the DSL parser takes care of bean declarations for inline objects,
See https://docs.spring.io/spring-integration/api/org/springframework/integration/dsl/Transformers.html[Transformers] in the Javadoc for more information and supported factory methods.
Starting with version 6.2, a `transformWith(Consumer<TransformerEndpointSpec>)` variant has been introduced to have all the transformer and its endpoint options to be configured via single builder argument.
This style gives DSL more readability and increases developer experience while modifying code.
This also make Groovy and Kotlin DSLs more straightforward.
Also see <<java-dsl-class-cast>>.
[[java-dsl-inbound-adapters]]
@@ -1428,4 +1438,4 @@ IntegrationFlow otherFlow() {
The composition in the middle of the flow is simply achievable with an existing `gateway(IntegrationFlow)` EIP-method.
This way we can build flows with any complexity by composing them from simpler, reusable logical blocks.
For example, you may add a library of `IntegrationFlow` beans as a dependency and it is just enough to have their configuration classes imported to the final project and autowired for your `IntegrationFlow` definitions.
For example, you may add a library of `IntegrationFlow` beans as a dependency, and it is just enough to have their configuration classes imported to the final project and autowired for your `IntegrationFlow` definitions.

View File

@@ -39,7 +39,10 @@ flowLambda() {
messageGroupId 'delayGroup'
defaultDelay 100
}
transform String, { it.toUpperCase() }
transform {
transformer { it.toUpperCase() }
expectedType String
}
}
}
----
@@ -78,8 +81,14 @@ functionFlow() {
integrationFlow Function<byte[], String>,
{ beanName 'functionGateway' },
{
transform Transformers.objectToString(), { id 'objectToStringTransformer' }
transform String, { it.toUpperCase() }
transform {
transformer Transformers.objectToString()
id 'objectToStringTransformer'
}
transform {
transformer { it.toUpperCase() }
expectedType String
}
split Message<?>, { it.payload }
split Object, { it }, { id 'splitterEndpoint' }
resequence()

View File

@@ -32,7 +32,7 @@ fun flowLambda() =
wireTap {
handle { println(it.payload) }
}
transform<String, String> { it.toUpperCase() }
transform<String> { it.toUpperCase() }
}
----
====
@@ -67,7 +67,7 @@ For example:
@Bean
fun functionFlow() =
integrationFlow<Function<String, String>>({ beanName("functionGateway") }) {
transform<String, String> { it.toUpperCase() }
transform<String> { it.toUpperCase() }
}
@Bean

View File

@@ -28,3 +28,6 @@ See <<./debezium.adoc#debezium, Debezium Support>> for more information.
- The XML configuration for `<poller>` and `@Poller` annotation now support ISO 8601 duration format for `fixed-delay`, `fixed-rate` and `initial-delay` options.
See <<./endpoint.adoc#endpoint-pollingconsumer, Polling Consumer>> for more information.
- Java, Groovy and Kotlin DSLs have now context-specific methods in the `IntegationFlowDefinition` with a single `Consumer` argument to configure an endpoint and its handler with one builder and readable options.
See, for example, `transformWith()` in <<./dsl.adoc#java-dsl, Java DSL Chapter>>.