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

@@ -17,6 +17,7 @@
package org.springframework.integration.http.config; package org.springframework.integration.http.config;
import java.util.List; import java.util.List;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanDefinitionStoreException;
@@ -74,7 +75,7 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
throws BeanDefinitionStoreException { throws BeanDefinitionStoreException {
String id = super.resolveId(element, definition, parserContext); String id = super.resolveId(element, definition, parserContext);
if (!element.hasAttribute(getInputChannelAttributeName())) { if (!this.expectReply && !element.hasAttribute("channel")) {
// the created channel will get the 'id', so the adapter's bean name includes a suffix // the created channel will get the 'id', so the adapter's bean name includes a suffix
id = id + ".adapter"; id = id + ".adapter";
} }

View File

@@ -18,14 +18,17 @@ package org.springframework.integration.jms.config;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition; 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.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jms.JmsMessageDrivenEndpoint; import org.springframework.integration.jms.JmsMessageDrivenEndpoint;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -79,6 +82,22 @@ public class JmsMessageDrivenEndpointParser extends AbstractSingleBeanDefinition
return JmsMessageDrivenEndpoint.class.getName(); 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 @Override
protected boolean shouldGenerateId() { protected boolean shouldGenerateId() {
return false; return false;
@@ -100,7 +119,11 @@ public class JmsMessageDrivenEndpointParser extends AbstractSingleBeanDefinition
} }
private String parseMessageListenerContainer(Element element, ParserContext parserContext) { private String parseMessageListenerContainer(Element element, ParserContext parserContext) {
String containerClass = element.getAttribute("container-class");
if (element.hasAttribute("container")) { if (element.hasAttribute("container")) {
if (StringUtils.hasText(containerClass)) {
parserContext.getReaderContext().error("Cannot have both 'container' and 'container-class'", element);
}
for (String containerAttribute : containerAttributes) { for (String containerAttribute : containerAttributes) {
if (element.hasAttribute(containerAttribute)) { if (element.hasAttribute(containerAttribute)) {
parserContext.getReaderContext().error("The '" + containerAttribute + parserContext.getReaderContext().error("The '" + containerAttribute +
@@ -110,8 +133,13 @@ public class JmsMessageDrivenEndpointParser extends AbstractSingleBeanDefinition
return element.getAttribute("container"); return element.getAttribute("container");
} }
// otherwise, we build a DefaultMessageListenerContainer instance // otherwise, we build a DefaultMessageListenerContainer instance
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( BeanDefinitionBuilder builder;
"org.springframework.jms.listener.DefaultMessageListenerContainer"); if (StringUtils.hasText(containerClass)) {
builder = BeanDefinitionBuilder.genericBeanDefinition(containerClass);
}
else {
builder = BeanDefinitionBuilder.genericBeanDefinition(DefaultMessageListenerContainer.class);
}
String destinationAttribute = this.expectReply ? "request-destination" : "destination"; String destinationAttribute = this.expectReply ? "request-destination" : "destination";
String destinationNameAttribute = this.expectReply ? "request-destination-name" : "destination-name"; String destinationNameAttribute = this.expectReply ? "request-destination-name" : "destination-name";
String pubSubDomainAttribute = this.expectReply ? "request-pub-sub-domain" : "pub-sub-domain"; 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"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel");
} }
else { 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, "send-timeout", "requestTimeout");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload", "extractRequestPayload"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload", "extractRequestPayload");
} }

View File

@@ -1201,6 +1201,11 @@
<xsd:extension base="jmsInboundAdapterType"> <xsd:extension base="jmsInboundAdapterType">
<xsd:attribute name="container" type="xsd:string"> <xsd:attribute name="container" type="xsd:string">
<xsd:annotation> <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> <xsd:appinfo>
<tool:annotation kind="ref"> <tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jms.listener.AbstractMessageListenerContainer"/> <tool:expected-type type="org.springframework.jms.listener.AbstractMessageListenerContainer"/>
@@ -1208,6 +1213,22 @@
</xsd:appinfo> </xsd:appinfo>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </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:extension>
</xsd:complexContent> </xsd:complexContent>
</xsd:complexType> </xsd:complexType>

View File

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

View File

@@ -87,12 +87,21 @@
message-driven Channel Adapter with a <classname>Destination</classname> reference. message-driven Channel Adapter with a <classname>Destination</classname> reference.
<programlisting language="xml"><![CDATA[<int-jms:message-driven-channel-adapter id="jmsIn" destination="inQueue" channel="exampleChannel"/>]]></programlisting> <programlisting language="xml"><![CDATA[<int-jms:message-driven-channel-adapter id="jmsIn" destination="inQueue" channel="exampleChannel"/>]]></programlisting>
<note> <note>
<para>
The Message-Driven adapter also accepts several properties that pertain to the MessageListener container. The Message-Driven adapter also accepts several properties that pertain to the MessageListener container.
These values are only considered if you do not provide an actual 'container' reference. In that case, These values are only considered if you do not provide a <code>container</code> reference. In that case,
an instance of DefaultMessageListenerContainer will be created and configured based on these properties. an instance of DefaultMessageListenerContainer will be created and configured based on these properties.
For example, you can specify the "transaction-manager" reference, the "concurrent-consumers" value, and For example, you can specify the "transaction-manager" reference, the "concurrent-consumers" value, and
several other property references and values. Refer to the JavaDoc and Spring Integration's JMS Schema several other property references and values. Refer to the JavaDoc and Spring Integration's JMS Schema
(spring-integration-jms.xsd) for more detail. (spring-integration-jms.xsd) for more details.
</para>
<para>
If you have a custom listener container implementation (usually a subclass of
<classname>DefaultMessageListenerContainer</classname>), you can either provide a reference to an instance
of it using the <code>container</code> attribute, or simply provide its fully qualified class name using
the <code>container-class</code> attribute. In that case, the attributes on the adapter
are transferred to an instance of your custom container.
</para>
</note> </note>
</para> </para>
<para> <para>