INT-2421 Support 'container-class' on JMS MDCA

Convenience for configuring a custom listener container - avoids
having to transfer many attributes from the adapter to the
bean when switching to a custom implementation.

JIRA: https://jira.springsource.org/browse/INT-2421

INT-2421 Polishing - PR Comments

Also auto-create adapter channel if necessary.
This commit is contained in:
Gary Russell
2013-11-07 20:39:11 +02:00
committed by Artem Bilan
parent f11f00b5ab
commit e128c80c19
6 changed files with 139 additions and 27 deletions

View File

@@ -18,14 +18,17 @@ package org.springframework.integration.jms.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jms.JmsMessageDrivenEndpoint;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.util.StringUtils;
/**
@@ -79,6 +82,22 @@ public class JmsMessageDrivenEndpointParser extends AbstractSingleBeanDefinition
return JmsMessageDrivenEndpoint.class.getName();
}
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
throws BeanDefinitionStoreException {
String id = super.resolveId(element, definition, parserContext);
if (!this.expectReply && !element.hasAttribute("channel")) {
// the created channel will get the 'id', so the adapter's bean name includes a suffix
id = id + ".adapter";
}
if (!StringUtils.hasText(id)) {
id = BeanDefinitionReaderUtils.generateBeanName(definition, parserContext.getRegistry());
}
return id;
}
@Override
protected boolean shouldGenerateId() {
return false;
@@ -100,7 +119,11 @@ public class JmsMessageDrivenEndpointParser extends AbstractSingleBeanDefinition
}
private String parseMessageListenerContainer(Element element, ParserContext parserContext) {
String containerClass = element.getAttribute("container-class");
if (element.hasAttribute("container")) {
if (StringUtils.hasText(containerClass)) {
parserContext.getReaderContext().error("Cannot have both 'container' and 'container-class'", element);
}
for (String containerAttribute : containerAttributes) {
if (element.hasAttribute(containerAttribute)) {
parserContext.getReaderContext().error("The '" + containerAttribute +
@@ -110,8 +133,13 @@ public class JmsMessageDrivenEndpointParser extends AbstractSingleBeanDefinition
return element.getAttribute("container");
}
// otherwise, we build a DefaultMessageListenerContainer instance
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.jms.listener.DefaultMessageListenerContainer");
BeanDefinitionBuilder builder;
if (StringUtils.hasText(containerClass)) {
builder = BeanDefinitionBuilder.genericBeanDefinition(containerClass);
}
else {
builder = BeanDefinitionBuilder.genericBeanDefinition(DefaultMessageListenerContainer.class);
}
String destinationAttribute = this.expectReply ? "request-destination" : "destination";
String destinationNameAttribute = this.expectReply ? "request-destination-name" : "destination-name";
String pubSubDomainAttribute = this.expectReply ? "request-pub-sub-domain" : "pub-sub-domain";
@@ -198,7 +226,11 @@ public class JmsMessageDrivenEndpointParser extends AbstractSingleBeanDefinition
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel");
}
else {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "channel", "requestChannel");
String channelName = element.getAttribute("channel");
if (!StringUtils.hasText(channelName)) {
channelName = IntegrationNamespaceUtils.createDirectChannel(element, parserContext);
}
builder.addPropertyReference("requestChannel", channelName);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout", "requestTimeout");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload", "extractRequestPayload");
}

View File

@@ -1201,6 +1201,11 @@
<xsd:extension base="jmsInboundAdapterType">
<xsd:attribute name="container" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to a custom listener container implementation.
Note that a custom container class will typically be a subclass of DefaultMessageListenerContainer.
This attribute is mutually exclusive with 'container-class'.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jms.listener.AbstractMessageListenerContainer"/>
@@ -1208,6 +1213,22 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="container-class" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A custom listener container implementation class as fully qualified class name.
Default is Spring's standard DefaultMessageListenerContainer.
Note that a custom container class will typically be a subclass of this
standard container class. This attribute is mutually exclusive with 'container'.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:expected-type type="java.lang.Class"/>
<tool:assignable-to type="org.springframework.jms.listener.AbstractMessageListenerContainer"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

View File

@@ -27,6 +27,7 @@ import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.jms.JmsMessageDrivenEndpoint;
@@ -38,6 +39,7 @@ import org.springframework.jms.support.destination.JmsDestinationAccessor;
/**
* @author Mark Fisher
* @author Michael Bannister
* @author Gary Russell
*/
public class JmsMessageDrivenChannelAdapterParserTests {
@@ -87,7 +89,7 @@ public class JmsMessageDrivenChannelAdapterParserTests {
public void adapterWithTaskExecutor() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"jmsInboundWithTaskExecutor.xml", this.getClass());
JmsMessageDrivenEndpoint endpoint = context.getBean("messageDrivenAdapter", JmsMessageDrivenEndpoint.class);
JmsMessageDrivenEndpoint endpoint = context.getBean("messageDrivenAdapter.adapter", JmsMessageDrivenEndpoint.class);
DefaultMessageListenerContainer container = TestUtils.getPropertyValue(endpoint, "listenerContainer",
DefaultMessageListenerContainer.class);
assertSame(context.getBean("exec"), TestUtils.getPropertyValue(container, "taskExecutor"));
@@ -95,52 +97,70 @@ public class JmsMessageDrivenChannelAdapterParserTests {
}
@Test
public void testGatewayWithReceiveTimeout() {
public void testAdapterWithReceiveTimeout() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"jmsInboundWithContainerSettings.xml", this.getClass());
JmsMessageDrivenEndpoint gateway = (JmsMessageDrivenEndpoint) context.getBean("adapterWithReceiveTimeout");
gateway.start();
JmsMessageDrivenEndpoint adapter = (JmsMessageDrivenEndpoint) context.getBean("adapterWithReceiveTimeout.adapter");
adapter.start();
AbstractMessageListenerContainer container = (AbstractMessageListenerContainer)
new DirectFieldAccessor(gateway).getPropertyValue("listenerContainer");
new DirectFieldAccessor(adapter).getPropertyValue("listenerContainer");
assertEquals(1111L, new DirectFieldAccessor(container).getPropertyValue("receiveTimeout"));
gateway.stop();
adapter.stop();
}
@Test
public void testGatewayWithRecoveryInterval() {
public void testAdapterWithRecoveryInterval() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"jmsInboundWithContainerSettings.xml", this.getClass());
JmsMessageDrivenEndpoint gateway = (JmsMessageDrivenEndpoint) context.getBean("adapterWithRecoveryInterval");
gateway.start();
JmsMessageDrivenEndpoint adapter = (JmsMessageDrivenEndpoint) context.getBean("adapterWithRecoveryInterval.adapter");
adapter.start();
AbstractMessageListenerContainer container = (AbstractMessageListenerContainer)
new DirectFieldAccessor(gateway).getPropertyValue("listenerContainer");
new DirectFieldAccessor(adapter).getPropertyValue("listenerContainer");
assertEquals(2222L, new DirectFieldAccessor(container).getPropertyValue("recoveryInterval"));
gateway.stop();
adapter.stop();
}
@Test
public void testGatewayWithIdleTaskExecutionLimit() {
public void testAdapterWithIdleTaskExecutionLimit() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"jmsInboundWithContainerSettings.xml", this.getClass());
JmsMessageDrivenEndpoint gateway = (JmsMessageDrivenEndpoint) context.getBean("adapterWithIdleTaskExecutionLimit");
gateway.start();
JmsMessageDrivenEndpoint adapter = (JmsMessageDrivenEndpoint) context.getBean("adapterWithIdleTaskExecutionLimit.adapter");
adapter.start();
AbstractMessageListenerContainer container = (AbstractMessageListenerContainer)
new DirectFieldAccessor(gateway).getPropertyValue("listenerContainer");
new DirectFieldAccessor(adapter).getPropertyValue("listenerContainer");
assertEquals(7, new DirectFieldAccessor(container).getPropertyValue("idleTaskExecutionLimit"));
gateway.stop();
adapter.stop();
}
@Test
public void testGatewayWithIdleConsumerLimit() {
public void testAdapterWithIdleConsumerLimit() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"jmsInboundWithContainerSettings.xml", this.getClass());
JmsMessageDrivenEndpoint gateway = (JmsMessageDrivenEndpoint) context.getBean("adapterWithIdleConsumerLimit");
gateway.start();
JmsMessageDrivenEndpoint adapter = (JmsMessageDrivenEndpoint) context.getBean("adapterWithIdleConsumerLimit.adapter");
adapter.start();
AbstractMessageListenerContainer container = (AbstractMessageListenerContainer)
new DirectFieldAccessor(gateway).getPropertyValue("listenerContainer");
new DirectFieldAccessor(adapter).getPropertyValue("listenerContainer");
assertEquals(33, new DirectFieldAccessor(container).getPropertyValue("idleConsumerLimit"));
assertEquals(3, new DirectFieldAccessor(container).getPropertyValue("cacheLevel"));
gateway.stop();
adapter.stop();
}
@Test
public void testAdapterWithContainerClass() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"jmsInboundWithContainerClass.xml", this.getClass());
JmsMessageDrivenEndpoint adapter = context.getBean("adapterWithIdleConsumerLimit.adapter", JmsMessageDrivenEndpoint.class);
MessageChannel channel = context.getBean("adapterWithIdleConsumerLimit", MessageChannel.class);
assertSame(channel, TestUtils.getPropertyValue(adapter, "listener.gatewayDelegate.requestChannel"));
adapter.start();
FooContainer container = TestUtils.getPropertyValue(adapter, "listenerContainer", FooContainer.class);
assertEquals(33, new DirectFieldAccessor(container).getPropertyValue("idleConsumerLimit"));
assertEquals(3, new DirectFieldAccessor(container).getPropertyValue("cacheLevel"));
adapter.stop();
}
public static final class FooContainer extends DefaultMessageListenerContainer {
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:si="http://www.springframework.org/schema/integration"
xmlns:jms="http://www.springframework.org/schema/integration/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jms
http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd">
<jms:message-driven-channel-adapter id="adapterWithIdleConsumerLimit"
connection-factory="testConnectionFactory"
destination-name="testQueue"
container-class="org.springframework.integration.jms.config.JmsMessageDrivenChannelAdapterParserTests$FooContainer"
idle-consumer-limit="33"
cache-level="3"
auto-startup="false" />
<bean id="testConnectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<constructor-arg>
<bean class="org.springframework.integration.jms.StubConnection">
<constructor-arg value="message-driven-test"/>
</bean>
</constructor-arg>
</bean>
</beans>