From 84421fd91cedf18f0f7d929ee988f1d299427273 Mon Sep 17 00:00:00 2001 From: David Liu Date: Tue, 19 Aug 2014 11:28:16 +0300 Subject: [PATCH] INT-2856: Add Management for RecipientListRouter JIRA: https://jira.spring.io/browse/INT-2856 INT-2856:Add support for adding/removing individual recipients to the RecipientListRouter INT-2856: allow recipient channel null on init INT-2856: Polishing --- .../config/xml/RecipientListRouterParser.java | 15 +- .../filter/ExpressionEvaluatingSelector.java | 23 ++- .../router/RecipientListRouter.java | 140 +++++++++++++-- .../router/RecipientListRouterManagement.java | 88 ++++++++++ .../config/xml/spring-integration-4.1.xsd | 87 ++++----- ...rolBusRecipientListRouterTests-context.xml | 45 +++++ .../ControlBusRecipientListRouterTests.java | 166 ++++++++++++++++++ .../router/RecipientListRouterTests.java | 16 +- src/reference/docbook/router.xml | 36 ++++ src/reference/docbook/whats-new.xml | 10 ++ 10 files changed, 548 insertions(+), 78 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouterManagement.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusRecipientListRouterTests-context.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusRecipientListRouterTests.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/RecipientListRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/RecipientListRouterParser.java index f4aedd4bd4..ceb2b96aa6 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/RecipientListRouterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/RecipientListRouterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 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. @@ -28,7 +28,6 @@ import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.filter.ExpressionEvaluatingSelector; import org.springframework.integration.router.RecipientListRouter; import org.springframework.integration.router.RecipientListRouter.Recipient; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; @@ -43,13 +42,11 @@ import org.springframework.util.xml.DomUtils; public class RecipientListRouterParser extends AbstractRouterParser { @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) protected BeanDefinition doParseRouter(Element element, ParserContext parserContext) { - BeanDefinitionBuilder recipientListRouterBuilder = BeanDefinitionBuilder.genericBeanDefinition(RecipientListRouter.class); + BeanDefinitionBuilder recipientListRouterBuilder = + BeanDefinitionBuilder.genericBeanDefinition(RecipientListRouter.class); List childElements = DomUtils.getChildElementsByTagName(element, "recipient"); - Assert.notEmpty(childElements, - "At least one recipient channel must be defined (e.g., )."); - ManagedList recipientList = new ManagedList(); + ManagedList recipientList = new ManagedList(); for (Element childElement : childElements) { BeanDefinitionBuilder recipientBuilder = BeanDefinitionBuilder.genericBeanDefinition(Recipient.class); recipientBuilder.addConstructorArgReference(childElement.getAttribute("channel")); @@ -62,7 +59,9 @@ public class RecipientListRouterParser extends AbstractRouterParser { } recipientList.add(recipientBuilder.getBeanDefinition()); } - recipientListRouterBuilder.addPropertyValue("recipients", recipientList); + if(recipientList.size() > 0) { + recipientListRouterBuilder.addPropertyValue("recipients", recipientList); + } return recipientListRouterBuilder.getBeanDefinition(); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/filter/ExpressionEvaluatingSelector.java b/spring-integration-core/src/main/java/org/springframework/integration/filter/ExpressionEvaluatingSelector.java index e1ab8a6a08..0bc3a006cb 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/filter/ExpressionEvaluatingSelector.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/filter/ExpressionEvaluatingSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2014 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. @@ -26,21 +26,36 @@ import org.springframework.integration.handler.ExpressionEvaluatingMessageProces /** * A {@link MessageSelector} implementation that evaluates a SpEL expression. * The evaluation result of the expression must be a boolean value. - * + * * @author Mark Fisher + * @author Liujiong * @since 2.0 */ public class ExpressionEvaluatingSelector extends AbstractMessageProcessingSelector { - private static final ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + private static final ExpressionParser expressionParser = + new SpelExpressionParser(new SpelParserConfiguration(true, true)); + private final String expressionString; public ExpressionEvaluatingSelector(String expressionString) { - super(new ExpressionEvaluatingMessageProcessor(expressionParser.parseExpression(expressionString), Boolean.class)); + super(new ExpressionEvaluatingMessageProcessor(expressionParser.parseExpression(expressionString), + Boolean.class)); + this.expressionString = expressionString; } public ExpressionEvaluatingSelector(Expression expression) { super(new ExpressionEvaluatingMessageProcessor(expression, Boolean.class)); + this.expressionString = expression.getExpressionString(); + } + + public String getExpressionString() { + return expressionString; + } + + @Override + public String toString() { + return "ExpressionEvaluatingSelector for: [" + this.expressionString + "]"; } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java index b7ad5178b5..9b40706a0c 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java @@ -18,13 +18,24 @@ package org.springframework.integration.router; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; import org.springframework.beans.factory.InitializingBean; import org.springframework.integration.core.MessageSelector; +import org.springframework.integration.filter.ExpressionEvaluatingSelector; +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** *
@@ -55,11 +66,12 @@ import org.springframework.util.Assert;
  * @author Mark Fisher
  * @author Oleg Zhurakousky
  * @author Artem Bilan
+ * @author Liujiong
  */
-public class RecipientListRouter extends AbstractMessageRouter implements InitializingBean {
-
-	private volatile List recipients;
+public class RecipientListRouter extends AbstractMessageRouter
+		implements InitializingBean, RecipientListRouterManagement {
 
+	private final ConcurrentLinkedQueue recipients = new ConcurrentLinkedQueue();
 
 	/**
 	 * Set the channels for this router. Either call this method or
@@ -82,7 +94,36 @@ public class RecipientListRouter extends AbstractMessageRouter implements Initia
 	 */
 	public void setRecipients(List recipients) {
 		Assert.notEmpty(recipients, "recipients must not be empty");
-		this.recipients = recipients;
+		ConcurrentLinkedQueue originalRecipients = this.recipients;
+		this.recipients.clear();
+		this.recipients.addAll(recipients);
+		if (logger.isDebugEnabled()) {
+			logger.debug("Channel Recipients:" + originalRecipients + " replaced with:" + this.recipients);
+		}
+	}
+
+	/**
+	 * Set the recipients for this router.
+	 * @param recipientMappings, map contains channelName and expression
+	 */
+	@Override
+	@ManagedAttribute
+	public void setRecipientMappings(Map recipientMappings) {
+		Assert.notEmpty(recipientMappings, "recipientMappings must not be empty");
+		Assert.noNullElements(recipientMappings.keySet().toArray(), "'recipientMappings' cannot have null keys.");
+		ConcurrentLinkedQueue originalRecipients = this.recipients;
+		this.recipients.clear();
+		for (Entry next : recipientMappings.entrySet()) {
+			if (StringUtils.hasText(next.getValue())) {
+				this.addRecipient(next.getKey(), next.getValue());
+			}
+			else {
+				this.addRecipient(next.getKey());
+			}
+		}
+		if (logger.isDebugEnabled()) {
+			logger.debug("Channel Recipients:" + originalRecipients + " replaced with:" + this.recipients);
+		}
 	}
 
 	@Override
@@ -90,17 +131,11 @@ public class RecipientListRouter extends AbstractMessageRouter implements Initia
 		return "recipient-list-router";
 	}
 
-	@Override
-	public void onInit() throws Exception {
-		Assert.notEmpty(this.recipients, "recipient list must not be empty");
-		super.onInit();
-	}
 
 	@Override
 	protected Collection determineTargetChannels(Message message) {
 		List channels = new ArrayList();
-		List recipientList = this.recipients;
-		for (Recipient recipient : recipientList) {
+		for (Recipient recipient : this.recipients) {
 			if (recipient.accept(message)) {
 				channels.add(recipient.getChannel());
 			}
@@ -108,6 +143,85 @@ public class RecipientListRouter extends AbstractMessageRouter implements Initia
 		return channels;
 	}
 
+	@Override
+	@ManagedOperation
+	public void addRecipient(String channelName, String selectorExpression) {
+		Assert.hasText(channelName, "'channelName' must not be empty.");
+		Assert.hasText(selectorExpression, "'selectorExpression' must not be empty.");
+		MessageChannel channel = this.getBeanFactory().getBean(channelName, MessageChannel.class);
+		ExpressionEvaluatingSelector expressionEvaluatingSelector = new ExpressionEvaluatingSelector(selectorExpression);
+		expressionEvaluatingSelector.setBeanFactory(this.getBeanFactory());
+		this.recipients.add(new Recipient(channel, expressionEvaluatingSelector));
+	}
+
+	@Override
+	@ManagedOperation
+	public void addRecipient(String channelName) {
+		Assert.hasText(channelName, "'channelName' must not be empty.");
+		MessageChannel channel = this.getBeanFactory().getBean(channelName, MessageChannel.class);
+		this.recipients.add(new Recipient(channel));
+	}
+
+	@Override
+	@ManagedOperation
+	public int removeRecipient(String channelName) {
+		int counter = 0;
+		MessageChannel channel = this.getBeanFactory().getBean(channelName, MessageChannel.class);
+		for (Iterator it = this.recipients.iterator(); it.hasNext(); ) {
+			if (it.next().getChannel() == channel) {
+				it.remove();
+				counter++;
+			}
+		}
+		return counter;
+	}
+
+	@Override
+	@ManagedOperation
+	public int removeRecipient(String channelName, String selectorExpression) {
+		int counter = 0;
+		MessageChannel targetChannel = this.getBeanFactory().getBean(channelName, MessageChannel.class);
+		for (Iterator it = this.recipients.iterator(); it.hasNext(); ) {
+			Recipient next = it.next();
+			MessageSelector selector = next.getSelector();
+			MessageChannel channel = next.getChannel();
+			if (selector instanceof ExpressionEvaluatingSelector &&
+					channel == targetChannel &&
+					((ExpressionEvaluatingSelector) selector).getExpressionString().equals(selectorExpression)) {
+				it.remove();
+				counter++;
+			}
+		}
+		return counter;
+	}
+
+	@Override
+	@ManagedAttribute
+	public Collection getRecipients() {
+		return Collections.unmodifiableCollection(this.recipients);
+	}
+
+	@Override
+	@ManagedOperation
+	public void replaceRecipients(Properties recipientMappings) {
+		Assert.notEmpty(recipientMappings, "'recipientMappings' must not be empty");
+		Set keys = recipientMappings.stringPropertyNames();
+		ConcurrentLinkedQueue originalRecipients = this.recipients;
+		this.recipients.clear();
+		for (String key : keys) {
+			Assert.notNull(key, "channelName can't be null.");
+			if (StringUtils.hasText(recipientMappings.getProperty(key))) {
+				this.addRecipient(key, recipientMappings.getProperty(key));
+			}
+			else {
+				this.addRecipient(key);
+			}
+		}
+		if (logger.isDebugEnabled()) {
+			logger.debug("Channel Recipients:" + originalRecipients	+ " replaced with:" + this.recipients);
+		}
+	}
+
 
 	public static class Recipient {
 
@@ -125,6 +239,9 @@ public class RecipientListRouter extends AbstractMessageRouter implements Initia
 			this.selector = selector;
 		}
 
+		private MessageSelector getSelector() {
+			return selector;
+		}
 
 		public MessageChannel getChannel() {
 			return this.channel;
@@ -133,6 +250,7 @@ public class RecipientListRouter extends AbstractMessageRouter implements Initia
 		public boolean accept(Message message) {
 			return (this.selector == null || this.selector.accept(message));
 		}
+
 	}
 
 }
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouterManagement.java b/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouterManagement.java
new file mode 100644
index 0000000000..d227c07a1a
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouterManagement.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2014 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
+ *
+ *      http://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.router;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Properties;
+
+import org.springframework.integration.router.RecipientListRouter.Recipient;
+import org.springframework.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedOperation;
+import org.springframework.jmx.export.annotation.ManagedResource;
+
+/**
+ * Exposes adding/removing individual recipients operations for
+ * RecipientListRouter. This can be used with a control-bus and JMX.
+ *
+ * @author Liujiong
+ * @since 4.1
+ *
+ */
+@ManagedResource
+public interface RecipientListRouterManagement {
+
+	/**
+	 * Add a recipient with channelName and expression.
+	 * @param channelName The channel name.
+	 * @param selectorExpression The expression to filter the incoming message.
+	 */
+	@ManagedOperation
+	void addRecipient(String channelName, String selectorExpression);
+
+	/**
+	 * Add a recipient with channelName.
+	 * @param channelName The channel name.
+	 */
+	@ManagedOperation
+	void addRecipient(String channelName);
+
+	/**
+	 * Remove all recipients that match the channelName.
+	 * @param channelName The channel name.
+	 */
+	@ManagedOperation
+	int removeRecipient(String channelName);
+
+	/**
+	 * Remove all recipients that match the channelName and expression.
+	 * @param channelName The channel name.
+	 * @param selectorExpression The expression to filter the incoming message
+	 */
+	@ManagedOperation
+	int removeRecipient(String channelName, String selectorExpression);
+
+	/**
+	 * @return an unmodifiable collection of recipients.
+	 */
+	@ManagedAttribute
+	Collection getRecipients();
+
+	/**
+	 * Replace recipient.
+	 * @param recipientMappings contain channelName and expression.
+	 */
+	@ManagedOperation
+	void replaceRecipients(Properties recipientMappings);
+
+	/**
+	 * Set recipients.
+	 * @param recipientMappings contain channelName and expression.
+	 */
+	@ManagedAttribute
+	void setRecipientMappings(Map recipientMappings);
+
+}
diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.1.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.1.xsd
index d27d5a10dc..918e762f64 100644
--- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.1.xsd
+++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.1.xsd
@@ -1685,7 +1685,15 @@
 					
 					
 					
-					
+					
+						
+							
+								
+									
+								
+							
+						
+					
 					
 					
 					
@@ -2835,7 +2843,7 @@
         
             
                 
-                    
+                    
                     
                 
                 
@@ -2943,61 +2951,40 @@
 
     
 
-    
-        
-            
-            
-        
-    
+	
+		
+			
+				
+					
+						An expression to be evaluated to determine if this recipient
+						should be included in the recipient list for a given input
+						Message. The evaluation result of the expression must be a boolean.
+						If this attribute is not defined, the channel will always be
+						among the list of recipients.
+					
+				
+			
+		
+	
 
-    
-        
-            
-                
-                    
-                    
-						
-						    
-						        An expression to be evaluated to determine if this recipient
-						        should be included in the recipient list for a given input
-						        Message. The evaluation result of the expression must be a boolean.
-						        If this attribute is not defined, the channel will always be
-						        among the list of recipients.
-						    
-						
-                    
-                
-                
-            
-        
-    
+	
+		
+			
+				
+					
+					
+				
+				
+			
+		
+	
 
-    
-        
-            
-                
-                    
-                        
-						    
-						        An expression to be evaluated to determine if this recipient
-						        should be included in the recipient list for a given input
-						        Message. The evaluation result of the expression must be a boolean.
-						        If this attribute is not defined, the channel will always be
-						        among the list of recipients.
-						    
-						
-                    
-                
-            
-        
-    
 
     
 
     
         
-            
-            
+            
         
     
 
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusRecipientListRouterTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusRecipientListRouterTests-context.xml
new file mode 100644
index 0000000000..cdcbff6ddd
--- /dev/null
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusRecipientListRouterTests-context.xml
@@ -0,0 +1,45 @@
+
+
+
+	
+		
+	
+
+	
+
+	
+
+	
+		
+	
+
+	
+		
+	
+
+	
+		
+	
+
+	
+		
+	
+
+	
+		
+	
+
+	
+		
+	
+
+	
+		
+	
+
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusRecipientListRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusRecipientListRouterTests.java
new file mode 100644
index 0000000000..bdf9dd2008
--- /dev/null
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusRecipientListRouterTests.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2002-2014 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
+ *
+ *      http://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.config.xml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.integration.core.MessagingTemplate;
+import org.springframework.integration.router.RecipientListRouter.Recipient;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.PollableChannel;
+import org.springframework.messaging.support.GenericMessage;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+/**
+ * @author Liujiong
+ * @since 4.1
+ */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
+public class ControlBusRecipientListRouterTests {
+
+	@Autowired
+	private MessageChannel input;
+
+	@Autowired
+	private PollableChannel output;
+
+	@Autowired
+	private ConfigurableApplicationContext context;
+
+	@Autowired
+	@Qualifier("routingChannelA")
+	private MessageChannel channel;
+
+	@Before
+	public  void aa(){
+		context.start();
+	}
+
+	@Test
+	public void testAddRecipient() {
+		MessagingTemplate messagingTemplate = new MessagingTemplate();
+		messagingTemplate.setReceiveTimeout(1000);
+		messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel2','true')");
+		Message  message = new GenericMessage(1);
+		channel.send(message);
+		PollableChannel chanel2 = (PollableChannel) context.getBean("channel2");
+		assertTrue(chanel2.receive(0).getPayload().equals(1));
+	}
+
+	@Test
+	public void testAddRecipientWithNullExpression() {
+		MessagingTemplate messagingTemplate = new MessagingTemplate();
+		messagingTemplate.setReceiveTimeout(1000);
+		messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel3')");
+
+		Message  message = new GenericMessage(1);
+		channel.send(message);
+		PollableChannel chanel3 = (PollableChannel) context.getBean("channel3");
+		assertTrue(chanel3.receive(0).getPayload().equals(1));
+	}
+
+	@Test
+	public void testRemoveRecipient() {
+		MessagingTemplate messagingTemplate = new MessagingTemplate();
+		messagingTemplate.setReceiveTimeout(1000);
+		messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel1')");
+		messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel4')");
+		messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.removeRecipient('channel4')");
+
+		Message  message = new GenericMessage(1);
+		channel.send(message);
+		PollableChannel chanel1 = (PollableChannel) context.getBean("channel1");
+		PollableChannel chanel4 = (PollableChannel) context.getBean("channel4");
+		assertTrue(chanel1.receive(0).getPayload().equals(1));
+		assertNull(chanel4.receive(0));
+	}
+
+	@Test
+	public void testRemoveRecipientWithExpression() {
+		MessagingTemplate messagingTemplate = new MessagingTemplate();
+		messagingTemplate.setReceiveTimeout(1000);
+		messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel1','true')");
+		messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel5','true')");
+		messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.removeRecipient('channel5','true')");
+
+		Message  message = new GenericMessage(1);
+		channel.send(message);
+		PollableChannel chanel1 = (PollableChannel) context.getBean("channel1");
+		PollableChannel chanel5 = (PollableChannel) context.getBean("channel5");
+		assertTrue(chanel1.receive(0).getPayload().equals(1));
+		assertNull(chanel5.receive(0));
+	}
+
+	@Test
+	@SuppressWarnings("unchecked")
+	public void testGetRecipients() {
+		MessagingTemplate messagingTemplate = new MessagingTemplate();
+		messagingTemplate.setReceiveTimeout(1000);
+		messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.addRecipient('channel1')");
+		messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.getRecipients()");
+		PollableChannel chanel1 = (PollableChannel) context.getBean("channel1");
+		Message result = this.output.receive(0);
+		Collection mappings = (Collection) result.getPayload();
+		assertEquals(context.getBean("channel1"), mappings.iterator().next().getChannel());
+	}
+
+	@Test
+	public void testSetRecipients() {
+		MessagingTemplate messagingTemplate = new MessagingTemplate();
+		messagingTemplate.setReceiveTimeout(1000);
+
+		Map map = new HashMap();map.put("channel6","true");
+		Message message = MessageBuilder.withPayload("@'simpleRouter.handler'.setRecipientMappings(headers.recipientMap)").setHeader("recipientMap", map).build();
+		this.input.send(message);
+		message = new GenericMessage(1);
+		channel.send(message);
+		PollableChannel chanel6 = (PollableChannel) context.getBean("channel6");
+		assertTrue(chanel6.receive(0).getPayload().equals(1));
+	}
+
+	@Test
+	public void testReplaceRecipients() {
+		MessagingTemplate messagingTemplate = new MessagingTemplate();
+		messagingTemplate.setReceiveTimeout(1000);
+
+		messagingTemplate.convertAndSend(input, "@'simpleRouter.handler'.replaceRecipients('channel7=true')");
+		Message  message = new GenericMessage(1);
+		channel.send(message);
+		PollableChannel chanel7 = (PollableChannel) context.getBean("channel7");
+		assertTrue(chanel7.receive(0).getPayload().equals(1));
+	}
+}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/RecipientListRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/RecipientListRouterTests.java
index 45a5250ab9..b354eac9aa 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/router/RecipientListRouterTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/router/RecipientListRouterTests.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.when;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
 
 import org.junit.Test;
 import org.mockito.Mockito;
@@ -62,11 +63,11 @@ public class RecipientListRouterTests {
 		router.setChannels(channels);
 		router.setBeanFactory(mock(BeanFactory.class));
 		router.afterPropertiesSet();
-		List recipients = (List)
+		ConcurrentLinkedQueue recipients = (ConcurrentLinkedQueue)
 				new DirectFieldAccessor(router).getPropertyValue("recipients");
 		assertEquals(2, recipients.size());
-		assertEquals(channel1, new DirectFieldAccessor(recipients.get(0)).getPropertyValue("channel"));
-		assertEquals(channel2, new DirectFieldAccessor(recipients.get(1)).getPropertyValue("channel"));
+		assertEquals(channel1, new DirectFieldAccessor(recipients.poll()).getPropertyValue("channel"));
+		assertEquals(channel2, new DirectFieldAccessor(recipients.poll()).getPropertyValue("channel"));
 	}
 
 	@Test
@@ -359,11 +360,16 @@ public class RecipientListRouterTests {
 		router.setChannels(channels);
 	}
 
-	@Test(expected = IllegalArgumentException.class)
-	public void noChannelListFailsInitialization() {
+	@Test
+	public void noChannelListPassInitialization() {
 		RecipientListRouter router = new RecipientListRouter();
 		router.setBeanFactory(mock(BeanFactory.class));
+		QueueChannel defaultOutputChannel = new QueueChannel();
+		router.setDefaultOutputChannel(defaultOutputChannel);
 		router.afterPropertiesSet();
+		router.handleMessage(new GenericMessage("foo"));
+		Message receive = defaultOutputChannel.receive(1000);
+		assertNotNull(receive);
 	}
 
 	@Test
diff --git a/src/reference/docbook/router.xml b/src/reference/docbook/router.xml
index a8f8acee34..8ca426fa4c 100644
--- a/src/reference/docbook/router.xml
+++ b/src/reference/docbook/router.xml
@@ -559,6 +559,42 @@
           
       
 
+	
+ RecipientListRouterManagement + + Starting with version 4.1, the RecipientListRouter provides + several operation to manipulate with recipients dynamically at runtime. These + management operations are presented by RecipientListRouterManagement + @ManagedResource. They are available using + as well as via JMX: + + + + + + +]]> + + From the application start up the simpleRouter will have only one channel1 + recipient. But after the addRecipient command above the new channel2 recipient + will be added. It is a "registering an interest in something that is part of the Message" use case, when + we may be interested in messages from the router at some time period, so we are + subscribing to the the recipient-list-router and in some point decide to + unsubscribe our interest. + + + Having the runtime management operation for the <recipient-list-router>, it can be configured + without any <recipient> from the start. In this case the behaviour of + RecipientListRouter is the same, when there is no one matching recipient for the + message: if defaultOutputChannel is configured, the message will be sent there, otherwise + the MessageDeliveryException is thrown. + +
+
XPath Router The XPath Router is part of the XML Module. As such, please read chapter diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index b3ed054d4b..f1483bb1ef 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -136,5 +136,15 @@ See for more information.
+
+ Management for RecipientListRouter + + The RecipientListRouter provides now several management + operations to configure recipients at runtime. + With that the <recipient-list-router> can now be configured without any + <recipient> from the start. + See for more information. + +