INT-2352 Support Control-Bus Atomic Router Updates

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

Add `replaceChannelMappings()` to the `AMMR`.

INT-2352 Polishing - PR Comments

Consolidate tests.
Fix up `@ManagedAttribute` Vs `@ManagedOperation`
Expose `setChannelMappings` over JMX
This commit is contained in:
Gary Russell
2014-04-15 18:55:50 +03:00
committed by Artem Bilan
parent 466af8a2f5
commit 66fe1c74be
6 changed files with 199 additions and 13 deletions

View File

@@ -22,10 +22,13 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -64,15 +67,13 @@ public abstract class AbstractMappingMessageRouter extends AbstractMessageRouter
*
* @param channelMappings The channel mappings.
*/
@Override
@ManagedAttribute
public void setChannelMappings(Map<String, String> channelMappings) {
Map<String, String> oldChannelMappings = this.channelMappings;
Assert.notNull(channelMappings, "'channelMappings' must not be null");
Map<String, String> newChannelMappings = new ConcurrentHashMap<String, String>();
newChannelMappings.putAll(channelMappings);
this.channelMappings = newChannelMappings;
if (logger.isDebugEnabled()) {
logger.debug("Channel mappings:" + oldChannelMappings
+ " replaced with:" + newChannelMappings);
}
this.doSetChannelMappings(newChannelMappings);
}
/**
@@ -122,7 +123,9 @@ public abstract class AbstractMappingMessageRouter extends AbstractMessageRouter
*
* @return The channel mappings.
*/
protected Map<String, String> getChannelMappings() {
@Override
@ManagedAttribute
public Map<String, String> getChannelMappings() {
return Collections.unmodifiableMap(this.channelMappings);
}
@@ -176,6 +179,38 @@ public abstract class AbstractMappingMessageRouter extends AbstractMessageRouter
return channels;
}
/**
* Convenience method allowing conversion of a list
* of mappings in a control-bus message.
* <p>This is intended to be called via a control-bus; keys and values that are not
* Strings will be ignored.
* <p>Mappings must be delimited with newlines, for example:
* <p>{@code "@'myRouter.handler'.replaceChannelMappings('foo=qux \n baz=bar')"}.
* @param channelMappings The channel mappings.
*
* @since 4.0
*/
@Override
@ManagedOperation
public void replaceChannelMappings(Properties channelMappings) {
Assert.notNull(channelMappings, "'channelMappings' must not be null");
Map<String, String> newChannelMappings = new ConcurrentHashMap<String, String>();
Set<String> keys = channelMappings.stringPropertyNames();
for (String key : keys) {
newChannelMappings.put(key.trim(), channelMappings.getProperty(key).trim());
}
this.doSetChannelMappings(newChannelMappings);
}
private void doSetChannelMappings(Map<String, String> newChannelMappings) {
Map<String, String> oldChannelMappings = this.channelMappings;
this.channelMappings = newChannelMappings;
if (logger.isDebugEnabled()) {
logger.debug("Channel mappings:" + oldChannelMappings
+ " replaced with:" + newChannelMappings);
}
}
private MessageChannel resolveChannelForName(String channelName, Message<?> message) {
if (this.channelResolver == null) {
this.onInit();

View File

@@ -15,9 +15,19 @@
*/
package org.springframework.integration.router;
import java.util.Map;
import java.util.Properties;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.messaging.core.DestinationResolver;
/**
* Exposes channel mapping operations when the router is proxied.
* {@link #setChannelMappings(Map)} is also exposed. This cannot
* be used with a control-bus, but it can be used programmatically
* or over JMX.
*
* @author Gary Russell
* @since 2.1
*
@@ -26,19 +36,45 @@ public interface MappingMessageRouterManagement {
/**
* Add a channel mapping from the provided key to channel name.
*
* @param key The key.
* @param channelName The channel name.
*/
@ManagedOperation
public abstract void setChannelMapping(String key, String channelName);
void setChannelMapping(String key, String channelName);
/**
* Remove a channel mapping for the given key if present.
*
* @param key The key.
*/
@ManagedOperation
public abstract void removeChannelMapping(String key);
void removeChannelMapping(String key);
/**
* Provide mappings from channel keys to channel names.
* @param channelMappings The channel mappings.
*
* @since 4.0
*/
@ManagedOperation
void replaceChannelMappings(Properties channelMappings);
/**
* @return an unmodifiable map of channel mappings.
*
* @since 4.0
*/
@ManagedAttribute
Map<String, String> getChannelMappings();
/**
* Provide mappings from channel keys to channel names.
* Channel names will be resolved by the {@link DestinationResolver}.
*
* @param channelMappings The channel mappings.
*
* @since 4.0
*/
@ManagedAttribute
void setChannelMappings(Map<String, String> channelMappings);
}

View File

@@ -14,4 +14,13 @@
<beans:bean id="service" class="org.springframework.integration.config.xml.ControlBusTests$Service" />
<router id="router" expression="headers['channel']" input-channel="routerIn">
<mapping value="foo" channel="bar" />
<mapping value="baz" channel="qux" />
</router>
<channel id="bar" />
<channel id="qux" />
</beans:beans>

View File

@@ -22,6 +22,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -40,6 +41,7 @@ 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.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -51,6 +53,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class ControlBusTests {
@Autowired
@@ -115,6 +118,26 @@ public class ControlBusTests {
this.registry.setReaperDelay(60000);
}
@Test
public void testRouterMappings() {
MessagingTemplate messagingTemplate = new MessagingTemplate();
messagingTemplate.setReceiveTimeout(1000);
messagingTemplate.convertAndSend(input, "@'router.handler'.getChannelMappings()");
Message<?> result = this.output.receive(0);
assertNotNull(result);
Map<?, ?> mappings = (Map<?, ?>) result.getPayload();
assertEquals("bar", mappings.get("foo"));
assertEquals("qux", mappings.get("baz"));
messagingTemplate.convertAndSend(input,
"@'router.handler'.replaceChannelMappings('foo=qux \n baz=bar')");
messagingTemplate.convertAndSend(input, "@'router.handler'.getChannelMappings()");
result = this.output.receive(0);
assertNotNull(result);
mappings = (Map<?, ?>) result.getPayload();
assertEquals("bar", mappings.get("baz"));
assertEquals("qux", mappings.get("foo"));
}
public static class Service {
private final CountDownLatch latch = new CountDownLatch(1);