INT-2431 Improve 'Dispatcher Has No Subscribers'
Add channel name to MessageDeliveryException
'Dispatcher has no subscribers'.
In a large integration flow, it can be difficult to track
down which subscribable channel has no subscribers.
This commit adds to the message text in the form
for channel someChannelName
to the exception message. For example:
"Dispatcher has no subscribers for channel myChannel."
Also
for amqp-channel someAmqpChannelName
for jms-channel someJMSChannelName
for redis-channel someJMSChannelName
INT-2431 Polishing
Make component type and name immutable once set to avoid
channels further down the stack frame claiming ownership.
INT-2431 Polishing
* PR Comments - use a new Exception type
* Add support for Redis
* Add WARN logs for AMQP/JMS pub-sub channels
INT-2431 Polishing
PR Comments: tighten up isPubSub field in AMQP-backed channel.
INT-2431 Polishing
Ensure channel name is not 'null' or empty string.
This commit is contained in:
committed by
Oleg Zhurakousky
parent
0b649abfaa
commit
f75dc53ab0
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -24,6 +24,8 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageDeliveryException;
|
||||
import org.springframework.integration.MessageDispatchingException;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.core.MessageHandler;
|
||||
import org.springframework.integration.core.SubscribableChannel;
|
||||
@@ -35,9 +37,11 @@ import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
import org.springframework.jms.listener.AbstractMessageListenerContainer;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SubscribableJmsChannel extends AbstractJmsChannel implements SubscribableChannel, SmartLifecycle, DisposableBean {
|
||||
@@ -71,8 +75,11 @@ public class SubscribableJmsChannel extends AbstractJmsChannel implements Subscr
|
||||
return;
|
||||
}
|
||||
super.onInit();
|
||||
this.configureDispatcher(this.container.isPubSubDomain());
|
||||
MessageListener listener = new DispatchingMessageListener(this.getJmsTemplate(), this.dispatcher);
|
||||
boolean isPubSub = this.container.isPubSubDomain();
|
||||
this.configureDispatcher(isPubSub);
|
||||
MessageListener listener = new DispatchingMessageListener(
|
||||
this.getJmsTemplate(), this.dispatcher,
|
||||
this.getComponentName(), isPubSub);
|
||||
this.container.setMessageListener(listener);
|
||||
if (!this.container.isActive()) {
|
||||
this.container.afterPropertiesSet();
|
||||
@@ -82,7 +89,7 @@ public class SubscribableJmsChannel extends AbstractJmsChannel implements Subscr
|
||||
|
||||
private void configureDispatcher(boolean isPubSub) {
|
||||
if (isPubSub) {
|
||||
this.dispatcher = new BroadcastingDispatcher();
|
||||
this.dispatcher = new BroadcastingDispatcher(true);
|
||||
}
|
||||
else {
|
||||
UnicastingDispatcher unicastingDispatcher = new UnicastingDispatcher();
|
||||
@@ -100,18 +107,26 @@ public class SubscribableJmsChannel extends AbstractJmsChannel implements Subscr
|
||||
|
||||
private final MessageDispatcher dispatcher;
|
||||
|
||||
private final String channelName;
|
||||
|
||||
private DispatchingMessageListener(JmsTemplate jmsTemplate, MessageDispatcher dispatcher) {
|
||||
private final boolean isPubSub;
|
||||
|
||||
|
||||
private DispatchingMessageListener(JmsTemplate jmsTemplate,
|
||||
MessageDispatcher dispatcher, String channelName, boolean isPubSub) {
|
||||
this.jmsTemplate = jmsTemplate;
|
||||
this.dispatcher = dispatcher;
|
||||
this.channelName = channelName;
|
||||
this.isPubSub = isPubSub;
|
||||
}
|
||||
|
||||
|
||||
public void onMessage(javax.jms.Message message) {
|
||||
Message<?> messageToSend = null;
|
||||
try {
|
||||
Object converted = this.jmsTemplate.getMessageConverter().fromMessage(message);
|
||||
if (converted != null) {
|
||||
Message<?> messageToSend = (converted instanceof Message<?>) ? (Message<?>) converted
|
||||
messageToSend = (converted instanceof Message<?>) ? (Message<?>) converted
|
||||
: MessageBuilder.withPayload(converted).build();
|
||||
this.dispatcher.dispatch(messageToSend);
|
||||
}
|
||||
@@ -119,6 +134,21 @@ public class SubscribableJmsChannel extends AbstractJmsChannel implements Subscr
|
||||
logger.warn("MessageConverter returned null, no Message to dispatch");
|
||||
}
|
||||
}
|
||||
catch (MessageDispatchingException e) {
|
||||
String channelName = StringUtils.hasText(this.channelName) ? this.channelName : "unknown";
|
||||
String exceptionMessage = e.getMessage() + " for jms-channel "
|
||||
+ channelName + ".";
|
||||
if (this.isPubSub) {
|
||||
// log only for backwards compatibility with pub/sub
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn(exceptionMessage, e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new MessageDeliveryException(
|
||||
messageToSend, exceptionMessage, e);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new MessagingException("failed to handle incoming JMS Message", e);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -48,6 +48,7 @@ import org.springframework.util.StringUtils;
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
* @since 2.0
|
||||
*/
|
||||
public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChannel> implements SmartLifecycle, DisposableBean, BeanNameAware {
|
||||
@@ -337,8 +338,8 @@ public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChanne
|
||||
if (!CollectionUtils.isEmpty(this.interceptors)) {
|
||||
this.channel.setInterceptors(this.interceptors);
|
||||
}
|
||||
this.channel.afterPropertiesSet();
|
||||
this.channel.setBeanName(this.beanName);
|
||||
this.channel.afterPropertiesSet();
|
||||
return this.channel;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -21,6 +21,11 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -29,25 +34,32 @@ import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.MessageListener;
|
||||
|
||||
import org.apache.activemq.ActiveMQConnectionFactory;
|
||||
import org.apache.activemq.command.ActiveMQQueue;
|
||||
import org.apache.activemq.command.ActiveMQTopic;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageDeliveryException;
|
||||
import org.springframework.integration.core.MessageHandler;
|
||||
import org.springframework.integration.jms.config.JmsChannelFactoryBean;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.jms.listener.AbstractMessageListenerContainer;
|
||||
import org.springframework.jms.listener.DefaultMessageListenerContainer;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SubscribableJmsChannelTests {
|
||||
|
||||
@@ -247,6 +259,85 @@ public class SubscribableJmsChannelTests {
|
||||
assertFalse(channel.isRunning());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatcherHasNoSubscribersQueue() throws Exception {
|
||||
JmsChannelFactoryBean factoryBean = new JmsChannelFactoryBean(true);
|
||||
factoryBean.setConnectionFactory(this.connectionFactory);
|
||||
factoryBean.setDestinationName("noSubscribersQueue");
|
||||
factoryBean.setBeanName("noSubscribersChannel");
|
||||
factoryBean.afterPropertiesSet();
|
||||
SubscribableJmsChannel channel = (SubscribableJmsChannel) factoryBean.getObject();
|
||||
channel.afterPropertiesSet();
|
||||
|
||||
AbstractMessageListenerContainer container = TestUtils
|
||||
.getPropertyValue(channel, "container",
|
||||
AbstractMessageListenerContainer.class);
|
||||
MessageListener listener = (MessageListener) container.getMessageListener();
|
||||
try {
|
||||
listener.onMessage(new StubTextMessage("Hello, world!"));
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (MessageDeliveryException e) {
|
||||
assertEquals("Dispatcher has no subscribers for jms-channel noSubscribersChannel.", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatcherHasNoSubscribersTopic() throws Exception {
|
||||
JmsChannelFactoryBean factoryBean = new JmsChannelFactoryBean(true);
|
||||
factoryBean.setConnectionFactory(this.connectionFactory);
|
||||
factoryBean.setDestinationName("noSubscribersTopic");
|
||||
factoryBean.setBeanName("noSubscribersChannel");
|
||||
factoryBean.setPubSubDomain(true);
|
||||
factoryBean.afterPropertiesSet();
|
||||
SubscribableJmsChannel channel = (SubscribableJmsChannel) factoryBean.getObject();
|
||||
channel.afterPropertiesSet();
|
||||
|
||||
AbstractMessageListenerContainer container = TestUtils
|
||||
.getPropertyValue(channel, "container",
|
||||
AbstractMessageListenerContainer.class);
|
||||
MessageListener listener = (MessageListener) container.getMessageListener();
|
||||
List<String> logList = insertMockLoggerInListener(channel);
|
||||
listener.onMessage(new StubTextMessage("Hello, world!"));
|
||||
verifyLogReceived(logList);
|
||||
}
|
||||
|
||||
private List<String> insertMockLoggerInListener(
|
||||
SubscribableJmsChannel channel) {
|
||||
AbstractMessageListenerContainer container = TestUtils.getPropertyValue(
|
||||
channel, "container", AbstractMessageListenerContainer.class);
|
||||
Log logger = mock(Log.class);
|
||||
final ArrayList<String> logList = new ArrayList<String>();
|
||||
doAnswer(new Answer<Object>() {
|
||||
public Object answer(InvocationOnMock invocation)
|
||||
throws Throwable {
|
||||
String message = (String) invocation.getArguments()[0];
|
||||
if (message.startsWith("Dispatcher has no subscribers")) {
|
||||
logList.add(message);
|
||||
}
|
||||
return null;
|
||||
}}).when(logger).warn(anyString(), any(Exception.class));
|
||||
when(logger.isWarnEnabled()).thenReturn(true);
|
||||
Object listener = container.getMessageListener();
|
||||
DirectFieldAccessor dfa = new DirectFieldAccessor(listener);
|
||||
dfa.setPropertyValue("logger", logger);
|
||||
return logList;
|
||||
}
|
||||
|
||||
private void verifyLogReceived(final List<String> logList) {
|
||||
assertTrue("Failed to get expected exception", logList.size() > 0);
|
||||
boolean expectedExceptionFound = false;
|
||||
while (logList.size() > 0) {
|
||||
String message = logList.remove(0);
|
||||
assertNotNull("Failed to get expected exception", message);
|
||||
if (message.startsWith("Dispatcher has no subscribers")) {
|
||||
expectedExceptionFound = true;
|
||||
assertEquals("Dispatcher has no subscribers for jms-channel noSubscribersChannel.", message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue("Failed to get expected exception", expectedExceptionFound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks until the listener container has subscribed; if the container does not support
|
||||
|
||||
Reference in New Issue
Block a user