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:
committed by
Artem Bilan
parent
f11f00b5ab
commit
e128c80c19
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user