INT-3538: Docs for Routing Slip

JIRA: https://jira.spring.io/browse/INT-3538

INT-3538-2: `RoutingSlipHeaderValueMessageProcessor` improvement

Since `HeaderEnricher` populates `beanFactory` to its `HeaderValueMessageProcessor`s,
there is no reason to specify `RoutingSlipHeaderValueMessageProcessor` as a separate bean.
Therefore the `IntegrationEvaluationContextAware` may not be reached.
Remove its support from `RoutingSlipHeaderValueMessageProcessor` and just rely on the `setBeanFactory`

RS Doc Polishing
This commit is contained in:
Artem Bilan
2014-11-04 17:17:12 +02:00
committed by Gary Russell
parent af19bbfa70
commit efe3b11fc3
3 changed files with 183 additions and 16 deletions

View File

@@ -157,12 +157,14 @@ public class HeaderEnricher extends IntegrationObjectSupport implements Transfor
if (processor instanceof BeanFactoryAware && this.getBeanFactory() != null) {
((BeanFactoryAware) processor).setBeanFactory(this.getBeanFactory());
}
Boolean processerOverwrite = processor.isOverwrite();
if (processerOverwrite != null) {
shouldOverwrite |= processerOverwrite;
Boolean processorOverwrite = processor.isOverwrite();
if (processorOverwrite != null) {
shouldOverwrite |= processorOverwrite;
}
}
if (this.messageProcessor != null && this.messageProcessor instanceof BeanFactoryAware && this.getBeanFactory() != null) {
if (this.messageProcessor != null
&& this.messageProcessor instanceof BeanFactoryAware
&& this.getBeanFactory() != null) {
((BeanFactoryAware) this.messageProcessor).setBeanFactory(this.getBeanFactory());
}
if (!shouldOverwrite && !this.shouldSkipNulls) {

View File

@@ -26,7 +26,7 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.expression.EvaluationContext;
import org.springframework.integration.expression.IntegrationEvaluationContextAware;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.routingslip.ExpressionEvaluatingRoutingSlipRouteStrategy;
import org.springframework.integration.routingslip.RoutingSlipRouteStrategy;
import org.springframework.messaging.Message;
@@ -46,7 +46,7 @@ import org.springframework.util.Assert;
*/
public class RoutingSlipHeaderValueMessageProcessor
extends AbstractHeaderValueMessageProcessor<Map<List<Object>, Integer>>
implements BeanFactoryAware, IntegrationEvaluationContextAware {
implements BeanFactoryAware {
private final List<String> routingSlipPath;
@@ -64,12 +64,9 @@ public class RoutingSlipHeaderValueMessageProcessor
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.beanFactory = beanFactory;
}
@Override
public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) {
this.evaluationContext = evaluationContext;
this.evaluationContext = IntegrationContextUtils.getEvaluationContext(beanFactory);
}
@Override

View File

@@ -985,7 +985,7 @@ public List<String> route(@Header("orderStatus") OrderStatus status)]]></program
<para>
Note that these methods can be used for simple changes (updating a single route or adding/removing a
route). However, if you want to remove one route and add another, the updates are not atomic. This
means the routing table may be in an indeterminate state betweent the updates. Starting with
means the routing table may be in an indeterminate state between the updates. Starting with
<emphasis>version 4.0</emphasis>, you can now use the control bus to update the entire routing
table atomically.
</para>
@@ -1002,10 +1002,10 @@ public List<String> route(@Header("orderStatus") OrderStatus status)]]></program
Notice that the parameter is a properties object; this allows the use of the inbuilt
<code>StringToPropertiesConverter</code> by a control bus command, for example:
<programlisting>"@'router.handler'.replaceChannelMappings('foo=qux \n baz=bar')"</programlisting>
- note that each mapping is separted by a newline character (<code>\n</code>). For programmatic
- note that each mapping is separated by a newline character (<code>\n</code>). For programmatic
changes to the map, it is recommended that the <code>setChannelMappings</code> method
is used instead, for type-safety. Any non-String keys or values passed into
<code>replaceChannelMappings</code> are ingnored.
<code>replaceChannelMappings</code> are ignored.
</para>
</listitem>
</itemizedlist>
@@ -1018,15 +1018,183 @@ public List<String> route(@Header("orderStatus") OrderStatus status)]]></program
</para>
<note>
<para>
For more information about Spring Integration's JMX suppor, please see chapter <emphasis><xref linkend="jmx" endterm="jmx.title"/></emphasis>.
For more information about Spring Integration's JMX support, please see chapter
<emphasis><xref linkend="jmx" endterm="jmx.title"/></emphasis>.
</para>
</note>
</section>
<section id="routing-slip">
<title>Routing Slip</title>
<para>
TBD
Starting with <emphasis>version 4.1</emphasis>, Spring Integration provides an implementation
of the <ulink url="http://www.eaipatterns.com/RoutingTable.html">Routing Slip</ulink>
Enterprise Integration Pattern. It is implemented as a <code>routingSlip</code> message header
which is used to determine the next channel in
<classname>AbstractMessageProducingHandler</classname>s, when an <code>outputChannel</code> isn't
specified for the endpoint. This pattern is useful in complex, dynamic, cases when it can become
difficult to configure multiple routers to determine message flow. When a message arrives at an
endpoint that has no <code>output-channel</code>, the <code>routingSlip</code> is consulted
to determine the next channel to which the message will be sent. When the routing slip is exhausted,
normal <code>replyChannel</code> processing resumes.
</para>
<para>
Configuration for the <emphasis>Routing Slip</emphasis> is presented as a
<code>HeaderEnricher</code> option - a <emphasis>semicolon-separated</emphasis> Routing Slip
<code>path</code> entries:
</para>
<programlisting language="xml"><![CDATA[<util:properties id="properties">
<beans:prop key="myRoutePath1">channel1</beans:prop>
<beans:prop key="myRoutePath2">request.headers[myRoutingSlipChannel]</beans:prop>
</util:properties>
<context:property-placeholder properties-ref="properties"/>
<header-enricher input-channel="input" output-channel="process">
<routing-slip
value="${myRoutePath1}; @routingSlipRoutingPojo.get(request, reply);
routingSlipRoutingStrategy; ${myRoutePath2}; finishChannel"/>
</header-enricher>]]></programlisting>
<para>
In this sample we have:
</para>
<itemizedlist>
<listitem>
<para>
A <code>&lt;context:property-placeholder&gt;</code> configuration to demonstrate that the
entries in the Routing Slip <code>path</code> can be specified as resolvable keys.
</para>
</listitem>
<listitem>
<para>
The <code>&lt;header-enricher&gt;</code> <code>&lt;routing-slip&gt;</code> sub-element is
used to populate the <classname>RoutingSlipHeaderValueMessageProcessor</classname> to the
<classname>HeaderEnricher</classname> handler.
</para>
</listitem>
<listitem>
<para>
The <classname>RoutingSlipHeaderValueMessageProcessor</classname> accepts a String array of
resolved Routing Slip <code>path</code> entries and returns (from <code>processMessage()</code>)
a <code>singletonMap</code> with the <code>path</code> as <code>key</code> and <code>0</code>
as initial <code>routingSlipIndex</code>.
</para>
</listitem>
</itemizedlist>
<para>
Routing Slip <code>path</code> entries can contain <interfacename>MessageChannel</interfacename> bean
names, <interfacename>RoutingSlipRouteStrategy</interfacename> bean names and also Spring
expressions (SpEL). The <classname>RoutingSlipHeaderValueMessageProcessor</classname> checks each
Routing Slip <code>path</code> entry against the <interfacename>BeanFactory</interfacename> on the
first <code>processMessage</code> invocation. It converts entries, which aren't bean names in the
application context, to <classname>ExpressionEvaluatingRoutingSlipRouteStrategy</classname> instances.
<interfacename>RoutingSlipRouteStrategy</interfacename> entries are invoked multiple times, until
they return null, or an empty String.
</para>
<para>
Since the <emphasis>Routing Slip</emphasis> is involved in the <code>getOutputChannel</code> process we
have a <emphasis>request-reply</emphasis> context. The
<interfacename>RoutingSlipRouteStrategy</interfacename> has been introduced to determine the next
<code>outputChannel</code> using the <code>requestMessage</code>, as well as the <code>reply</code>
object. An implementation of this strategy should be registered as a bean in the application context
and its bean name is used in the Routing Slip <code>path</code>. The
<classname>ExpressionEvaluatingRoutingSlipRouteStrategy</classname> implementation is provided. It
accepts a SpEL expression, and an internal
<classname>ExpressionEvaluatingRoutingSlipRouteStrategy.RequestAndReply</classname> object is used as
the <emphasis>root object</emphasis> of the evaluation context. This is to avoid the overhead of
<interfacename>EvaluationContext</interfacename> creation for each
<code>ExpressionEvaluatingRoutingSlipRouteStrategy.getNextPath()</code> invocation. It is a simple
Java Bean with two properties - <code>Message&lt;?&gt; request</code> and <code>Object reply</code>.
With this expression implementation, we can specify Routing Slip <code>path</code> entries using SpEL
(<code>@routingSlipRoutingPojo.get(request, reply)</code>, <code>request.headers[myRoutingSlipChannel]</code>)
avoiding a bean definition for the <interfacename>RoutingSlipRouteStrategy</interfacename>.
</para>
<important>
<para>
If a <emphasis>Routing Slip</emphasis> is involved in a distributed environment - cross-JVM
application, <code>request-reply</code> through a Message Broker (e.g. <xref linkend="amqp"/>,
<xref linkend="jms"/>), or persistence <interfacename>MessageStore</interfacename>
(<xref linkend="message-store"/>) is used in the integration flow, etc., - it is recommended
to <emphasis role="bold">not</emphasis>
use <emphasis>inline</emphasis> expressions for the Routing Slip <code>path</code>. The framework
(<classname>RoutingSlipHeaderValueMessageProcessor</classname>) converts them to
<classname>ExpressionEvaluatingRoutingSlipRouteStrategy</classname> objects and they are used in
the <code>routingSlip</code> message header. Since this class isn't
<interfacename>Serializable</interfacename> (and it can't be, because it depends on the
<interfacename>BeanFactory</interfacename>) the entire Message becomes non-serializable and in
any distributed operation we end up with <classname>NotSerializableException</classname>.
To overcome this limitation, register an
<classname>ExpressionEvaluatingRoutingSlipRouteStrategy</classname> bean with the desired SpEL and
use its bean name in the Routing Slip <code>path</code> configuration.
</para>
</important>
<para>
For Java configuration, simply add a
<classname>RoutingSlipHeaderValueMessageProcessor</classname> instance to the
<classname>HeaderEnricher</classname> bean definition:
</para>
<programlisting language="java"><![CDATA[@Bean
@Transformer(inputChannel = "routingSlipHeaderChannel")
public HeaderEnricher headerEnricher() {
return new HeaderEnricher(Collections.singletonMap(IntegrationMessageHeaderAccessor.ROUTING_SLIP,
new RoutingSlipHeaderValueMessageProcessor("myRoutePath1",
"@routingSlipRoutingPojo.get(request, reply)",
"routingSlipRoutingStrategy",
"request.headers[myRoutingSlipChannel]",
"finishChannel")));
}]]></programlisting>
<para>
The <emphasis>Routing Slip</emphasis> algorithm works as follows when an endpoint produces
a reply and there is no <code>outputChannel</code> defined:
</para>
<itemizedlist>
<listitem>
<para>
The <code>routingSlipIndex</code> is used to get a value from the Routing Slip
<code>path</code> list.
</para>
</listitem>
<listitem>
<para>
If the value by <code>routingSlipIndex</code> is <classname>String</classname>, it
is used to get a bean from <interfacename>BeanFactory</interfacename>.
</para>
</listitem>
<listitem>
<para>
If a returned bean is an instance of <interfacename>MessageChannel</interfacename>, it is used
as the next <code>outputChannel</code> and the <code>routingSlipIndex</code> is incremented
in the reply message header (the Routing Slip <code>path</code> entries remain unchanged).
</para>
</listitem>
<listitem>
<para>
If a returned bean is an instance of <interfacename>RoutingSlipRouteStrategy</interfacename> and its
<code>getNextPath</code> doesn't return an empty String, that result is used a bean name for
the next <code>outputChannel</code>. The <code>routingSlipIndex</code> remains unchanged.
</para>
</listitem>
<listitem>
<para>
If <code>RoutingSlipRouteStrategy.getNextPath</code> returns an empty String,
the <code>routingSlipIndex</code> is incremented and the
<code>getOutputChannelFromRoutingSlip</code> is invoked recursively for the next Routing Slip
<code>path</code> item;
</para>
</listitem>
<listitem>
<para>
If the next Routing Slip <code>path</code> entry isn't a String it must be an instance of
<interfacename>RoutingSlipRouteStrategy</interfacename>;
</para>
</listitem>
<listitem>
<para>
When the <code>routingSlipIndex</code> exceeds the size of the Routing Slip <code>path</code>
list, the algorithm moves to the default behavior for the standard <code>replyChannel</code>
header.
</para>
</listitem>
</itemizedlist>
</section>
</section>
</section>