GH-3869: Add ContextHolderRequestHandlerAdvice

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

* Move `ContextHolderRequestHandlerAdvice` to the `core` module for more general purposes
* Add `ContextHolderRequestHandlerAdviceTests`
* Rework `DelegatingSessionFactoryTests` to rely on the `ContextHolderRequestHandlerAdvice`.
This allows us to remove unnecessary XML configuration for this test class
* Document the feature
This commit is contained in:
Adel Haidar
2022-12-04 16:36:50 -05:00
committed by abilan
parent b0093ef161
commit bfc6931329
8 changed files with 201 additions and 53 deletions

View File

@@ -231,33 +231,10 @@ private static final class SharedSSLFTPSClient extends FTPSClient {
Version 4.2 introduced the `DelegatingSessionFactory`, which allows the selection of the actual session factory at runtime.
Prior to invoking the FTP endpoint, call `setThreadKey()` on the factory to associate a key with the current thread.
That key is then used to lookup the actual session factory to be used.
That key is then used to look up the actual session factory to be used.
You can clear the key by calling `clearThreadKey()` after use.
We added convenience methods so that you can easily do use a delegating session factory from a message flow.
The following example shows how to declare a delegating session factory:
====
[source, xml]
----
<bean id="dsf" class="org.springframework.integration.file.remote.session.DelegatingSessionFactory">
<constructor-arg>
<bean class="o.s.i.file.remote.session.DefaultSessionFactoryLocator">
<!-- delegate factories here -->
</bean>
</constructor-arg>
</bean>
<int:service-activator input-channel="in" output-channel="c1"
expression="@dsf.setThreadKey(#root, headers['factoryToUse'])" />
<int-ftp:outbound-gateway request-channel="c1" reply-channel="c2" ... />
<int:service-activator input-channel="c2" output-channel="out"
expression="@dsf.clearThreadKey(#root)" />
----
====
See <<./handler-advice.adoc#context-holder-advice, Context Holder Advice>> for more information how this factory can be used together with a `ContextHolderRequestHandlerAdvice`.
IMPORTANT: When you use session caching (see <<ftp-session-caching>>), each of the delegates should be cached.
You cannot cache the `DelegatingSessionFactory` itself.

View File

@@ -57,6 +57,9 @@ In addition to providing the general mechanism to apply AOP advice classes, Spri
* `RateLimiterRequestHandlerAdvice` (described in <<rate-limiter-advice>>)
* `CacheRequestHandlerAdvice` (described in <<cache-advice>>)
* `ReactiveRequestHandlerAdvice` (described in <<reactive-advice>>)
* `ContextHolderRequestHandlerAdvice` (described in <<context-holder-advice>>)
[[expression-advice]]
[[retry-advice]]
===== Retry Advice
@@ -571,6 +574,44 @@ The `message` argument is the request message for the message handler and can be
The `mono` argument is the result of this message handler's `handleRequestMessage()` method implementation.
A nested `Mono.transform()` can also be called from this function to apply, for example, a https://spring.io/projects/spring-cloud-circuitbreaker[Reactive Circuit Breaker].
[[context-holder-advice]]
==== Context Holder Advice
Starting with version 6.1, the `ContextHolderRequestHandlerAdvice` has been introduced.
This advice takes some value from the request message as and stores it in the context holder.
The value is clear from the context when an execution is finished on the target `MessageHandler`.
The best way to think about this advice is similar to the programming flow where we store some value into a `ThreadLocal`, get access to it from the target call and then clean up the `ThreadLocal` after execution.
The `ContextHolderRequestHandlerAdvice` requires these constructor arguments: a `Function<Message<?>, Object>` as a value provider, `Consumer<Object>` as a context set callback and `Runnable` as a context clean up hook.
Following is a sample how a `ContextHolderRequestHandlerAdvice` can be used in combination with a `o.s.i.file.remote.session.DelegatingSessionFactory`:
====
[source, java]
----
@Bean
DelegatingSessionFactory<?> dsf(SessionFactory<?> one, SessionFactory<?> two) {
return new DelegatingSessionFactory<>(Map.of("one", one, "two", two), null);
}
@Bean
ContextHolderRequestHandlerAdvice contextHolderRequestHandlerAdvice(DelegatingSessionFactory<String> dsf) {
return new ContextHolderRequestHandlerAdvice(message -> message.getHeaders().get("FACTORY_KEY"),
dsf::setThreadKey, dsf::clearThreadKey);
}
@ServiceActivator(inputChannel = "in", adviceChain = "contextHolderRequestHandlerAdvice")
FtpOutboundGateway ftpOutboundGateway(DelegatingSessionFactory<?> sessionFactory) {
return new FtpOutboundGateway(sessionFactory, "ls", "payload");
}
----
====
And it is just enough to send a message to the `in` channel with a `FACTORY_KEY` header set to either `one` or `two`.
The `ContextHolderRequestHandlerAdvice` sets the value from that header into a `DelegatingSessionFactory` via its `setThreadKey`.
Then when `FtpOutboundGateway` executes an `ls` command a proper delegating `SessionFactory` is chosen from the `DelegatingSessionFactory` according to the value in its `ThreadLocal`.
When the result is produced from the `FtpOutboundGateway`, a `ThreadLocal` value in the `DelegatingSessionFactory` is cleared according to the `clearThreadKey()` call from the `ContextHolderRequestHandlerAdvice`.
See <<./ftp.adoc#ftp-dsf,Delegating Session Factory>> for more information.
[[custom-advice]]
==== Custom Advice Classes

View File

@@ -23,6 +23,11 @@ In general the project has been moved to the latest dependency versions.
The Zip Spring Integration Extension project has been migrated as the `spring-integration-zip` module.
See <<./zip.adoc#zip,Zip Support>> for more information.
[[x6.1-context-holder-advice]]
==== `ContextHolderRequestHandlerAdvice`
The `ContextHolderRequestHandlerAdvice` allows to store a value from a request message into some context around `MessageHandler` execution.
See <<./handler-advice.adoc#context-holder-advice, Context Holder Advice>> for more information.
[[x6.1-general]]
=== General Changes