Merging unrelated Subversion branches using Subclipse

So you ended up with 2 feature branches that were forked off from the trunk at different points in time. And you want to merge some changes from one branch to the other. No problemo. Subclipse supports selective merging of revisions. Let’s say you want to merge some changes from feature_branch101 into feature_branch100. Here’s how you should go about it

  1. Just as with any merge, make sure your working copy has been switched to the branch you want to update. So in our case, the local working copy should be feature_branch101. You can read more on this in my previous Subversion merge post.
  2. Make sure there are no outgoing (i.e., not checked in) changes in your local working copy.
  3. Right-click your project in eclipse and choose Team -> Merge…
  4. In the wonderfully misnamed ‘Merge input’ section choose ‘Merge a range of revisions’. Uncheck ‘Perform pre-merge best practices checks’. Here’s a capture to illustrate better
  5. Click Next. In this screen we have to choose the merge source ‘from’ which we want to grab the changes to merge into our local working copy. This is feature_branch101 in our case. Here’s another capture
  6. Make sure ‘Select revisions on next page’ is selected to allow hand-picking of the revisions we want
  7. Click Next. In the ‘Select the revisions’ dialog, you can select the revisions you want.
  8. Click Next. This takes you to the merge options. Just leave things the way they are. Click ‘Finish’.
  9. You can check the SVN console for the progress of the merge. Here you should find the merge command and the list of changes happening. Something like below
  10. merge -c 5064 -c 5073 -r 5086:5088  W:/
        --- Merging r5063 through r5064 into W:/
    	...
    	...
    	Merge complete.
        ===== File Statistics: =====
        Merged: 1
        Added: 6
        Updated: 17
    
  11. Subclipse will popup the merge statistics once the merge is complete. You can now synchronize your changes with the feature_branch100 in the svn repository. Right click your project, Team -> Synchronize

Post it – Combining property placeholder replacement and SpEL

Ever wanted to negate a boolean placeholder property value before its set for one of your bean’s properties? We can do this with SpEL support

<bean class="com.oh.my.DingDongRepeater">
	<property name="enabled" value="#{!${dingDongDisabled}}"/>
</bean>

The #{!} negates the ${dingDongDisabled}. You might be curious why one needs to do this rather than renaming the property the other way around. Well, let’s just say that i get stubborn/adamant/picky every once in a while.

Merging with Subclipse (and Subversion)

If you have been stumped by the “Trying to use an unsupported feature svn: Retrieval of mergeinfo unsupported by” error messages, you are not alone. I think this happens when the subclipse version (and features) is different from the server repository version (and capabilities). But i am just not sure. [Edit: Mark's comment below explains why. Thanks Mark!] So here’s how to do a merge in this case.

  1. Make sure the working copy reflects the target of the merge. If you want to merge your feature branch to trunk (so that the trunk gets up-to-date with the feature), then the working copy (the current project in eclipse) should be trunk. If you are not in trunk, then switch your project’s working copy using context menu Team -> Switch… Ideally changes should be checked in before switching.
  2. Wait for switch to complete. The SVN console will display the list of files that get added, deleted or updated by subversion client to make the working copy reflect the new version it is switching to.
  3. Now do Team -> Merge
  4. In there choose ‘Merge two different trees’ as the merge input (what’s with these cryptic names). This allows a fully configurable merge.
  5. Uncheck ‘Perform pre-merge best practice checks’. But make sure there are no unchecked changes in the working copy.
  6. In the next window specify the ‘from’ and ‘to’ urls for the merge. This I have found to be quite confusing the first time. It helps to take an analogy. If one wants to go from Bangalore to Chicago then the  ‘from’ location is Bangalore and the ‘to’ location is Chicago. Now let’s translate that to repository versions. If we want the working copy to go ‘from’ trunk ‘to’ the feature branch state, then the ‘from’ has to be the trunk and the  ‘to’ has to be the feature branch URL. If instead we are merging a branch101 back to branch100, then the working copy should be branch100,  the  ‘from’ should be branch100 and the ‘to’ should be branch101. Phew! That’s a lot of words.
  7. In the same window, specify the revisions. Typically one would choose HEAD revision for both the ‘from’ and the ‘to’.
  8. Click Next. In the conflict handling options window, just leave it to the defaults. If it doesn’t work with defaults, then enable ‘Ignore ancestry’ and ‘Allow unversioned obstructions’.
  9. Click Finish. That’s it. Easy peasy lemon squeezy. SVN console will log all that the SVN client does to update the working copy to the ‘to’ location and revision.
  10. Once its all done, the working copy can now be synchronized with the repository to merge/check-in the changes to the trunk. Trunk of course, stays safe, till you do this.

Displaying custom labels in Spark DataGrid when using ComboBoxGridItemEditor

The spark DataGrid component ships with a handy ComboBoxGridItemEditor. But all’s not well if you want to display data from something more serious than a string.

For ex, you might have a list of PropertyStatus instances that you want to display in the grid’s cell

public var statuses:ArrayList =
	new ArrayList([
	{name: "sold", desc: "Sold out"},
	{name: "avail", desc: "Still available"}
]);

Though the ComboBox supports this case, the ComboBoxGridItemEditor does not. So the following wouldn’t work.

<s:DataGrid dataProvider="{props}" width="400" editable="true">
	<s:columns>
		<s:ArrayList>
			<s:GridColumn dataField="name" editable="false"/>
			<s:GridColumn dataField="status">
				<s:itemEditor>
					<fx:Component>
						<s:ComboBoxGridItemEditor dataProvider="{outerDocument.statuses}"/>
					</fx:Component>
				</s:itemEditor>
			</s:GridColumn>
		</s:ArrayList>
	</s:columns>
</s:DataGrid>

So we would see something like below

But it’s pretty easy to enable this by extending the ComboBoxGridItemEditor as shown below. All we need to do is add a labelFunction attribute and pass it onto the ComboBox.

<?xml version="1.0" encoding="utf-8"?>
<s:ComboBoxGridItemEditor xmlns:fx="http://ns.adobe.com/mxml/2009"
						  xmlns:s="library://ns.adobe.com/flex/spark"
						  xmlns:mx="library://ns.adobe.com/flex/mx" width="400" height="300">
	<fx:Declarations>
		<!-- Place non-visual elements (e.g., services, value objects) here -->
	</fx:Declarations>
	<fx:Script>
		<![CDATA[
			import spark.components.gridClasses.GridItemRenderer;
			private var _labelFunction:Function;

			public function get labelFunction():Function {
				return _labelFunction;
			}

			public function set labelFunction(func:Function):void {
				_labelFunction = func;
				comboBox.labelFunction = _labelFunction;
			}
		]]>
	</fx:Script>
</s:ComboBoxGridItemEditor>

So now the following would work

<local:CustomizableComboBoxGridItemEditor
        dataProvider="{outerDocument.statuses}" labelFunction="{outerDocument.statusLabel}"/>

So we would see something like

But wait, is that enough? Good catch. Note that the DataGrid uses two UI component instances for each cell. One for the display mode and one for the edit mode. We fixed the edit mode. But the display mode would still display gibberish.

The simplest way to fix this is to use GridColumn.labelFunction.

	public function columnStatusLabel(data:Object, col:GridColumn):String {
				return data[col.dataField].desc;
			}
		]]>
	</fx:Script>
	<s:DataGrid dataProvider="{props}" width="400" editable="true">
		....
			<s:GridColumn dataField="status" labelFunction="{columnStatusLabel}">

So the grid would now look like

All these labelFunctions floating around would do us no good when we want to reuse them across dialogs. The best way in this case would be to have a PropertyStatus class and define the label functions there. This makes the functions available wherever we want them and leaves them in a place logical enough to find. Something like

public class PropertyStatus {
	public var name:String;
	public var desc: String;

        public static function statusLabel(status:Object):String {
		return status.desc;
	}
	public static function columnStatusLabel(data:Object, col:GridColumn):String {
		return data[col.dataField].desc;
	}
}

Throttling message processing with the Spring JMS MessageListenerContainer

In the previous post we saw how we could easily scale the parallel processing of messages by tweaking the concurrency attribute of the MessageListenerContainer abstraction. In this post, we will see how we can do the reverse, ie, throttle message processing.

What do we want?

Lets assume a requirement which dictates that we shouldn’t be processing more than 2 messages simultaneously on the server. That’s easy, we just need to set the concurrency attribute to 2. But to complicate things, lets assume we have another message listener container defined in the same context, wired to listen on a different queue. And we want to process a maximum of 2 messages simultaneously, across these 2 queues.

Also, if one of the queues is empty, we would naturally want 2 messages of the other queue be processed simultaneously so that the app doesn’t just sit twiddling its thumb.

How do we do it

The solution is quite simple. The MessageListenerContainer abstraction supports injection of a custom task executor. And if we inject the same task executor instance to the 2 MLC instances, we end up controlling both the MLCs from one common threadpool. Now whatever throttling we apply on the task executor will in turn dictate how the MLCs process messages.

So the configuration now looks like

<jms:listener-container connection-factory="connectionFactory"
	destination-resolver="serverDestinationResolver" message-converter="messageConverter"
	concurrency="3-3" task-executor="executor">
	<jms:listener id="someName" destination="trRequestQueue"
		ref="remoteExecutionEndPoint" method="receive" response-destination="trStatusQueue"/>
</jms:listener-container>

<task:executor id="executor" pool-size="2" queue-capacity="2"
	rejection-policy="ABORT"/>

All we did was define a task executor and inject it to the MLC(s). And voila, only 2 messages will be processed simultaneously from now. This works with the concurrency attribute, for ex, if the pool size is increased to 6, then both MLCs will start processing 3 messages a a time, because their concurrency attribute limits each of them to 3. And all we did was type one extra line of configuration XML !

Scaling with the Spring JMS MessageListenerContainer

This is in continuation to the previous post on Spring JMS MessageListenerContainer. Lets see how we can use the MessageListenerContainer to scale message consumption.

Here’s the basic message listener container configuration to start with.

        <jms:listener-container connection-factory="connectionFactory"
		destination-resolver="serverDestinationResolver" message-converter="messageConverter">
		<jms:listener id="someName" destination="trRequestQueue"
			ref="remoteExecutionEndPoint" method="runAround"
                        response-destination="trStatusQueue"/>
	</jms:listener-container>

Check out the appendix at the end for the surrounding configuration like serverDestinationResolver, messageConverter etc. I wouldn’t have needed the messageConverter if i had a symmetric interface that accepted a String and returned a String for ex.
The server-side listener can be a simple POJO. The implementation sleeps for 3 seconds to simulate work being done.

public interface Park {
	public String runAround(TextMessage message);
}
public class ParkImpl implements Park {
...
	public String runAround(TextMessage message) {
		try {
			logger.info("Received message: " + message.getText());
			Thread.sleep(3000);
			return "processed: " + message.getText();
		}
		catch (Exception e) {
			return "processed: junk";
		}
	}
}

Our client will use the Spring JMSTemplate to send 3 Hello messages as shown below

public void interact() throws Exception {
		for (int i = 1; i <= 3; i++) {
			final int iVal = i;
			clientTemplate.send(sendDestination,
				new MessageCreator() {
					public Message createMessage(
						Session session) throws JMSException {
					return session.
						createTextMessage("Hello " + iVal);
				}
			});
			logger.info("Sent");
		}
		for (int i = 1; i <= 3; i++) {
			TextMessage message = (TextMessage)
				clientTemplate.receive(replyDestination);
		}
	}

In our simple application, we are embedding both the client and the server in the same vm.

Let’s see the default behavior in the console on running this

23:07:10.062 [main] INFO  c.s.rexec.RemoteExecutionClient - Sent
23:07:10.093 [main] INFO  c.s.rexec.RemoteExecutionClient - Sent
23:07:10.171 [main] INFO  c.s.rexec.RemoteExecutionClient - Sent
23:07:10.031 [someName-1] INFO  c.s.r.RemoteExecutionEndPointImpl
	- Received message: Hello 1
23:07:13.078 [someName-1] INFO  c.s.r.RemoteExecutionEndPointImpl
	- Received message: Hello 2
23:07:16.109 [someName-1] INFO  c.s.r.RemoteExecutionEndPointImpl
	- Received message: Hello 3

We see that one message is processed every 3 seconds. This is the default settings at work. A JMS Session processes only one message a time so that the consumer need not be multi-threaded.

Now how do we scale? Simple. The message listener container has a concurrency attribute. Lets set it to 3 so that all 3 of our messages get processed simultaneously.

        <jms:listener-container connection-factory="connectionFactory"
		destination-resolver="serverDestinationResolver" message-converter="messageConverter">
		<jms:listener id="someName" destination="trRequestQueue"
			ref="remoteExecutionEndPoint" method="runAround"
                        response-destination="trStatusQueue" concurrency="3"/>
	</jms:listener-container>

Now lets observe the behavior on running the program

07:42:23.246 [main] INFO  c.n.rexec.RemoteExecutionClient - Sent
07:42:23.277 [main] INFO  c.n.rexec.RemoteExecutionClient - Sent
07:42:23.308 [main] INFO  c.n.rexec.RemoteExecutionClient - Sent
07:42:23.199 [someName-1] INFO  c.n.r.RemoteExecutionEndPointImpl
	- Received message: Hello 1
07:42:23.277 [someName-2] INFO  c.n.r.RemoteExecutionEndPointImpl
	- Received message: Hello 3
07:42:26.386 [someName-1] INFO  c.n.r.RemoteExecutionEndPointImpl
	- Received message: Hello 2

What we are seeing instead is that 2 messages are processed simultaneously, not 3 as we had configured. The third message is picked up at 07:42:26 while the first 2 at 07:42:23. So what’s going on?

The MessageListenerContainer documentation does not mention anything specially about the concurrency attribute. But the DefaultMessageListenerContainer API tells us that there are 2 attributes related to concurrency, concurrentConsumers and maxConcurrentConsumers. So it could be that the default max is 2. So lets try upping the max. Here’s our new configuration.

        <jms:listener-container connection-factory="connectionFactory"
		destination-resolver="serverDestinationResolver" message-converter="messageConverter">
		<jms:listener id="someName" destination="trRequestQueue"
			ref="remoteExecutionEndPoint" method="runAround"
                        response-destination="trStatusQueue" concurrency="3-3"/>
	</jms:listener-container>

The only change (yet again) is the concurrency which is now set to 3-3. This is typically the way we set a range in Spring configuration. Lets observe the behavior again

07:51:19.933 [main] INFO  c.n.rexec.RemoteExecutionClient - Sent
07:51:19.980 [main] INFO  c.n.rexec.RemoteExecutionClient - Sent
07:51:19.996 [main] INFO  c.n.rexec.RemoteExecutionClient - Sent
07:51:19.886 [someName-1] INFO  c.n.r.RemoteExecutionEndPointImpl
	- Received message: Hello 1
07:51:19.949 [someName-3] INFO  c.n.r.RemoteExecutionEndPointImpl
	- Received message: Hello 2
07:51:19.980 [someName-2] INFO  c.n.r.RemoteExecutionEndPointImpl
	- Received message: Hello 3

Good, that works, all 3 messages have been handled simultaneously. And its working because the MessageListenerContainer is creating extra sessions as per the concurrency attribute. As mentioned in the documentation, these attributes are modifiable at runtime via JMX, thereby allowing us to scale even more dynamically, staying truly J2EE container independent all the while.

This is already quite a long post, so i will discuss the throttling part in the next post.

Appendix A: ActiveMQ configuration

Active MQ configuration to configure the broker, queues. One queue to submit requests to the server and another to get response from server.

        <amq:broker useJmx="false" id="amqServer">
		<amq:transportConnectors>
			<amq:transportConnector uri="${mqBrokerURL}"/>
		</amq:transportConnectors>
	</amq:broker>

	<!-- Predefined queues, we don't need dynamic queues -->
	<amq:queue id="trRequestQueue" name="sendDestination"
		physicalName="queue.trRequest"/>
	<amq:queue id="trStatusQueue" physicalName="queue.trResponse"/>

	<!-- J2EE ConnectionFactory -->
	<amq:connectionFactory id="targetConnectionFactory" brokerURL="vm://localhost"/>

	<!-- Cache the connection -->
	<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
		<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
		<property name="reconnectOnException" value="true"/>
	</bean>

Appendix B: Listener configuration

	<jms:listener-container connection-factory="connectionFactory"
		destination-resolver="serverDestinationResolver" message-converter="messageConverter">
		<jms:listener id="someName" destination="trRequestQueue"
			ref="remoteExecutionEndPoint" method="receive" response-destination="trStatusQueue"/>
	</jms:listener-container>
	<bean id="remoteExecutionEndPoint"
		class="com.sabscape.rexec.ParkImpl"/>
	<bean id="serverDestinationResolver"
		class="org.springframework.jms.support.destination.BeanFactoryDestinationResolver"/>
	<bean id="messageConverter"
		class="com.sabscape.rexec.PassthroughMessageConverter"/>

Why would you want to use a Spring JMS MessageListenerContainer

Flexibility. Here’s a few stuff i noticed

  • Scale message consumption easily. We can specify the concurrency level we want and it will create additional sessions (because JMS, by design, disallows concurrent use of a Session) as required. This is modifiable at run-time as well.
  • Allows our POJO message handlers a.k.a listeners to be JMS agnostic (actually integration channel agnostic). So i can have a business-driven method name like runAroundThePark() instead of something that means nothing like onMessage. Of course its not a big deal to dispatch to a business centric method from onMessage, still, why should another level of redirection be forced upon us just because our logic is being invoked through a JMS channel
  • Our POJO message handling class can send a response message, again while being JMS agnostic
  • Easily throttle across queues (and/or selector-based-listeners)
    • The scenario is something like this. Assume that you are handling messages that are requesting some jobs to be run and these jobs involve some heavy processing. And you have been asked to ensure that not more than 4 jobs are run at a time
    • And there are multiple queues that accept a variety of jobs
    • Now you want to make sure that (across the whole broker) not more than 4 jobs are running at a time

How we can scale and how we can throttle across the application’s queues is going to be the content of the next post.

Spring Integration – Channel mysteries – Part 4 – Final part

In the previous part we observed how the rejection policy of the thread pool executor played an important role. To summarize, the CallerRuns policy automatically throttles the producer from overloading the consumer. Now lets try a variation and observe what happens.

Process files in parallel, with limited executor queue capacity set to abort on rejection

Here’s the new configuration

<file:inbound-channel-adapter directory="file:${mediaInputDirectory}"
	prevent-duplicates="true" channel="mediaIn01"
	filename-pattern="*media*">

	<integration:poller max-messages-per-poll="5" cron="*/5 * * * * *"/>
</file:inbound-channel-adapter>

<integration:channel id="mediaIn01">
	<integration:dispatcher task-executor="fileChannelTaskExecutor"/>
</integration:channel>

<task:executor id="fileChannelTaskExecutor"
	pool-size="${numParallelExecutions}" queue-capacity="2"
	rejection-policy="ABORT"/>

<integration:service-activator input-channel="mediaIn01"
	ref="integrationJobLauncher"/>

The only difference from what we had in the previous part is the task:executor. Note that this declaration results in a JDK 5.0 ThreadPoolTaskExecutor, the one we used before was the Spring ThreadPoolTaskExecutor. And we have set the rejection-policy to ABORT. So the thread pool should complain when its queue is full (the capacity of the queue is still 2). I also modified the IntegrationBatchJobLauncher to sleep for 20 seconds instead of the 5 seconds before.

What’s the behavior?

With these changes (and 8 files in the mail-box directory), here’s the behavior we see

15:08:16.140 [main] INFO  c.n.ar.integration.launch.Launcher - Launched spring context
15:08:20.171 [task-scheduler-1] ERROR o.s.i.handler.LoggingHandler
- org.springframework.integration.MessageDeliveryException: failed to send Message to channel 'mediaIn01'
15:08:20.171 [fileChannelTaskExecutor-2] INFO  c.n.a.i.l.IntegrationBatchJobLauncher
- Launching job for file: c:\mytemp\media\Copy (3) of media.csv
15:08:20.171 [fileChannelTaskExecutor-1] INFO  c.n.a.i.l.IntegrationBatchJobLauncher
- Launching job for file: c:\mytemp\media\Copy (2) of media.csv
15:08:25.000 [task-scheduler-2] ERROR o.s.i.handler.LoggingHandler
- org.springframework.integration.MessageDeliveryException: failed to send Message to channel 'mediaIn01'
15:08:30.000 [task-scheduler-1] ERROR o.s.i.handler.LoggingHandler
- org.springframework.integration.MessageDeliveryException: failed to send Message to channel 'mediaIn01'
15:08:35.000 [task-scheduler-3] ERROR o.s.i.handler.LoggingHandler
- org.springframework.integration.MessageDeliveryException: failed to send Message to channel 'mediaIn01'
15:08:40.265 [fileChannelTaskExecutor-2] INFO  c.n.a.i.l.IntegrationBatchJobLauncher
- Launching job for file: c:\mytemp\media\Copy (4) of media.csv
15:08:40.265 [fileChannelTaskExecutor-1] INFO  c.n.a.i.l.IntegrationBatchJobLauncher
- Launching job for file: c:\mytemp\media\Copy (5) of media.csv

We see that message handling failed at 15:08:20.171, 15:08:25.000, 15:08:30.000, 15:08:35.000. So finally we are seeing the behavior we expected. But it still needs some explanation.

Why does it work this way?

Here’s what happened

  • The file poller is set to grab 5 files. So there is going to be 5 mediaIn01.send() calls
  • 2 of those can succeed because executor queue capacity is 2 and num pooled threads is 2
  • So 3rd mediaIn01.send() should have failed, that’s what we see for 15:08:20.171 timestamp
  • After first failure, the poller thread shuts up.
  • These 2 pool threads won’t be free till 15:08:40
  • So now the failure count is 1 and success count is 2
  • Because the poller is configured to check for files every 5 seconds, it wakes up again at 15:08:25.000 and does another mediaIn01.send()
  • But that fails as we see in the logs. So it shuts up again
  • So now the failure count is 2 and success count is 2
  • The same thing happens again at 15:08:30.000, 15:08:35.000 taking the unprocessed count to 4.
  • Now at 15:08:40.000, the two pool threads are relieved of their work. So they become available again.
  • And so the medianIn01.send() for the remaining files, ie 2, will succeed. And thats the last 2 logs.
  • So final failure count is 4 and success count is 4

This wraps up this series in which we looked at different ways of configuring channels and the behavior that results. Though the configuration xml looks quite simple, the behind-the-scenes stuff is not so simple. And without understanding this, troubleshooting for discarded files etc would be a tough job.

Customizing Spring Batch to process zipped files

Had an issue in production today wherein we ran out of disk space processing a 60 GB input file. The uncompressed size of the input file was 60 gigs and due to some reasons there wasn’t enough space in the disk. But this got one of my colleagues thinking on how to efficiently handle such cases. His idea was cool, use gzcat to read directly from the gzip file, instead of un-compressing the whole file at once. Cool idea, though it had to be retrofitted to a java based tech stack. Added to that our process was built on top of Spring Batch

The solution was simple. java.util.zip.GZipInputStream allows us to read from a gzip file just like gzcat does. Cool. And now to make Spring Batch work with this input stream, I pointed the browser to the API docs for FlatFileItemReader. As expected, they had a factory class for plugging in our custom BufferedReader. One of the reasons i love to use the Spring framework is because their designs encourage us to think and design the right way as well. Here the class author is basically encapsulating what could vary (the source stream for the reader could vary). So here’s my factory implementation that gets plugged into our reader beans

public class GZipBufferedReaderFactory
	implements BufferedReaderFactory {

	private boolean autoDetect = false;
	public BufferedReader create(Resource resource, String encoding)
			throws UnsupportedEncodingException, IOException {
		InputStream sourceStream = null;
		if ((!isAutoDetect()) ||
			(StringUtils.isNotEmpty(resource.getFilename())
				&& resource.getFilename().endsWith(".gz"))) {
			sourceStream =
				new GZIPInputStream(resource.getInputStream());
		}
		else {
			sourceStream = resource.getInputStream();
		}
		return new
			BufferedReader(new InputStreamReader(sourceStream, encoding));
	}

	public boolean isAutoDetect() {
		return autoDetect;
	}
	public void setAutoDetect(boolean autoDetect) {
		this.autoDetect = autoDetect;
	}

}

I added an autoDetect configuration setting as well so that i wouldn’t apply the GZipInputStream decorator unless the target file is a .gz file.

Coupled with some spring property magic

<bean id="gzipReaderFactory"
	class="com.sabscape.GZipBufferedReaderFactory">
	<property name="autoDetect" value="${autoDetectGzipStreams}"/>
</bean>

its ready to be injected wherever we need support to read zip files (i actually have a parent bean with this buffered reader factory injected in it).

No need to extend their reader and dig into their code to see if my changes gel well with the super class. Nice.

Post it – Flexible content based routing using SpEL expressions (Spring Integration)

expression (using SpEL) can be used to eliminate the need for a custom router (almost always).

A couple examples in the spring reference manual

  • Use to generate the channel name
  • Use to make a recipient list router

But there’s another gem which is not mentioned explicitly in doc.

<integration:router input-channel="channel03" resolution-required="true" expression="payload.statusCode">
	<integration:mapping value="0" channel="channel04"/>
	<integration:mapping value="1" channel="channel05"/>
</integration:router>

You can use expression to select a payload attribute, which will then be matched against your mappings to decide the output channel to use. So its very much like a header-value-router, just that the value is coming from the payload. So no need for a custom router just because your routing logic depends on the payload attributes.

In the above snippet, payload.statusCode value is used to determine the output channel.