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 f4aedd4bd4a..2c430d6ee59 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 @@ -28,7 +28,6 @@ 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; @@ -47,8 +46,6 @@ public class RecipientListRouterParser extends AbstractRouterParser { protected BeanDefinition doParseRouter(Element element, ParserContext parserContext) { 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(); for (Element childElement : childElements) { BeanDefinitionBuilder recipientBuilder = BeanDefinitionBuilder.genericBeanDefinition(Recipient.class); @@ -62,7 +59,9 @@ protected BeanDefinition doParseRouter(Element element, ParserContext parserCont } 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 e1ab8a6a085..f19eb91c766 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 @@ -26,21 +26,34 @@ /** * 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 final String expressionString; public ExpressionEvaluatingSelector(String expressionString) { 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 b7ad5178b54..59670f6216d 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,25 @@ 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 +67,12 @@
  * @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,25 +95,50 @@ public void setChannels(List channels) {
 	 */
 	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
-	public String getComponentType() {
-		return "recipient-list-router";
+	@ManagedAttribute
+	public void setRecipientMappings(Map recipientMappings) {
+		Assert.notEmpty(recipientMappings, "recipientMappings must not be empty");
+		ConcurrentLinkedQueue originalRecipients = this.recipients;
+		this.recipients.clear();
+		for(Iterator> it = recipientMappings.entrySet().iterator(); it.hasNext();) {
+			Entry next = it.next();
+			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
-	public void onInit() throws Exception {
-		Assert.notEmpty(this.recipients, "recipient list must not be empty");
-		super.onInit();
+	public String getComponentType() {
+		return "recipient-list-router";
 	}
 
+
 	@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 +146,89 @@ protected Collection determineTargetChannels(Message message)
 		return channels;
 	}
 
+	@Override
+	@ManagedOperation
+	public void addRecipient(String channelName, String selectorExpression) {
+		Assert.notNull(channelName, "channelName can't be null.");
+		Assert.hasText(channelName, "channelName can't be empty.");
+		Assert.notNull(selectorExpression, "selectorExpression can't be null.");
+		Assert.hasText(selectorExpression, "selectorExpression can't 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.notNull(channelName, "channelName can't be null.");
+		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.notNull(recipientMappings, "'recipientMappings' must not be null");
+		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 +246,9 @@ public Recipient(MessageChannel channel, MessageSelector selector) {
 			this.selector = selector;
 		}
 
+		private MessageSelector getSelector() {
+			return selector;
+		}
 
 		public MessageChannel getChannel() {
 			return this.channel;
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 00000000000..d227c07a1a0
--- /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 73f8e537c38..51b52e9d767 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
@@ -2923,7 +2923,7 @@
             
                 
                     
-                    
+                    
 						
 						    
 						        An expression to be evaluated to determine if this recipient
@@ -2944,7 +2944,7 @@
         
             
                 
-                    
+                    
                         
 						    
 						        An expression to be evaluated to determine if this recipient
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 00000000000..18694a6ca04
--- /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 00000000000..4f552c4b957
--- /dev/null
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusRecipientListRouterTests.java
@@ -0,0 +1,167 @@
+/*
+ * 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
+	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);
+		Properties prop = new Properties();
+		prop.setProperty("channel7", "true");
+
+		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 5b6a0dc7255..bbec6c3c74e 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
@@ -16,12 +16,17 @@
 
 package org.springframework.integration.router;
 
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.mock;
+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;
@@ -58,11 +63,11 @@ public void channelConfig() {
 		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
@@ -355,11 +360,12 @@ public void emptyChannelListRejected() {
 		router.setChannels(channels);
 	}
 
-	@Test(expected = IllegalArgumentException.class)
+	@Test
 	public void noChannelListFailsInitialization() {
 		RecipientListRouter router = new RecipientListRouter();
 		router.setBeanFactory(mock(BeanFactory.class));
 		router.afterPropertiesSet();
+		assertNotNull(router);
 	}
 
 	@Test
diff --git a/src/reference/docbook/router.xml b/src/reference/docbook/router.xml
index a8f8acee349..4a2c252dca9 100644
--- a/src/reference/docbook/router.xml
+++ b/src/reference/docbook/router.xml
@@ -558,6 +558,28 @@
              attribute is not defined, the channel will always be among the list of recipients.
           
       
+      
+	
+ RecipientListRouterManagement + + Starting with version 4.1, a RecipientListRouterManagement will support adding and removing + recipients dynamically etc: + + + + + ]]> + + + + <recipient-list-router> can have no <recipient> for feature reconfiguration at runtime + +
XPath Router diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index b96ba11e6f6..6c6e81081fd 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -74,5 +74,13 @@ See for more information.
+
+ Add ManagedOperation and ManagedAttribute in RecipientListRouter + + The RecipientListRouterManagement implemented by RecipientListRouter + will support ManagedOperation and ManagedAttribute + See for more information. + +