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;
import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanDefinitionStoreException;
@@ -74,7 +75,7 @@ public class HttpInboundEndpointParser extends AbstractSingleBeanDefinitionParse
throws BeanDefinitionStoreException {
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
id = id + ".adapter";
}

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>

View File

@@ -87,12 +87,21 @@
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>
<note>
<para>
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.
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
(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>
</para>
<para>