diff --git a/build.gradle b/build.gradle
index b80b4ab75c..3b4680b6b0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -485,6 +485,7 @@ project("spring-jms") {
compile(project(":spring-beans"))
compile(project(":spring-aop"))
compile(project(":spring-context"))
+ compile(project(":spring-messaging"))
compile(project(":spring-tx"))
provided("javax.jms:jms-api:1.1-rev-1")
optional(project(":spring-oxm"))
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java
index 20353ae664..c876f3d704 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java
@@ -190,6 +190,18 @@ public class AnnotationConfigUtils {
public static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.cache.aspectj.AspectJJCacheConfiguration";
+ /**
+ * The bean name of the internally managed jms listener annotation processor.
+ */
+ public static final String JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME =
+ "org.springframework.jms.config.internalJmsListenerAnnotationProcessor";
+
+ /**
+ * The bean name of the internally managed jms listener endpoint registry.
+ */
+ public static final String JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME =
+ "org.springframework.jms.config.internalJmsListenerEndpointRegistry";
+
/**
* The bean name of the internally managed JPA annotation processor.
*/
diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/EnableJms.java b/spring-jms/src/main/java/org/springframework/jms/annotation/EnableJms.java
new file mode 100644
index 0000000000..d8782b2b03
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/annotation/EnableJms.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Import;
+
+/**
+ * Enable JMS listener annotated endpoints that are created under the cover
+ * by a {@link org.springframework.jms.config.JmsListenerContainerFactory
+ * JmsListenerContainerFactory}. To be used on
+ * @{@link org.springframework.context.annotation.Configuration Configuration} classes
+ * as follows:
+ *
+ *
+ *
+ * The {@code JmsListenerContainerFactory} is responsible to create the listener container
+ * responsible for a particular endpoint. Typical implementations, as the
+ * {@link org.springframework.jms.config.DefaultJmsListenerContainerFactory DefaultJmsListenerContainerFactory}
+ * used in the sample above, provides the necessary configuration options that are supported by
+ * the underlying {@link org.springframework.jms.listener.MessageListenerContainer MessageListenerContainer}.
+ *
+ *
{@code @EnableJms} enables detection of @{@link JmsListener} annotations on
+ * any Spring-managed bean in the container. For example, given a class {@code MyService}
+ *
+ *
+ * package com.acme.foo;
+ *
+ * public class MyService {
+ * @JmsListener(containerFactory = "myJmsListenerContainerFactory", destination="myQueue")
+ * public void process(String msg) {
+ * // process incoming message
+ * }
+ * }
+ *
+ * The container factory to use is identified by the {@link JmsListener#containerFactory() containerFactory}
+ * attribute defining the name of the {@code JmsListenerContainerFactory} bean to use.
+ *
+ *
the following configuration would ensure that every time a {@link javax.jms.Message}
+ * is received on the {@link javax.jms.Destination} named "myQueue", {@code MyService.process()}
+ * is called with the content of the message:
+ *
+ *
+ * @Configuration
+ * @EnableJms
+ * public class AppConfig {
+ * @Bean
+ * public MyService myService() {
+ * return new MyService();
+ * }
+ *
+ * // JMS infrastructure setup
+ * }
+ *
+ * Alternatively, if {@code MyService} were annotated with {@code @Component}, the
+ * following configuration would ensure that its {@code @JmsListener} annotated
+ * method is invoked with a matching incoming message:
+ *
+ *
+ * @Configuration
+ * @EnableJms
+ * @ComponentScan(basePackages="com.acme.foo")
+ * public class AppConfig {
+ * }
+ *
+ * Note that the created containers are not registered against the application context
+ * but can be easily located for management purposes using the
+ * {@link org.springframework.jms.config.JmsListenerEndpointRegistry JmsListenerEndpointRegistry}.
+ *
+ *
Annotated methods can use flexible signature; in particular, it is possible to use
+ * the {@link org.springframework.messaging.Message Message} abstraction and related annotations,
+ * see @{@link JmsListener} Javadoc for more details. For instance, the following would
+ * inject the content of the message and a a custom "myCounter" JMS header:
+ *
+ *
+ * @JmsListener(containerFactory = "myJmsListenerContainerFactory", destination="myQueue")
+ * public void process(String msg, @Header("myCounter") int counter) {
+ * // process incoming message
+ * }
+ *
+ * These features are abstracted by the {@link org.springframework.jms.config.JmsHandlerMethodFactory
+ * JmsHandlerMethodFactory} that is responsible to build the necessary invoker to process
+ * the annotated method. By default, {@link org.springframework.jms.config.DefaultJmsHandlerMethodFactory
+ * DefaultJmsHandlerMethodFactory} is used.
+ *
+ *
When more control is desired, a {@code @Configuration} class may implement
+ * {@link JmsListenerConfigurer}. This allows access to the underlying
+ * {@link org.springframework.jms.config.JmsListenerEndpointRegistrar JmsListenerEndpointRegistrar}
+ * instance. The following example demonstrates how to specify a default
+ * {@code JmsListenerContainerFactory} so that {@link JmsListener#containerFactory()} may be
+ * omitted for endpoints willing to use the default container factory.
+ *
+ *
+ *
+ * It is also possible to specify a custom {@link org.springframework.jms.config.JmsListenerEndpointRegistry
+ * JmsListenerEndpointRegistry} in case you need more control on the way the containers
+ * are created and managed. The example below also demonstrates how to customize the
+ * {@code JmsHandlerMethodFactory} to use with a custom {@link org.springframework.validation.Validator
+ * Validator} so that payloads annotated with {@link org.springframework.validation.annotation.Validated
+ * @Validated} are first validated against a custom {@code Validator}.
+ *
+ *
+ *
+ * Implementing {@code JmsListenerConfigurer} also allows for fine-grained
+ * control over endpoints registration via the {@code JmsListenerEndpointRegistrar}.
+ * For example, the following configures an extra endpoint:
+ *
+ *
+ *
+ * Note that all beans implementing {@code JmsListenerConfigurer} will be detected and
+ * invoked in a similar fashion. The example above can be translated in a regular bean
+ * definition registered in the context in case you use the XML configuration.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see JmsListener
+ * @see JmsListenerAnnotationBeanPostProcessor
+ * @see org.springframework.jms.config.JmsListenerEndpointRegistrar
+ * @see org.springframework.jms.config.JmsListenerEndpointRegistry
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Import(JmsBootstrapConfiguration.class)
+public @interface EnableJms {
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsBootstrapConfiguration.java b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsBootstrapConfiguration.java
new file mode 100644
index 0000000000..4cacee7aa7
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsBootstrapConfiguration.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.annotation;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.AnnotationConfigUtils;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Role;
+import org.springframework.jms.config.JmsListenerEndpointRegistry;
+
+/**
+ * {@code @Configuration} class that registers a {@link JmsListenerAnnotationBeanPostProcessor}
+ * bean capable of processing Spring's @{@link JmsListener} annotation. Also register
+ * a default {@link JmsListenerEndpointRegistry}.
+ *
+ *
This configuration class is automatically imported when using the @{@link EnableJms}
+ * annotation. See {@link EnableJms} Javadoc for complete usage.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see JmsListenerAnnotationBeanPostProcessor
+ * @see JmsListenerEndpointRegistry
+ * @see EnableJms
+ */
+@Configuration
+public class JmsBootstrapConfiguration {
+
+ @Bean(name = AnnotationConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
+ @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ public JmsListenerAnnotationBeanPostProcessor jmsListenerAnnotationProcessor() {
+ return new JmsListenerAnnotationBeanPostProcessor();
+ }
+
+ @Bean(name = AnnotationConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME)
+ public JmsListenerEndpointRegistry defaultJmsListenerEndpointRegistry() {
+ return new JmsListenerEndpointRegistry();
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java
new file mode 100644
index 0000000000..c476c68876
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.messaging.handler.annotation.MessageMapping;
+
+/**
+ * Annotation that marks a method to be the target of a JMS message
+ * listener on the specified {@link #destination()}. The {@link #containerFactory()}
+ * identifies the {@link org.springframework.jms.config.JmsListenerContainerFactory
+ * JmsListenerContainerFactory} to use to build the jms listener container. It may
+ * be omitted as long as a default container factory has been defined.
+ *
+ *
Processing of {@code @JmsListener} annotations is performed by
+ * registering a {@link JmsListenerAnnotationBeanPostProcessor}. This can be
+ * done manually or, more conveniently, through the {@code }
+ * element or {@link EnableJms} annotation.
+ *
+ *
Annotated methods are allowed to have flexible signatures similar to what
+ * {@link MessageMapping} provides, that is
+ *
+ *
{@link javax.jms.Session} to get access to the JMS session
+ *
{@link javax.jms.Message} or one if subclass to get access to the raw JMS message
+ *
{@link org.springframework.messaging.Message} to use the messaging abstraction counterpart
+ *
{@link org.springframework.messaging.handler.annotation.Payload @Payload}-annotated method
+ * arguments including the support of validation
+ *
{@link org.springframework.messaging.handler.annotation.Header @Header}-annotated method
+ * arguments to extract a specific header value, including standard JMS headers defined by
+ * {@link org.springframework.jms.support.converter.JmsHeaders JmsHeaders}
+ *
{@link org.springframework.messaging.handler.annotation.Headers @Headers}-annotated
+ * argument that must also be assignable to {@link java.util.Map} for getting access to all
+ * headers.
+ *
{@link org.springframework.messaging.MessageHeaders MessageHeaders} arguments for
+ * getting access to all headers.
+ *
{@link org.springframework.messaging.support.MessageHeaderAccessor MessageHeaderAccessor}
+ * or {@link org.springframework.jms.support.JmsMessageHeaderAccessor JmsMessageHeaderAccessor}
+ * for convenient access to all method arguments.
+ *
+ *
+ *
Annotated method may have a non {@code void} return type. When they do, the result of the
+ * method invocation is sent as a JMS reply to the destination defined by either the
+ * {@code JMSReplyTO} header of the incoming message or the value of {@link #responseDestination()}.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see EnableJms
+ * @see JmsListenerAnnotationBeanPostProcessor
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@MessageMapping
+@Documented
+public @interface JmsListener {
+
+ /**
+ * The unique identifier of the container managing this endpoint.
+ *
if none is specified an auto-generated one is provided.
+ * @see org.springframework.jms.config.JmsListenerEndpointRegistry#getContainer(String)
+ */
+ String id() default "";
+
+ /**
+ * The bean name of the {@link org.springframework.jms.config.JmsListenerContainerFactory}
+ * to use to create the message listener container responsible to serve this endpoint.
+ *
If not specified, the default container factory is used, if any.
+ */
+ String containerFactory() default "";
+
+ /**
+ * The destination name for this listener, resolved through the container-wide
+ * {@link org.springframework.jms.support.destination.DestinationResolver} strategy.
+ */
+ String destination();
+
+ /**
+ * Specify if the {@link #destination()} refers to a queue or not. Refer to a
+ * queue by default.
+ */
+ boolean queue() default true;
+
+ /**
+ * The name for the durable subscription, if any.
+ */
+ String subscription() default "";
+
+ /**
+ * The JMS message selector expression, if any
+ *
See the JMS specification for a detailed definition of selector expressions.
+ */
+ String selector() default "";
+
+ /**
+ * The name of the default response destination to send response messages to.
+ *
This will be applied in case of a request message that does not carry
+ * a "JMSReplyTo" field. The type of this destination will be determined
+ * by the listener-container's "destination-type" attribute.
+ *
Note: This only applies to a listener method with a return value,
+ * for which each result object will be converted into a response message.
+ */
+ String responseDestination() default "";
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java
new file mode 100644
index 0000000000..784a5762fd
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.annotation;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanInitializationException;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.annotation.AnnotationConfigUtils;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.jms.config.DefaultJmsHandlerMethodFactory;
+import org.springframework.jms.config.JmsHandlerMethodFactory;
+import org.springframework.jms.config.JmsListenerContainerFactory;
+import org.springframework.jms.config.JmsListenerEndpointRegistrar;
+import org.springframework.jms.config.JmsListenerEndpointRegistry;
+import org.springframework.jms.config.MethodJmsListenerEndpoint;
+import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Bean post-processor that registers methods annotated with @{@link JmsListener}
+ * to be invoked by a JMS message listener container created under the cover
+ * by a {@link org.springframework.jms.config.JmsListenerContainerFactory} according
+ * to the parameters of the annotation.
+ *
+ *
Annotated methods can use flexible arguments as defined by @{@link JmsListener}.
+ *
+ *
This post-processor is automatically registered by Spring's
+ * {@code } XML element, and also by the @{@link EnableJms}
+ * annotation.
+ *
+ *
Auto-detect any {@link JmsListenerConfigurer} instances in the container,
+ * allowing for customization of the registry to be used, the default container
+ * factory or for fine-grained control over endpoints registration. See
+ * @{@link EnableJms} Javadoc for complete usage details.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see JmsListener
+ * @see EnableJms
+ * @see JmsListenerConfigurer
+ * @see JmsListenerEndpointRegistrar
+ * @see JmsListenerEndpointRegistry
+ * @see org.springframework.jms.config.AbstractJmsListenerEndpoint
+ * @see MethodJmsListenerEndpoint
+ * @see MessageListenerFactory
+ */
+public class JmsListenerAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered,
+ ApplicationContextAware, ApplicationListener {
+
+ private final AtomicInteger counter = new AtomicInteger();
+
+ private ApplicationContext applicationContext;
+
+ private JmsListenerEndpointRegistry endpointRegistry;
+
+ private JmsListenerContainerFactory> defaultContainerFactory;
+
+ private final JmsHandlerMethodFactoryAdapter jmsHandlerMethodFactory = new JmsHandlerMethodFactoryAdapter();
+
+ private final JmsListenerEndpointRegistrar registrar = new JmsListenerEndpointRegistrar();
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+ /**
+ * Set the {@link JmsListenerEndpointRegistry} that will hold the created
+ * endpoint and manage the lifecycle of the related listener container.
+ */
+ public void setEndpointRegistry(JmsListenerEndpointRegistry endpointRegistry) {
+ this.endpointRegistry = endpointRegistry;
+ }
+
+ /**
+ * Set the default {@link JmsListenerContainerFactory} to use in case a
+ * {@link JmsListener} does not define any.
+ * {@linkplain JmsListener#containerFactory() containerFactory}
+ */
+ public void setDefaultContainerFactory(JmsListenerContainerFactory> defaultContainerFactory) {
+ this.defaultContainerFactory = defaultContainerFactory;
+ }
+
+ /**
+ * Set the {@link JmsHandlerMethodFactory} to use to configure the message
+ * listener responsible to serve an endpoint detected by this processor.
+ *
By default, {@link DefaultJmsHandlerMethodFactory} is used and it
+ * can be configured further to support additional method arguments
+ * or to customize conversion and validation support. See
+ * {@link DefaultJmsHandlerMethodFactory} Javadoc for more details.
+ */
+ public void setJmsHandlerMethodFactory(JmsHandlerMethodFactory jmsHandlerMethodFactory) {
+ this.jmsHandlerMethodFactory.setJmsHandlerMethodFactory(jmsHandlerMethodFactory);
+ }
+
+ @Override
+ public int getOrder() {
+ return LOWEST_PRECEDENCE;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
+ Class> targetClass = AopUtils.getTargetClass(bean);
+ ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
+ @Override
+ public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
+ JmsListener jmsListener = AnnotationUtils.getAnnotation(method, JmsListener.class);
+ if (jmsListener != null) {
+ processJmsListener(jmsListener, method, bean);
+ }
+ }
+ });
+ return bean;
+ }
+
+ protected void processJmsListener(JmsListener jmsListener, Method method, Object bean) {
+ if (AopUtils.isJdkDynamicProxy(bean)) {
+ try {
+ // Found a @JmsListener method on the target class for this JDK proxy ->
+ // is it also present on the proxy itself?
+ method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
+ }
+ catch (SecurityException ex) {
+ ReflectionUtils.handleReflectionException(ex);
+ }
+ catch (NoSuchMethodException ex) {
+ throw new IllegalStateException(String.format(
+ "@JmsListener method '%s' found on bean target class '%s', " +
+ "but not found in any interface(s) for bean JDK proxy. Either " +
+ "pull the method up to an interface or switch to subclass (CGLIB) " +
+ "proxies by setting proxy-target-class/proxyTargetClass " +
+ "attribute to 'true'", method.getName(), method.getDeclaringClass().getSimpleName()));
+ }
+ }
+
+ MethodJmsListenerEndpoint endpoint = new MethodJmsListenerEndpoint();
+ endpoint.setBean(bean);
+ endpoint.setMethod(method);
+ endpoint.setJmsHandlerMethodFactory(jmsHandlerMethodFactory);
+ endpoint.setId(getEndpointId(jmsListener));
+ endpoint.setDestination(jmsListener.destination());
+ endpoint.setQueue(jmsListener.queue());
+ if (StringUtils.hasText(jmsListener.selector())) {
+ endpoint.setSelector(jmsListener.selector());
+ }
+ if (StringUtils.hasText(jmsListener.subscription())) {
+ endpoint.setSubscription(jmsListener.subscription());
+ }
+ if (StringUtils.hasText(jmsListener.responseDestination())) {
+ endpoint.setResponseDestination(jmsListener.responseDestination());
+ }
+
+ JmsListenerContainerFactory> factory = null;
+ String containerFactoryBeanName = jmsListener.containerFactory();
+ if (StringUtils.hasText(containerFactoryBeanName)) {
+ try {
+ factory = applicationContext.getBean(containerFactoryBeanName, JmsListenerContainerFactory.class);
+ }
+ catch (NoSuchBeanDefinitionException e) {
+ throw new BeanInitializationException("Could not register jms listener endpoint on ["
+ + method + "], no " + JmsListenerContainerFactory.class.getSimpleName() + " with id '"
+ + containerFactoryBeanName + "' was found in the application context", e);
+ }
+ }
+
+ registrar.registerEndpoint(endpoint, factory);
+
+ }
+
+ @Override
+ public void onApplicationEvent(ContextRefreshedEvent event) {
+ if (event.getApplicationContext() != this.applicationContext) {
+ return;
+ }
+
+ Map instances =
+ this.applicationContext.getBeansOfType(JmsListenerConfigurer.class);
+ for (JmsListenerConfigurer configurer : instances.values()) {
+ configurer.configureJmsListeners(registrar);
+ }
+ if (registrar.getEndpointRegistry() == null) {
+ if (endpointRegistry == null) {
+ endpointRegistry = applicationContext
+ .getBean(AnnotationConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME,
+ JmsListenerEndpointRegistry.class);
+ }
+ registrar.setEndpointRegistry(endpointRegistry);
+ }
+ if (registrar.getDefaultContainerFactory() == null && defaultContainerFactory != null) {
+ registrar.setDefaultContainerFactory(defaultContainerFactory);
+ }
+
+ JmsHandlerMethodFactory handlerMethodFactory = registrar.getJmsHandlerMethodFactory();
+ if (handlerMethodFactory != null) {
+ this.jmsHandlerMethodFactory.setJmsHandlerMethodFactory(handlerMethodFactory);
+ }
+
+ // Create all the listeners and starts them
+ try {
+ registrar.afterPropertiesSet();
+ }
+ catch (Exception e) {
+ throw new BeanInitializationException(e.getMessage(), e);
+ }
+ }
+
+
+ private String getEndpointId(JmsListener jmsListener) {
+ if (StringUtils.hasText(jmsListener.id())) {
+ return jmsListener.id();
+ }
+ else {
+ return "org.springframework.jms.JmsListenerEndpointContainer#"
+ + counter.getAndIncrement();
+ }
+ }
+
+ /**
+ * An {@link JmsHandlerMethodFactory} adapter that offers a configurable underlying
+ * instance to use. Useful if the factory to use is determined once the endpoints
+ * have been registered but not created yet.
+ * @see JmsListenerEndpointRegistrar#setJmsHandlerMethodFactory(JmsHandlerMethodFactory)
+ */
+ private class JmsHandlerMethodFactoryAdapter implements JmsHandlerMethodFactory {
+
+ private JmsHandlerMethodFactory jmsHandlerMethodFactory;
+
+ private void setJmsHandlerMethodFactory(JmsHandlerMethodFactory jmsHandlerMethodFactory) {
+ this.jmsHandlerMethodFactory = jmsHandlerMethodFactory;
+ }
+
+ @Override
+ public InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method) {
+ return getJmsHandlerMethodFactory().createInvocableHandlerMethod(bean, method);
+ }
+
+ private JmsHandlerMethodFactory getJmsHandlerMethodFactory() {
+ if (jmsHandlerMethodFactory == null) {
+ jmsHandlerMethodFactory= createDefaultJmsHandlerMethodFactory();
+ }
+ return jmsHandlerMethodFactory;
+ }
+
+ private JmsHandlerMethodFactory createDefaultJmsHandlerMethodFactory() {
+ DefaultJmsHandlerMethodFactory defaultFactory = new DefaultJmsHandlerMethodFactory();
+ defaultFactory.setApplicationContext(applicationContext);
+ defaultFactory.afterPropertiesSet();
+ return defaultFactory;
+ }
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerConfigurer.java b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerConfigurer.java
new file mode 100644
index 0000000000..09f527d87b
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerConfigurer.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.annotation;
+
+import org.springframework.jms.config.JmsListenerEndpointRegistrar;
+
+/**
+ * Optional interface to be implemented by Spring managed bean willing
+ * to customize how JMS listener endpoints are configured. Typically
+ * used to defined the default {@link org.springframework.jms.config.JmsListenerContainerFactory
+ * JmsListenerContainerFactory} to use or for registering jms endpoints
+ * in a programmatic fashion as opposed to the declarative
+ * approach of using the @{@link JmsListener} annotation.
+ *
+ *
See @{@link EnableJms} for detailed usage examples.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see EnableJms
+ * @see JmsListenerEndpointRegistrar
+ */
+public interface JmsListenerConfigurer {
+
+ /**
+ * Callback allowing a {@link org.springframework.jms.config.JmsListenerEndpointRegistry
+ * JmsListenerEndpointRegistry} and specific {@link org.springframework.jms.config.AbstractJmsListenerEndpoint
+ * JmsListenerEndpoint} instances to be registered against the given
+ * {@link JmsListenerEndpointRegistrar}. The default
+ * {@link org.springframework.jms.config.JmsListenerContainerFactory JmsListenerContainerFactory}
+ * can also be customized.
+ * @param registrar the registrar to be configured
+ */
+ void configureJmsListeners(JmsListenerEndpointRegistrar registrar);
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/package-info.java b/spring-jms/src/main/java/org/springframework/jms/annotation/package-info.java
new file mode 100644
index 0000000000..6226e5cad8
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/annotation/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Annotations and supporting classes for declarative jms listener
+ * endpoint
+ */
+package org.springframework.jms.annotation;
\ No newline at end of file
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/AbstractJmsListenerContainerFactory.java b/spring-jms/src/main/java/org/springframework/jms/config/AbstractJmsListenerContainerFactory.java
new file mode 100644
index 0000000000..f5344afe84
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/AbstractJmsListenerContainerFactory.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+
+import javax.jms.ConnectionFactory;
+
+import org.springframework.jms.listener.AbstractMessageListenerContainer;
+import org.springframework.jms.support.converter.MessageConverter;
+import org.springframework.jms.support.destination.DestinationResolver;
+import org.springframework.util.ErrorHandler;
+
+/**
+ * Base {@link JmsListenerContainerFactory} for Spring's base container implementation.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see AbstractMessageListenerContainer
+ */
+public abstract class AbstractJmsListenerContainerFactory
+ implements JmsListenerContainerFactory {
+
+ private ConnectionFactory connectionFactory;
+
+ private DestinationResolver destinationResolver;
+
+ private ErrorHandler errorHandler;
+
+ private MessageConverter messageConverter;
+
+ private Boolean sessionTransacted;
+
+ private Integer sessionAcknowledgeMode;
+
+ private Boolean pubSubDomain;
+
+ private Boolean subscriptionDurable;
+
+ private String clientId;
+
+ /**
+ * @see AbstractMessageListenerContainer#setConnectionFactory(ConnectionFactory)
+ */
+ public void setConnectionFactory(ConnectionFactory connectionFactory) {
+ this.connectionFactory = connectionFactory;
+ }
+
+ /**
+ * @see AbstractMessageListenerContainer#setDestinationResolver(DestinationResolver)
+ */
+ public void setDestinationResolver(DestinationResolver destinationResolver) {
+ this.destinationResolver = destinationResolver;
+ }
+
+ /**
+ * @see AbstractMessageListenerContainer#setErrorHandler(ErrorHandler)
+ */
+ public void setErrorHandler(ErrorHandler errorHandler) {
+ this.errorHandler = errorHandler;
+ }
+
+ /**
+ * @see AbstractMessageListenerContainer#setMessageConverter(MessageConverter)
+ */
+ public void setMessageConverter(MessageConverter messageConverter) {
+ this.messageConverter = messageConverter;
+ }
+
+ /**
+ * @see AbstractMessageListenerContainer#setSessionTransacted(boolean)
+ */
+ public void setSessionTransacted(Boolean sessionTransacted) {
+ this.sessionTransacted = sessionTransacted;
+ }
+
+ /**
+ * @see AbstractMessageListenerContainer#setSessionAcknowledgeMode(int)
+ */
+ public void setSessionAcknowledgeMode(Integer sessionAcknowledgeMode) {
+ this.sessionAcknowledgeMode = sessionAcknowledgeMode;
+ }
+
+ /**
+ * @see AbstractMessageListenerContainer#setPubSubDomain(boolean)
+ */
+ public void setPubSubDomain(Boolean pubSubDomain) {
+ this.pubSubDomain = pubSubDomain;
+ }
+
+ /**
+ * @see AbstractMessageListenerContainer#setSubscriptionDurable(boolean)
+ */
+ public void setSubscriptionDurable(Boolean subscriptionDurable) {
+ this.subscriptionDurable = subscriptionDurable;
+ }
+
+ /**
+ * @see AbstractMessageListenerContainer#setClientId(String)
+ */
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ /**
+ * Create an empty container instance.
+ */
+ protected abstract C createContainerInstance();
+
+ @Override
+ public C createMessageListenerContainer(JmsListenerEndpoint endpoint) {
+ C instance = createContainerInstance();
+
+ if (this.connectionFactory != null) {
+ instance.setConnectionFactory(this.connectionFactory);
+ }
+ if (this.destinationResolver != null) {
+ instance.setDestinationResolver(this.destinationResolver);
+ }
+ if (this.errorHandler != null) {
+ instance.setErrorHandler(this.errorHandler);
+ }
+ if (this.messageConverter != null) {
+ instance.setMessageConverter(this.messageConverter);
+ }
+
+ if (this.sessionTransacted != null) {
+ instance.setSessionTransacted(this.sessionTransacted);
+ }
+ if (this.sessionAcknowledgeMode != null) {
+ instance.setSessionAcknowledgeMode(this.sessionAcknowledgeMode);
+ }
+
+ if (this.pubSubDomain != null) {
+ instance.setPubSubDomain(this.pubSubDomain);
+ }
+ if (this.subscriptionDurable != null) {
+ instance.setSubscriptionDurable(this.subscriptionDurable);
+ }
+ if (this.clientId != null) {
+ instance.setClientId(this.clientId);
+ }
+
+ endpoint.setupMessageContainer(instance);
+
+ initializeContainer(instance);
+
+ return instance;
+ }
+
+ /**
+ * Further initialize the specified container.
+ *
Subclasses can inherit from this method to apply extra
+ * configuration if necessary.
+ */
+ protected void initializeContainer(C instance) {
+
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/AbstractJmsListenerEndpoint.java b/spring-jms/src/main/java/org/springframework/jms/config/AbstractJmsListenerEndpoint.java
new file mode 100644
index 0000000000..4f48158d5d
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/AbstractJmsListenerEndpoint.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import javax.jms.MessageListener;
+
+import org.springframework.jms.listener.AbstractMessageListenerContainer;
+import org.springframework.jms.listener.MessageListenerContainer;
+import org.springframework.jms.listener.endpoint.JmsActivationSpecConfig;
+import org.springframework.jms.listener.endpoint.JmsMessageEndpointManager;
+import org.springframework.util.Assert;
+
+/**
+ * Base model for a JMS listener endpoint
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see MethodJmsListenerEndpoint
+ * @see SimpleJmsListenerEndpoint
+ */
+public abstract class AbstractJmsListenerEndpoint implements JmsListenerEndpoint {
+
+ private String id;
+
+ private String destination;
+
+ private boolean queue = true;
+
+ private String subscription;
+
+ private String selector;
+
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Return the name of the destination for this endpoint.
+ */
+ public String getDestination() {
+ return destination;
+ }
+
+ /**
+ * Set the name of the destination for this endpoint.
+ */
+ public void setDestination(String destination) {
+ this.destination = destination;
+ }
+
+ /**
+ * Return {@code true} if the destination is a queue.
+ */
+ public boolean isQueue() {
+ return queue;
+ }
+
+ /**
+ * Specify if the destination is a queue.
+ */
+ public void setQueue(boolean queue) {
+ this.queue = queue;
+ }
+
+ /**
+ * Return the name for the durable subscription, if any.
+ */
+ public String getSubscription() {
+ return subscription;
+ }
+
+ /**
+ * Set the name for the durable subscription.
+ */
+ public void setSubscription(String subscription) {
+ this.subscription = subscription;
+ }
+
+ /**
+ * Return the JMS message selector expression, if any.
+ *
See the JMS specification for a detailed definition of selector expressions.
+ */
+ public String getSelector() {
+ return selector;
+ }
+
+ /**
+ * Set the JMS message selector expression.
+ */
+ public void setSelector(String selector) {
+ this.selector = selector;
+ }
+
+ @Override
+ public void setupMessageContainer(MessageListenerContainer container) {
+ if (container instanceof AbstractMessageListenerContainer) { // JMS
+ setupJmsMessageContainer((AbstractMessageListenerContainer) container);
+ }
+ else if (container instanceof JmsMessageEndpointManager) { // JCA
+ setupJcaMessageContainer((JmsMessageEndpointManager) container);
+ }
+ else {
+ throw new IllegalArgumentException("Could not configure endpoint with the specified container '"
+ + container + "' Only JMS (" + AbstractMessageListenerContainer.class.getName()
+ + " subclass) or JCA (" + JmsMessageEndpointManager.class.getName() + ") are supported.");
+ }
+ }
+
+ protected void setupJmsMessageContainer(AbstractMessageListenerContainer container) {
+ if (getDestination() != null) {
+ container.setDestinationName(getDestination());
+ }
+ if (getSubscription() != null) {
+ container.setDurableSubscriptionName(getSubscription());
+ }
+ if (getSelector() != null) {
+ container.setMessageSelector(getSelector());
+ }
+ container.setPubSubDomain(!isQueue());
+ setupMessageListener(container);
+ }
+
+ protected void setupJcaMessageContainer(JmsMessageEndpointManager container) {
+ JmsActivationSpecConfig activationSpecConfig = container.getActivationSpecConfig();
+ if (activationSpecConfig == null) {
+ activationSpecConfig = new JmsActivationSpecConfig();
+ container.setActivationSpecConfig(activationSpecConfig);
+ }
+
+ if (getDestination() != null) {
+ activationSpecConfig.setDestinationName(getDestination());
+ }
+ if (getSubscription() != null) {
+ activationSpecConfig.setDurableSubscriptionName(getSubscription());
+ }
+ if (getSelector() != null) {
+ activationSpecConfig.setMessageSelector(getSelector());
+ }
+ activationSpecConfig.setPubSubDomain(!isQueue());
+ setupMessageListener(container);
+ }
+
+ /**
+ * Create a {@link MessageListener} that is able to serve this endpoint for the
+ * specified container.
+ */
+ protected abstract MessageListener createMessageListener(MessageListenerContainer container);
+
+ private void setupMessageListener(MessageListenerContainer container) {
+ MessageListener messageListener = createMessageListener(container);
+ Assert.state(messageListener != null, "Endpoint [" + this + "] must provide a non null message listener");
+ container.setupMessageListener(messageListener);
+ }
+
+ /**
+ * Return a description for this endpoint.
+ *
Available to subclasses, for inclusion in their {@code toString()} result.
+ */
+ protected StringBuilder getEndpointDescription() {
+ StringBuilder result = new StringBuilder();
+ return result.append(getClass().getSimpleName())
+ .append("[")
+ .append(this.id)
+ .append("] destination=")
+ .append(this.destination)
+ .append(" | queue='")
+ .append(this.queue)
+ .append("' | subscription='")
+ .append(this.subscription)
+ .append(" | selector='")
+ .append(this.selector)
+ .append("'");
+ }
+
+ @Override
+ public String toString() {
+ return getEndpointDescription().toString();
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/AbstractListenerContainerParser.java b/spring-jms/src/main/java/org/springframework/jms/config/AbstractListenerContainerParser.java
index c60f7b6cc0..ccb23c68c4 100644
--- a/spring-jms/src/main/java/org/springframework/jms/config/AbstractListenerContainerParser.java
+++ b/spring-jms/src/main/java/org/springframework/jms/config/AbstractListenerContainerParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -22,6 +22,9 @@ import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.PropertyValue;
+import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
@@ -36,10 +39,13 @@ import org.springframework.util.StringUtils;
* common properties that are identical for all listener container variants.
*
* @author Juergen Hoeller
+ * @author Stephane Nicoll
* @since 2.5
*/
abstract class AbstractListenerContainerParser implements BeanDefinitionParser {
+ protected static final String FACTORY_ID_ATTRIBUTE = "factory-id";
+
protected static final String LISTENER_ELEMENT = "listener";
protected static final String ID_ATTRIBUTE = "id";
@@ -95,13 +101,21 @@ abstract class AbstractListenerContainerParser implements BeanDefinitionParser {
new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
+ // First parse the common configuration of the container
+ PropertyValues containerValues = parseProperties(element, parserContext);
+
+ // Expose the factory if requested
+ parseContainerFactory(element, parserContext, containerValues);
+
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
String localName = parserContext.getDelegate().getLocalName(child);
if (LISTENER_ELEMENT.equals(localName)) {
- parseListener((Element) child, element, parserContext);
+ ListenerContainerParserContext context = new ListenerContainerParserContext(
+ element, (Element) child, containerValues, parserContext);
+ parseListener(context);
}
}
}
@@ -110,10 +124,46 @@ abstract class AbstractListenerContainerParser implements BeanDefinitionParser {
return null;
}
- private void parseListener(Element listenerEle, Element containerEle, ParserContext parserContext) {
+ /**
+ * Parse the common properties for all listeners as defined by the specified
+ * container {@link Element}.
+ */
+ protected abstract PropertyValues parseProperties(Element containerEle, ParserContext parserContext);
+
+ /**
+ * Create the {@link BeanDefinition} for the container factory using the specified
+ * shared property values.
+ */
+ protected abstract RootBeanDefinition createContainerFactory(String factoryId,
+ Element containerElement, PropertyValues propertyValues);
+
+ /**
+ * Create the container {@link BeanDefinition} for the specified context.
+ */
+ protected abstract BeanDefinition createContainer(ListenerContainerParserContext context);
+
+
+ private void parseContainerFactory(Element element, ParserContext parserContext, PropertyValues propertyValues) {
+ String factoryId = element.getAttribute(FACTORY_ID_ATTRIBUTE);
+ if (StringUtils.hasText(factoryId)) {
+ RootBeanDefinition beanDefinition = createContainerFactory(factoryId, element, propertyValues);
+ if (beanDefinition != null) {
+ beanDefinition.setSource(parserContext.extractSource(element));
+ parserContext.registerBeanComponent(new BeanComponentDefinition(beanDefinition, factoryId));
+ }
+ }
+ }
+
+ private void parseListener(ListenerContainerParserContext context) {
+ ParserContext parserContext = context.getParserContext();
+ PropertyValues containerValues = context.getContainerValues();
+ Element listenerEle = context.getListenerElement();
+
RootBeanDefinition listenerDef = new RootBeanDefinition();
listenerDef.setSource(parserContext.extractSource(listenerEle));
+ BeanDefinition containerDef = createContainer(context);
+
String ref = listenerEle.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(ref)) {
parserContext.getReaderContext().error(
@@ -133,23 +183,15 @@ abstract class AbstractListenerContainerParser implements BeanDefinitionParser {
}
listenerDef.getPropertyValues().add("defaultListenerMethod", method);
- if (containerEle.hasAttribute(MESSAGE_CONVERTER_ATTRIBUTE)) {
- String messageConverter = containerEle.getAttribute(MESSAGE_CONVERTER_ATTRIBUTE);
- if (!StringUtils.hasText(messageConverter)) {
- parserContext.getReaderContext().error(
- "Listener container 'message-converter' attribute contains empty value.", containerEle);
- }
- else {
- listenerDef.getPropertyValues().add("messageConverter",
- new RuntimeBeanReference(messageConverter));
- }
+ PropertyValue messageConverterPv = getMessageConverter(containerValues);
+ if (messageConverterPv != null) {
+ listenerDef.getPropertyValues().addPropertyValue(messageConverterPv);
}
- BeanDefinition containerDef = parseContainer(listenerEle, containerEle, parserContext);
if (listenerEle.hasAttribute(RESPONSE_DESTINATION_ATTRIBUTE)) {
String responseDestination = listenerEle.getAttribute(RESPONSE_DESTINATION_ATTRIBUTE);
- boolean pubSubDomain = indicatesPubSub(containerDef);
+ boolean pubSubDomain = indicatesPubSub(containerValues);
listenerDef.getPropertyValues().add(
pubSubDomain ? "defaultResponseTopicName" : "defaultResponseQueueName", responseDestination);
if (containerDef.getPropertyValues().contains("destinationResolver")) {
@@ -172,13 +214,14 @@ abstract class AbstractListenerContainerParser implements BeanDefinitionParser {
parserContext.registerBeanComponent(new BeanComponentDefinition(containerDef, containerBeanName));
}
- protected abstract BeanDefinition parseContainer(
- Element listenerEle, Element containerEle, ParserContext parserContext);
-
- protected boolean indicatesPubSub(BeanDefinition containerDef) {
+ protected boolean indicatesPubSub(PropertyValues propertyValues) {
return false;
}
+ protected PropertyValue getMessageConverter(PropertyValues containerValues) {
+ return containerValues.getPropertyValue("messageConverter");
+ }
+
protected void parseListenerConfiguration(Element ele, ParserContext parserContext, BeanDefinition configDef) {
String destination = ele.getAttribute(DESTINATION_ATTRIBUTE);
if (!StringUtils.hasText(destination)) {
@@ -206,7 +249,9 @@ abstract class AbstractListenerContainerParser implements BeanDefinitionParser {
}
}
- protected void parseContainerConfiguration(Element ele, ParserContext parserContext, BeanDefinition configDef) {
+ protected PropertyValues parseCommonContainerProperties(Element ele, ParserContext parserContext) {
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+
String destinationType = ele.getAttribute(DESTINATION_TYPE_ATTRIBUTE);
boolean pubSubDomain = false;
boolean subscriptionDurable = false;
@@ -224,8 +269,8 @@ abstract class AbstractListenerContainerParser implements BeanDefinitionParser {
parserContext.getReaderContext().error("Invalid listener container 'destination-type': " +
"only \"queue\", \"topic\" and \"durableTopic\" supported.", ele);
}
- configDef.getPropertyValues().add("pubSubDomain", pubSubDomain);
- configDef.getPropertyValues().add("subscriptionDurable", subscriptionDurable);
+ propertyValues.add("pubSubDomain", pubSubDomain);
+ propertyValues.add("subscriptionDurable", subscriptionDurable);
if (ele.hasAttribute(CLIENT_ID_ATTRIBUTE)) {
String clientId = ele.getAttribute(CLIENT_ID_ATTRIBUTE);
@@ -233,8 +278,9 @@ abstract class AbstractListenerContainerParser implements BeanDefinitionParser {
parserContext.getReaderContext().error(
"Listener 'client-id' attribute contains empty value.", ele);
}
- configDef.getPropertyValues().add("clientId", clientId);
+ propertyValues.add("clientId", clientId);
}
+ return propertyValues;
}
protected Integer parseAcknowledgeMode(Element ele, ParserContext parserContext) {
@@ -261,8 +307,45 @@ abstract class AbstractListenerContainerParser implements BeanDefinitionParser {
}
}
- protected boolean indicatesPubSubConfig(BeanDefinition configDef) {
- return (Boolean) configDef.getPropertyValues().getPropertyValue("pubSubDomain").getValue();
+ protected boolean indicatesPubSubConfig(PropertyValues configuration) {
+ return (Boolean) configuration.getPropertyValue("pubSubDomain").getValue();
+ }
+
+
+ protected static class ListenerContainerParserContext {
+ private final Element containerElement;
+ private final Element listenerElement;
+ private final PropertyValues containerValues;
+ private final ParserContext parserContext;
+
+ public ListenerContainerParserContext(Element containerElement, Element listenerElement,
+ PropertyValues containerValues, ParserContext parserContext) {
+ this.containerElement = containerElement;
+ this.listenerElement = listenerElement;
+ this.containerValues = containerValues;
+ this.parserContext = parserContext;
+ }
+
+ public Element getContainerElement() {
+ return containerElement;
+ }
+
+ public Element getListenerElement() {
+ return listenerElement;
+ }
+
+ public PropertyValues getContainerValues() {
+ return containerValues;
+ }
+
+ public ParserContext getParserContext() {
+ return parserContext;
+ }
+
+ public Object getSource() {
+ return parserContext.extractSource(containerElement);
+ }
+
}
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/AnnotationDrivenJmsBeanDefinitionParser.java b/spring-jms/src/main/java/org/springframework/jms/config/AnnotationDrivenJmsBeanDefinitionParser.java
new file mode 100644
index 0000000000..b4322abd7b
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/AnnotationDrivenJmsBeanDefinitionParser.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import org.w3c.dom.Element;
+
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinitionHolder;
+import org.springframework.beans.factory.parsing.BeanComponentDefinition;
+import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.context.annotation.AnnotationConfigUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Parser for the 'annotation-driven' element of the 'jms' namespace.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+final class AnnotationDrivenJmsBeanDefinitionParser implements BeanDefinitionParser {
+
+ @Override
+ public BeanDefinition parse(Element element, ParserContext parserContext) {
+ Object source = parserContext.extractSource(element);
+
+ // Register component for the surrounding element.
+ CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
+ parserContext.pushContainingComponent(compDefinition);
+
+ // Nest the concrete post-processor bean in the surrounding component.
+ BeanDefinitionRegistry registry = parserContext.getRegistry();
+
+ if (registry.containsBeanDefinition(AnnotationConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)) {
+ parserContext.getReaderContext().error(
+ "Only one JmsListenerAnnotationBeanPostProcessor may exist within the context.", source);
+ }
+ else {
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
+ "org.springframework.jms.annotation.JmsListenerAnnotationBeanPostProcessor");
+ builder.getRawBeanDefinition().setSource(source);
+ String endpointRegistry = element.getAttribute("registry");
+ if (StringUtils.hasText(endpointRegistry)) {
+ builder.addPropertyReference("endpointRegistry", endpointRegistry);
+ }
+ else {
+ registerDefaultEndpointRegistry(source, parserContext);
+ }
+ String defaultContainerFactory = element.getAttribute("default-container-factory");
+ if (StringUtils.hasText(defaultContainerFactory)) {
+ builder.addPropertyReference("defaultContainerFactory", defaultContainerFactory);
+ }
+ String handlerMethodFactory = element.getAttribute("handler-method-factory");
+ if (StringUtils.hasText(handlerMethodFactory)) {
+ builder.addPropertyReference("jmsHandlerMethodFactory", handlerMethodFactory);
+ }
+
+ registerInfrastructureBean(parserContext, builder,
+ AnnotationConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME);
+ }
+
+ // Finally register the composite component.
+ parserContext.popAndRegisterContainingComponent();
+
+ return null;
+ }
+
+ private static void registerDefaultEndpointRegistry(Object source, ParserContext parserContext) {
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
+ "org.springframework.jms.config.JmsListenerEndpointRegistry");
+ builder.getRawBeanDefinition().setSource(source);
+ builder.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE);
+ registerInfrastructureBean(parserContext, builder, AnnotationConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME);
+ }
+
+ private static void registerInfrastructureBean(
+ ParserContext parserContext, BeanDefinitionBuilder builder, String beanName) {
+
+ builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ parserContext.getRegistry().registerBeanDefinition(beanName, builder.getBeanDefinition());
+ BeanDefinitionHolder holder = new BeanDefinitionHolder(builder.getBeanDefinition(), beanName);
+ parserContext.registerComponent(new BeanComponentDefinition(holder));
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/DefaultJcaListenerContainerFactory.java b/spring-jms/src/main/java/org/springframework/jms/config/DefaultJcaListenerContainerFactory.java
new file mode 100644
index 0000000000..ec597c1d86
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/DefaultJcaListenerContainerFactory.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import javax.resource.spi.ResourceAdapter;
+
+import org.springframework.jms.listener.endpoint.JmsActivationSpecConfig;
+import org.springframework.jms.listener.endpoint.JmsActivationSpecFactory;
+import org.springframework.jms.listener.endpoint.JmsMessageEndpointManager;
+import org.springframework.jms.support.destination.DestinationResolver;
+
+/**
+ * A {@link JmsListenerContainerFactory} implementation to build a
+ * JCA {@link JmsMessageEndpointManager}.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public class DefaultJcaListenerContainerFactory implements JmsListenerContainerFactory {
+
+ private ResourceAdapter resourceAdapter;
+
+ private JmsActivationSpecFactory activationSpecFactory;
+
+ private DestinationResolver destinationResolver;
+
+ private Object transactionManager;
+
+ private JmsActivationSpecConfig activationSpecConfig;
+
+ /**
+ * @see JmsMessageEndpointManager#setResourceAdapter(ResourceAdapter)
+ */
+ public void setResourceAdapter(ResourceAdapter resourceAdapter) {
+ this.resourceAdapter = resourceAdapter;
+ }
+
+ /**
+ * @see JmsMessageEndpointManager#setActivationSpecFactory(JmsActivationSpecFactory)
+ */
+ public void setActivationSpecFactory(JmsActivationSpecFactory activationSpecFactory) {
+ this.activationSpecFactory = activationSpecFactory;
+ }
+
+ /**
+ * @see JmsMessageEndpointManager#setDestinationResolver(DestinationResolver)
+ */
+ public void setDestinationResolver(DestinationResolver destinationResolver) {
+ this.destinationResolver = destinationResolver;
+ }
+
+ /**
+ * @see JmsMessageEndpointManager#setTransactionManager(Object)
+ */
+ public void setTransactionManager(Object transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+
+ /**
+ * @see JmsMessageEndpointManager#setActivationSpecConfig(JmsActivationSpecConfig)
+ */
+ public void setActivationSpecConfig(JmsActivationSpecConfig activationSpecConfig) {
+ this.activationSpecConfig = activationSpecConfig;
+ }
+
+ /**
+ * Return the current {@link JmsActivationSpecConfig}.
+ */
+ public JmsActivationSpecConfig getActivationSpecConfig() {
+ return activationSpecConfig;
+ }
+
+ /**
+ * Create an empty container instance.
+ */
+ protected JmsMessageEndpointManager createContainerInstance() {
+ return new JmsMessageEndpointManager();
+ }
+
+ @Override
+ public JmsMessageEndpointManager createMessageListenerContainer(JmsListenerEndpoint endpoint) {
+ if (this.destinationResolver != null && this.activationSpecFactory != null) {
+ throw new IllegalStateException("Specify either 'activationSpecFactory' or " +
+ "'destinationResolver', not both. If you define a dedicated JmsActivationSpecFactory bean, " +
+ "specify the custom DestinationResolver there (if possible)");
+ }
+
+ JmsMessageEndpointManager instance = createContainerInstance();
+
+ if (this.resourceAdapter != null) {
+ instance.setResourceAdapter(this.resourceAdapter);
+ }
+ if (this.activationSpecFactory != null) {
+ instance.setActivationSpecFactory(this.activationSpecFactory);
+ }
+ if (this.destinationResolver != null) {
+ instance.setDestinationResolver(this.destinationResolver);
+ }
+ if (this.transactionManager != null) {
+ instance.setTransactionManager(this.transactionManager);
+ }
+ if (this.activationSpecConfig != null) {
+ instance.setActivationSpecConfig(this.activationSpecConfig);
+ }
+
+ endpoint.setupMessageContainer(instance);
+
+ return instance;
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/DefaultJmsHandlerMethodFactory.java b/spring-jms/src/main/java/org/springframework/jms/config/DefaultJmsHandlerMethodFactory.java
new file mode 100644
index 0000000000..5b4f165c5f
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/DefaultJmsHandlerMethodFactory.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.format.support.DefaultFormattingConversionService;
+import org.springframework.messaging.converter.GenericMessageConverter;
+import org.springframework.messaging.converter.MessageConverter;
+import org.springframework.messaging.handler.annotation.support.HeaderMethodArgumentResolver;
+import org.springframework.messaging.handler.annotation.support.HeadersMethodArgumentResolver;
+import org.springframework.messaging.handler.annotation.support.MessageMethodArgumentResolver;
+import org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver;
+import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
+import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite;
+import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.validation.Errors;
+import org.springframework.validation.Validator;
+
+/**
+ * The default {@link JmsHandlerMethodFactory} implementation creating an
+ * {@link InvocableHandlerMethod} with the necessary
+ * {@link HandlerMethodArgumentResolver} instances to detect and process
+ * all the use cases defined by {@link org.springframework.jms.annotation.JmsListener
+ * JmsListener}.
+ *
+ *
Extra method argument resolvers can be added to customize the method
+ * signature that can be handled.
+ *
+ *
By default, the validation process redirects to a no-op implementation, see
+ * {@link #setValidator(Validator)} to customize it. The {@link ConversionService}
+ * can be customized in a similar manner to tune how the message payload
+ * can be converted
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see #setCustomArgumentResolvers(java.util.List)
+ * @see #setValidator(Validator)
+ * @see #setConversionService(ConversionService)
+ */
+public class DefaultJmsHandlerMethodFactory
+ implements JmsHandlerMethodFactory, InitializingBean, ApplicationContextAware {
+
+ private ApplicationContext applicationContext;
+
+ private ConversionService conversionService = new DefaultFormattingConversionService();
+
+ private MessageConverter messageConverter;
+
+ private Validator validator = new NoOpValidator();
+
+ private List customArgumentResolvers
+ = new ArrayList();
+
+ private HandlerMethodArgumentResolverComposite argumentResolvers
+ = new HandlerMethodArgumentResolverComposite();
+
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+ /**
+ * Set the {@link ConversionService} to use to convert the original
+ * message payload or headers.
+ *
+ * @see HeaderMethodArgumentResolver
+ * @see GenericMessageConverter
+ */
+ public void setConversionService(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ protected ConversionService getConversionService() {
+ return conversionService;
+ }
+
+ /**
+ * Set the {@link MessageConverter} to use. By default a {@link GenericMessageConverter}
+ * is used.
+ *
+ * @see GenericMessageConverter
+ */
+ public void setMessageConverter(MessageConverter messageConverter) {
+ this.messageConverter = messageConverter;
+ }
+
+ protected MessageConverter getMessageConverter() {
+ return messageConverter;
+ }
+
+ /**
+ * Set the Validator instance used for validating @Payload arguments
+ * @see org.springframework.validation.annotation.Validated
+ * @see org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver
+ */
+ public void setValidator(Validator validator) {
+ this.validator = validator;
+ }
+
+ /**
+ * The configured Validator instance
+ */
+ public Validator getValidator() {
+ return validator;
+ }
+
+ /**
+ * Set the list of custom {@code HandlerMethodArgumentResolver}s that will be used
+ * after resolvers for supported argument type.
+ * @param customArgumentResolvers the list of resolvers; never {@code null}.
+ */
+ public void setCustomArgumentResolvers(List customArgumentResolvers) {
+ Assert.notNull(customArgumentResolvers, "The 'customArgumentResolvers' cannot be null.");
+ this.customArgumentResolvers = customArgumentResolvers;
+ }
+
+ public List getCustomArgumentResolvers() {
+ return customArgumentResolvers;
+ }
+
+ /**
+ * Configure the complete list of supported argument types effectively overriding
+ * the ones configured by default. This is an advanced option. For most use cases
+ * it should be sufficient to use {@link #setCustomArgumentResolvers(java.util.List)}.
+ */
+ public void setArgumentResolvers(List argumentResolvers) {
+ if (argumentResolvers == null) {
+ this.argumentResolvers.clear();
+ return;
+ }
+ this.argumentResolvers.addResolvers(argumentResolvers);
+ }
+
+ public List getArgumentResolvers() {
+ return this.argumentResolvers.getResolvers();
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ if (messageConverter == null) {
+ messageConverter = new GenericMessageConverter(getConversionService());
+ }
+ if (this.argumentResolvers.getResolvers().isEmpty()) {
+ this.argumentResolvers.addResolvers(initArgumentResolvers());
+ }
+ }
+
+ @Override
+ public InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method) {
+ InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(bean, method);
+ handlerMethod.setMessageMethodArgumentResolvers(argumentResolvers);
+ return handlerMethod;
+ }
+
+ protected List initArgumentResolvers() {
+ ConfigurableBeanFactory beanFactory =
+ (ClassUtils.isAssignableValue(ConfigurableApplicationContext.class, applicationContext)) ?
+ ((ConfigurableApplicationContext) applicationContext).getBeanFactory() : null;
+
+ List resolvers = new ArrayList();
+
+ // Annotation-based argument resolution
+ resolvers.add(new HeaderMethodArgumentResolver(getConversionService(), beanFactory));
+ resolvers.add(new HeadersMethodArgumentResolver());
+
+ // Type-based argument resolution
+ resolvers.add(new MessageMethodArgumentResolver());
+
+ resolvers.addAll(getCustomArgumentResolvers());
+ resolvers.add(new PayloadArgumentResolver(getMessageConverter(), getValidator()));
+
+ return resolvers;
+ }
+
+
+ private static final class NoOpValidator implements Validator {
+ @Override
+ public boolean supports(Class> clazz) {
+ return false;
+ }
+
+ @Override
+ public void validate(Object target, Errors errors) {
+ }
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/DefaultJmsListenerContainerFactory.java b/spring-jms/src/main/java/org/springframework/jms/config/DefaultJmsListenerContainerFactory.java
new file mode 100644
index 0000000000..d27af25992
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/DefaultJmsListenerContainerFactory.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import java.util.concurrent.Executor;
+
+import org.springframework.jms.listener.DefaultMessageListenerContainer;
+import org.springframework.transaction.PlatformTransactionManager;
+
+/**
+ * A {@link JmsListenerContainerFactory} implementation to build regular
+ * {@link DefaultMessageListenerContainer}.
+ *
+ *
This should be the default for most users and a good transition
+ * paths for those that are used to build such container definition
+ * manually.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public class DefaultJmsListenerContainerFactory
+ extends AbstractJmsListenerContainerFactory {
+
+ private Executor taskExecutor;
+
+ private PlatformTransactionManager transactionManager;
+
+ private Integer cacheLevel;
+
+ private String cacheLevelName;
+
+ private String concurrency;
+
+ private Integer maxMessagesPerTask;
+
+ private Long receiveTimeout;
+
+ private Long recoveryInterval;
+
+ /**
+ * @see DefaultMessageListenerContainer#setTaskExecutor(java.util.concurrent.Executor)
+ */
+ public void setTaskExecutor(Executor taskExecutor) {
+ this.taskExecutor = taskExecutor;
+ }
+
+ /**
+ * @see DefaultMessageListenerContainer#setTransactionManager(PlatformTransactionManager)
+ */
+ public void setTransactionManager(PlatformTransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+
+ /**
+ * @see DefaultMessageListenerContainer#setCacheLevel(int)
+ */
+ public void setCacheLevel(Integer cacheLevel) {
+ this.cacheLevel = cacheLevel;
+ }
+
+ /**
+ * @see DefaultMessageListenerContainer#setCacheLevelName(String)
+ */
+ public void setCacheLevelName(String cacheLevelName) {
+ this.cacheLevelName = cacheLevelName;
+ }
+
+ /**
+ * @see DefaultMessageListenerContainer#setConcurrency(String)
+ */
+ public void setConcurrency(String concurrency) {
+ this.concurrency = concurrency;
+ }
+
+ /**
+ * @see DefaultMessageListenerContainer#setMaxMessagesPerTask(int)
+ */
+ public void setMaxMessagesPerTask(Integer maxMessagesPerTask) {
+ this.maxMessagesPerTask = maxMessagesPerTask;
+ }
+
+ /**
+ * @see DefaultMessageListenerContainer#setReceiveTimeout(long)
+ */
+ public void setReceiveTimeout(Long receiveTimeout) {
+ this.receiveTimeout = receiveTimeout;
+ }
+
+ /**
+ * @see DefaultMessageListenerContainer#setRecoveryInterval(long)
+ */
+ public void setRecoveryInterval(Long recoveryInterval) {
+ this.recoveryInterval = recoveryInterval;
+ }
+
+ @Override
+ protected DefaultMessageListenerContainer createContainerInstance() {
+ return new DefaultMessageListenerContainer();
+ }
+
+ @Override
+ protected void initializeContainer(DefaultMessageListenerContainer container) {
+ if (this.taskExecutor != null) {
+ container.setTaskExecutor(this.taskExecutor);
+ }
+ if (this.transactionManager != null) {
+ container.setTransactionManager(this.transactionManager);
+ }
+
+ if (this.cacheLevel != null) {
+ container.setCacheLevel(this.cacheLevel);
+ }
+ else if (this.cacheLevelName != null) {
+ container.setCacheLevelName(this.cacheLevelName);
+ }
+
+ if (this.concurrency != null) {
+ container.setConcurrency(this.concurrency);
+ }
+ if (this.maxMessagesPerTask != null) {
+ container.setMaxMessagesPerTask(this.maxMessagesPerTask);
+ }
+ if (this.receiveTimeout != null) {
+ container.setReceiveTimeout(this.receiveTimeout);
+ }
+ if (this.recoveryInterval != null) {
+ container.setRecoveryInterval(this.recoveryInterval);
+ }
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java b/spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java
index 327c15b3e9..8bfff85455 100644
--- a/spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java
+++ b/spring-jms/src/main/java/org/springframework/jms/config/JcaListenerContainerParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -18,6 +18,9 @@ package org.springframework.jms.config;
import org.w3c.dom.Element;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.PropertyValue;
+import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -28,6 +31,7 @@ import org.springframework.util.StringUtils;
* Parser for the JMS {@code <jca-listener-container>} element.
*
* @author Juergen Hoeller
+ * @author Stephane Nicoll
* @since 2.5
*/
class JcaListenerContainerParser extends AbstractListenerContainerParser {
@@ -38,11 +42,68 @@ class JcaListenerContainerParser extends AbstractListenerContainerParser {
@Override
- protected BeanDefinition parseContainer(Element listenerEle, Element containerEle, ParserContext parserContext) {
+ protected PropertyValues parseProperties(Element containerEle, ParserContext parserContext) {
+ final MutablePropertyValues properties = new MutablePropertyValues();
+ PropertyValues containerValues = parseContainerProperties(containerEle, parserContext);
+
+ // Common values are added to the activationSpecConfig
+ PropertyValues commonValues = parseCommonContainerProperties(containerEle, parserContext);
+ BeanDefinition beanDefinition = getActivationSpecConfigBeanDefinition(containerValues);
+ beanDefinition.getPropertyValues().addPropertyValues(commonValues);
+
+ properties.addPropertyValues(containerValues);
+ return properties;
+ }
+
+ @Override
+ protected RootBeanDefinition createContainerFactory(String factoryId,
+ Element containerElement, PropertyValues propertyValues) {
+ RootBeanDefinition beanDefinition = new RootBeanDefinition();
+ beanDefinition.setBeanClassName("org.springframework.jms.config.DefaultJcaListenerContainerFactory");
+ beanDefinition.getPropertyValues().addPropertyValues(propertyValues);
+ return beanDefinition;
+ }
+
+ @Override
+ protected BeanDefinition createContainer(ListenerContainerParserContext context) {
RootBeanDefinition containerDef = new RootBeanDefinition();
- containerDef.setSource(parserContext.extractSource(containerEle));
+ containerDef.setSource(context.getSource());
containerDef.setBeanClassName("org.springframework.jms.listener.endpoint.JmsMessageEndpointManager");
+ containerDef.getPropertyValues().addPropertyValues(context.getContainerValues());
+
+
+ BeanDefinition activationSpec = getActivationSpecConfigBeanDefinition(context.getContainerValues());
+ parseListenerConfiguration(context.getListenerElement(), context.getParserContext(), activationSpec);
+
+ String phase = context.getContainerElement().getAttribute(PHASE_ATTRIBUTE);
+ if (StringUtils.hasText(phase)) {
+ containerDef.getPropertyValues().add("phase", phase);
+ }
+
+ return containerDef;
+ }
+
+ @Override
+ protected boolean indicatesPubSub(PropertyValues propertyValues) {
+ BeanDefinition configDef = getActivationSpecConfigBeanDefinition(propertyValues);
+ return indicatesPubSubConfig(configDef.getPropertyValues());
+ }
+
+ @Override
+ protected PropertyValue getMessageConverter(PropertyValues containerValues) {
+ BeanDefinition configDef = getActivationSpecConfigBeanDefinition(containerValues);
+ return super.getMessageConverter(configDef.getPropertyValues());
+ }
+
+ private BeanDefinition getActivationSpecConfigBeanDefinition(PropertyValues containerValues) {
+ PropertyValue activationSpecConfig = containerValues.getPropertyValue("activationSpecConfig");
+ return (BeanDefinition) activationSpecConfig.getValue();
+ }
+
+ private PropertyValues parseContainerProperties(Element containerEle,
+ ParserContext parserContext) {
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
if (containerEle.hasAttribute(RESOURCE_ADAPTER_ATTRIBUTE)) {
String resourceAdapterBeanName = containerEle.getAttribute(RESOURCE_ADAPTER_ATTRIBUTE);
if (!StringUtils.hasText(resourceAdapterBeanName)) {
@@ -50,7 +111,7 @@ class JcaListenerContainerParser extends AbstractListenerContainerParser {
"Listener container 'resource-adapter' attribute contains empty value.", containerEle);
}
else {
- containerDef.getPropertyValues().add("resourceAdapter",
+ propertyValues.add("resourceAdapter",
new RuntimeBeanReference(resourceAdapterBeanName));
}
}
@@ -63,32 +124,29 @@ class JcaListenerContainerParser extends AbstractListenerContainerParser {
"'destination-resolver', not both. If you define a dedicated JmsActivationSpecFactory bean, " +
"specify the custom DestinationResolver there (if possible).", containerEle);
}
- containerDef.getPropertyValues().add("activationSpecFactory",
+ propertyValues.add("activationSpecFactory",
new RuntimeBeanReference(activationSpecFactoryBeanName));
}
if (StringUtils.hasText(destinationResolverBeanName)) {
- containerDef.getPropertyValues().add("destinationResolver",
+ propertyValues.add("destinationResolver",
new RuntimeBeanReference(destinationResolverBeanName));
}
+ String transactionManagerBeanName = containerEle.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE);
+ if (StringUtils.hasText(transactionManagerBeanName)) {
+ propertyValues.add("transactionManager",
+ new RuntimeBeanReference(transactionManagerBeanName));
+ }
+
RootBeanDefinition configDef = new RootBeanDefinition();
configDef.setSource(parserContext.extractSource(configDef));
configDef.setBeanClassName("org.springframework.jms.listener.endpoint.JmsActivationSpecConfig");
- parseListenerConfiguration(listenerEle, parserContext, configDef);
- parseContainerConfiguration(containerEle, parserContext, configDef);
Integer acknowledgeMode = parseAcknowledgeMode(containerEle, parserContext);
if (acknowledgeMode != null) {
configDef.getPropertyValues().add("acknowledgeMode", acknowledgeMode);
}
-
- String transactionManagerBeanName = containerEle.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE);
- if (StringUtils.hasText(transactionManagerBeanName)) {
- containerDef.getPropertyValues().add("transactionManager",
- new RuntimeBeanReference(transactionManagerBeanName));
- }
-
String concurrency = containerEle.getAttribute(CONCURRENCY_ATTRIBUTE);
if (StringUtils.hasText(concurrency)) {
configDef.getPropertyValues().add("concurrency", concurrency);
@@ -99,21 +157,21 @@ class JcaListenerContainerParser extends AbstractListenerContainerParser {
configDef.getPropertyValues().add("prefetchSize", new Integer(prefetch));
}
- String phase = containerEle.getAttribute(PHASE_ATTRIBUTE);
- if (StringUtils.hasText(phase)) {
- containerDef.getPropertyValues().add("phase", phase);
+ if (containerEle.hasAttribute(MESSAGE_CONVERTER_ATTRIBUTE)) {
+ String messageConverter = containerEle.getAttribute(MESSAGE_CONVERTER_ATTRIBUTE);
+ if (!StringUtils.hasText(messageConverter)) {
+ parserContext.getReaderContext().error(
+ "listener container 'message-converter' attribute contains empty value.", containerEle);
+ }
+ else {
+ configDef.getPropertyValues().add("messageConverter",
+ new RuntimeBeanReference(messageConverter));
+ }
}
- containerDef.getPropertyValues().add("activationSpecConfig", configDef);
+ propertyValues.add("activationSpecConfig", configDef);
- return containerDef;
- }
-
- @Override
- protected boolean indicatesPubSub(BeanDefinition containerDef) {
- BeanDefinition configDef =
- (BeanDefinition) containerDef.getPropertyValues().getPropertyValue("activationSpecConfig").getValue();
- return indicatesPubSubConfig(configDef);
+ return propertyValues;
}
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/JmsHandlerMethodFactory.java b/spring-jms/src/main/java/org/springframework/jms/config/JmsHandlerMethodFactory.java
new file mode 100644
index 0000000000..a53e147467
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/JmsHandlerMethodFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import java.lang.reflect.Method;
+
+import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
+
+/**
+ * A factory for {@link InvocableHandlerMethod} that is suitable to process
+ * an incoming JMS message.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public interface JmsHandlerMethodFactory {
+
+ /**
+ * Create the {@link InvocableHandlerMethod} that is able to process the specified
+ * JMS method endpoint.
+ * @param bean the bean instance
+ * @param method the method to invoke
+ * @return an JMS-specific {@link InvocableHandlerMethod} suitable for that method
+ */
+ InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method);
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerContainerFactory.java b/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerContainerFactory.java
new file mode 100644
index 0000000000..ed21f17f9a
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerContainerFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import org.springframework.jms.listener.MessageListenerContainer;
+
+/**
+ * Factory of {@link MessageListenerContainer} based on a
+ * {@link JmsListenerEndpoint} definition.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see JmsListenerEndpoint
+ */
+public interface JmsListenerContainerFactory {
+
+ /**
+ * Create a {@link MessageListenerContainer} for the given {@link JmsListenerEndpoint}.
+ * @param endpoint the endpoint to configure
+ * @return the created container
+ */
+ C createMessageListenerContainer(JmsListenerEndpoint endpoint);
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerContainerParser.java b/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerContainerParser.java
index f4439b05fc..0340bcfe7c 100644
--- a/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerContainerParser.java
+++ b/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerContainerParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -20,6 +20,8 @@ import javax.jms.Session;
import org.w3c.dom.Element;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -31,6 +33,7 @@ import org.springframework.util.StringUtils;
*
* @author Mark Fisher
* @author Juergen Hoeller
+ * @author Stephane Nicoll
* @since 2.5
*/
class JmsListenerContainerParser extends AbstractListenerContainerParser {
@@ -52,14 +55,44 @@ class JmsListenerContainerParser extends AbstractListenerContainerParser {
private static final String RECOVERY_INTERVAL_ATTRIBUTE = "recovery-interval";
+ protected PropertyValues parseProperties(Element containerEle, ParserContext parserContext) {
+ final MutablePropertyValues properties = new MutablePropertyValues();
+ PropertyValues commonValues = parseCommonContainerProperties(containerEle, parserContext);
+ PropertyValues containerValues = parseContainerProperties(containerEle,
+ parserContext, isSimpleContainer(containerEle));
+ properties.addPropertyValues(commonValues);
+ properties.addPropertyValues(containerValues);
+ return properties;
+ }
+
@Override
- protected BeanDefinition parseContainer(Element listenerEle, Element containerEle, ParserContext parserContext) {
+ protected RootBeanDefinition createContainerFactory(String factoryId, Element containerEle, PropertyValues propertyValues) {
+ RootBeanDefinition beanDefinition = new RootBeanDefinition();
+ String containerType = containerEle.getAttribute(CONTAINER_TYPE_ATTRIBUTE);
+ String containerClass = containerEle.getAttribute(CONTAINER_CLASS_ATTRIBUTE);
+ if (!"".equals(containerClass)) {
+ return null; // Not supported
+ }
+ else if ("".equals(containerType) || containerType.startsWith("default")) {
+ beanDefinition.setBeanClassName("org.springframework.jms.config.DefaultJmsListenerContainerFactory");
+ }
+ else if (containerType.startsWith("simple")) {
+ beanDefinition.setBeanClassName("org.springframework.jms.config.SimpleJmsListenerContainerFactory");
+ }
+ beanDefinition.getPropertyValues().addPropertyValues(propertyValues);
+ return beanDefinition;
+ }
+
+ @Override
+ protected BeanDefinition createContainer(ListenerContainerParserContext context) {
RootBeanDefinition containerDef = new RootBeanDefinition();
- containerDef.setSource(parserContext.extractSource(containerEle));
+ containerDef.setSource(context.getSource());
- parseListenerConfiguration(listenerEle, parserContext, containerDef);
- parseContainerConfiguration(containerEle, parserContext, containerDef);
+ // Set all container values
+ containerDef.getPropertyValues().addPropertyValues(context.getContainerValues());
+ parseListenerConfiguration(context.getListenerElement(), context.getParserContext(), containerDef);
+ Element containerEle = context.getContainerElement();
String containerType = containerEle.getAttribute(CONTAINER_TYPE_ATTRIBUTE);
String containerClass = containerEle.getAttribute(CONTAINER_CLASS_ATTRIBUTE);
if (!"".equals(containerClass)) {
@@ -72,103 +105,10 @@ class JmsListenerContainerParser extends AbstractListenerContainerParser {
containerDef.setBeanClassName("org.springframework.jms.listener.SimpleMessageListenerContainer");
}
else {
- parserContext.getReaderContext().error(
+ context.getParserContext().getReaderContext().error(
"Invalid 'container-type' attribute: only \"default\" and \"simple\" supported.", containerEle);
}
- String connectionFactoryBeanName = "connectionFactory";
- if (containerEle.hasAttribute(CONNECTION_FACTORY_ATTRIBUTE)) {
- connectionFactoryBeanName = containerEle.getAttribute(CONNECTION_FACTORY_ATTRIBUTE);
- if (!StringUtils.hasText(connectionFactoryBeanName)) {
- parserContext.getReaderContext().error(
- "Listener container 'connection-factory' attribute contains empty value.", containerEle);
- }
- }
- if (StringUtils.hasText(connectionFactoryBeanName)) {
- containerDef.getPropertyValues().add("connectionFactory",
- new RuntimeBeanReference(connectionFactoryBeanName));
- }
-
- String taskExecutorBeanName = containerEle.getAttribute(TASK_EXECUTOR_ATTRIBUTE);
- if (StringUtils.hasText(taskExecutorBeanName)) {
- containerDef.getPropertyValues().add("taskExecutor",
- new RuntimeBeanReference(taskExecutorBeanName));
- }
-
- String errorHandlerBeanName = containerEle.getAttribute(ERROR_HANDLER_ATTRIBUTE);
- if (StringUtils.hasText(errorHandlerBeanName)) {
- containerDef.getPropertyValues().add("errorHandler",
- new RuntimeBeanReference(errorHandlerBeanName));
- }
-
- String destinationResolverBeanName = containerEle.getAttribute(DESTINATION_RESOLVER_ATTRIBUTE);
- if (StringUtils.hasText(destinationResolverBeanName)) {
- containerDef.getPropertyValues().add("destinationResolver",
- new RuntimeBeanReference(destinationResolverBeanName));
- }
-
- String cache = containerEle.getAttribute(CACHE_ATTRIBUTE);
- if (StringUtils.hasText(cache)) {
- if (containerType.startsWith("simple")) {
- if (!("auto".equals(cache) || "consumer".equals(cache))) {
- parserContext.getReaderContext().warning(
- "'cache' attribute not actively supported for listener container of type \"simple\". " +
- "Effective runtime behavior will be equivalent to \"consumer\" / \"auto\".", containerEle);
- }
- }
- else {
- containerDef.getPropertyValues().add("cacheLevelName", "CACHE_" + cache.toUpperCase());
- }
- }
-
- Integer acknowledgeMode = parseAcknowledgeMode(containerEle, parserContext);
- if (acknowledgeMode != null) {
- if (acknowledgeMode == Session.SESSION_TRANSACTED) {
- containerDef.getPropertyValues().add("sessionTransacted", Boolean.TRUE);
- }
- else {
- containerDef.getPropertyValues().add("sessionAcknowledgeMode", acknowledgeMode);
- }
- }
-
- String transactionManagerBeanName = containerEle.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE);
- if (StringUtils.hasText(transactionManagerBeanName)) {
- if (containerType.startsWith("simple")) {
- parserContext.getReaderContext().error(
- "'transaction-manager' attribute not supported for listener container of type \"simple\".", containerEle);
- }
- else {
- containerDef.getPropertyValues().add("transactionManager",
- new RuntimeBeanReference(transactionManagerBeanName));
- }
- }
-
- String concurrency = containerEle.getAttribute(CONCURRENCY_ATTRIBUTE);
- if (StringUtils.hasText(concurrency)) {
- containerDef.getPropertyValues().add("concurrency", concurrency);
- }
-
- String prefetch = containerEle.getAttribute(PREFETCH_ATTRIBUTE);
- if (StringUtils.hasText(prefetch)) {
- if (containerType.startsWith("default")) {
- containerDef.getPropertyValues().add("maxMessagesPerTask", prefetch);
- }
- }
-
- String receiveTimeout = containerEle.getAttribute(RECEIVE_TIMEOUT_ATTRIBUTE);
- if (StringUtils.hasText(receiveTimeout)) {
- if (containerType.startsWith("default")) {
- containerDef.getPropertyValues().add("receiveTimeout", receiveTimeout);
- }
- }
-
- String recoveryInterval = containerEle.getAttribute(RECOVERY_INTERVAL_ATTRIBUTE);
- if (StringUtils.hasText(recoveryInterval)) {
- if (containerType.startsWith("default")) {
- containerDef.getPropertyValues().add("recoveryInterval", recoveryInterval);
- }
- }
-
String phase = containerEle.getAttribute(PHASE_ATTRIBUTE);
if (StringUtils.hasText(phase)) {
containerDef.getPropertyValues().add("phase", phase);
@@ -178,8 +118,124 @@ class JmsListenerContainerParser extends AbstractListenerContainerParser {
}
@Override
- protected boolean indicatesPubSub(BeanDefinition containerDef) {
- return indicatesPubSubConfig(containerDef);
+ protected boolean indicatesPubSub(PropertyValues propertyValues) {
+ return indicatesPubSubConfig(propertyValues);
+ }
+
+ private PropertyValues parseContainerProperties(Element containerEle,
+ ParserContext parserContext, boolean isSimpleContainer) {
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ String connectionFactoryBeanName = "connectionFactory";
+ if (containerEle.hasAttribute(CONNECTION_FACTORY_ATTRIBUTE)) {
+ connectionFactoryBeanName = containerEle.getAttribute(CONNECTION_FACTORY_ATTRIBUTE);
+ if (!StringUtils.hasText(connectionFactoryBeanName)) {
+ parserContext.getReaderContext().error(
+ "Listener container 'connection-factory' attribute contains empty value.", containerEle);
+ }
+ }
+ if (StringUtils.hasText(connectionFactoryBeanName)) {
+ propertyValues.add("connectionFactory",
+ new RuntimeBeanReference(connectionFactoryBeanName));
+ }
+
+ String taskExecutorBeanName = containerEle.getAttribute(TASK_EXECUTOR_ATTRIBUTE);
+ if (StringUtils.hasText(taskExecutorBeanName)) {
+ propertyValues.add("taskExecutor",
+ new RuntimeBeanReference(taskExecutorBeanName));
+ }
+
+ String errorHandlerBeanName = containerEle.getAttribute(ERROR_HANDLER_ATTRIBUTE);
+ if (StringUtils.hasText(errorHandlerBeanName)) {
+ propertyValues.add("errorHandler",
+ new RuntimeBeanReference(errorHandlerBeanName));
+ }
+
+ if (containerEle.hasAttribute(MESSAGE_CONVERTER_ATTRIBUTE)) {
+ String messageConverter = containerEle.getAttribute(MESSAGE_CONVERTER_ATTRIBUTE);
+ if (!StringUtils.hasText(messageConverter)) {
+ parserContext.getReaderContext().error(
+ "listener container 'message-converter' attribute contains empty value.", containerEle);
+ }
+ else {
+ propertyValues.add("messageConverter",
+ new RuntimeBeanReference(messageConverter));
+ }
+ }
+
+ String destinationResolverBeanName = containerEle.getAttribute(DESTINATION_RESOLVER_ATTRIBUTE);
+ if (StringUtils.hasText(destinationResolverBeanName)) {
+ propertyValues.add("destinationResolver",
+ new RuntimeBeanReference(destinationResolverBeanName));
+ }
+
+ String cache = containerEle.getAttribute(CACHE_ATTRIBUTE);
+ if (StringUtils.hasText(cache)) {
+ if (isSimpleContainer) {
+ if (!("auto".equals(cache) || "consumer".equals(cache))) {
+ parserContext.getReaderContext().warning(
+ "'cache' attribute not actively supported for listener container of type \"simple\". " +
+ "Effective runtime behavior will be equivalent to \"consumer\" / \"auto\".", containerEle);
+ }
+ }
+ else {
+ propertyValues.add("cacheLevelName", "CACHE_" + cache.toUpperCase());
+ }
+ }
+
+ Integer acknowledgeMode = parseAcknowledgeMode(containerEle, parserContext);
+ if (acknowledgeMode != null) {
+ if (acknowledgeMode == Session.SESSION_TRANSACTED) {
+ propertyValues.add("sessionTransacted", Boolean.TRUE);
+ }
+ else {
+ propertyValues.add("sessionAcknowledgeMode", acknowledgeMode);
+ }
+ }
+
+ String transactionManagerBeanName = containerEle.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE);
+ if (StringUtils.hasText(transactionManagerBeanName)) {
+ if (isSimpleContainer) {
+ parserContext.getReaderContext().error(
+ "'transaction-manager' attribute not supported for listener container of type \"simple\".", containerEle);
+ }
+ else {
+ propertyValues.add("transactionManager",
+ new RuntimeBeanReference(transactionManagerBeanName));
+ }
+ }
+
+ String concurrency = containerEle.getAttribute(CONCURRENCY_ATTRIBUTE);
+ if (StringUtils.hasText(concurrency)) {
+ propertyValues.add("concurrency", concurrency);
+ }
+
+ String prefetch = containerEle.getAttribute(PREFETCH_ATTRIBUTE);
+ if (StringUtils.hasText(prefetch)) {
+ if (!isSimpleContainer) {
+ propertyValues.add("maxMessagesPerTask", prefetch);
+ }
+ }
+
+ String receiveTimeout = containerEle.getAttribute(RECEIVE_TIMEOUT_ATTRIBUTE);
+ if (StringUtils.hasText(receiveTimeout)) {
+ if (!isSimpleContainer) {
+ propertyValues.add("receiveTimeout", receiveTimeout);
+ }
+ }
+
+ String recoveryInterval = containerEle.getAttribute(RECOVERY_INTERVAL_ATTRIBUTE);
+ if (StringUtils.hasText(recoveryInterval)) {
+ if (!isSimpleContainer) {
+ propertyValues.add("recoveryInterval", recoveryInterval);
+ }
+ }
+
+ return propertyValues;
+ }
+
+ private boolean isSimpleContainer(Element containerEle) {
+ String containerType = containerEle.getAttribute(CONTAINER_TYPE_ATTRIBUTE);
+ return containerType.startsWith("simple");
}
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerEndpoint.java b/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerEndpoint.java
new file mode 100644
index 0000000000..afbd67056f
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerEndpoint.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import org.springframework.jms.listener.MessageListenerContainer;
+
+/**
+ * Model for a JMS listener endpoint. Can be used against a
+ * {@link org.springframework.jms.annotation.JmsListenerConfigurer
+ * JmsListenerConfigurer} to register endpoints programmatically.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public interface JmsListenerEndpoint {
+
+ /**
+ * Return the id of this endpoint.
+ */
+ String getId();
+
+ /**
+ * Setup the specified message listener container with the model
+ * defined by this endpoint.
+ *
This endpoint must provide the requested missing option(s) of
+ * the specified container to make it usable. Usually, this is about
+ * setting the {@code destination} and the {@code messageListener} to
+ * use but an implementation may override any default setting that
+ * was already set.
+ * @param container the container to configure
+ */
+ void setupMessageContainer(MessageListenerContainer container);
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerEndpointRegistrar.java b/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerEndpointRegistrar.java
new file mode 100644
index 0000000000..e14ff41596
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerEndpointRegistrar.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+
+/**
+ * Helper bean for registering {@link JmsListenerEndpoint} with
+ * a {@link JmsListenerEndpointRegistry}.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see org.springframework.jms.annotation.JmsListenerConfigurer
+ */
+public class JmsListenerEndpointRegistrar implements InitializingBean {
+
+ private JmsListenerEndpointRegistry endpointRegistry;
+
+ private JmsListenerContainerFactory> defaultContainerFactory;
+
+ private JmsHandlerMethodFactory jmsHandlerMethodFactory;
+
+ private final List endpointDescriptors
+ = new ArrayList();
+
+ /**
+ * Set the {@link JmsListenerEndpointRegistry} instance to use.
+ */
+ public void setEndpointRegistry(JmsListenerEndpointRegistry endpointRegistry) {
+ this.endpointRegistry = endpointRegistry;
+ }
+
+ /**
+ * Return the {@link JmsListenerEndpointRegistry} instance for this
+ * registrar, may be {@code null}.
+ */
+ public JmsListenerEndpointRegistry getEndpointRegistry() {
+ return endpointRegistry;
+ }
+
+ /**
+ * Set the default {@link JmsListenerContainerFactory} to use in case a
+ * {@link JmsListenerEndpoint} is registered with a {@code null} container
+ * factory.
+ */
+ public void setDefaultContainerFactory(JmsListenerContainerFactory> defaultContainerFactory) {
+ this.defaultContainerFactory = defaultContainerFactory;
+ }
+
+ /**
+ * Return the {@link JmsListenerContainerFactory} to use if none has been
+ * defined for a particular endpoint or {@code null} if no default is set.
+ */
+ public JmsListenerContainerFactory> getDefaultContainerFactory() {
+ return defaultContainerFactory;
+ }
+
+ /**
+ * Set the {@link JmsHandlerMethodFactory} to use to configure the message
+ * listener responsible to serve an endpoint detected by this processor.
+ *
By default, {@link DefaultJmsHandlerMethodFactory} is used and it
+ * can be configured further to support additional method arguments
+ * or to customize conversion and validation support. See
+ * {@link DefaultJmsHandlerMethodFactory} javadoc for more details.
+ */
+ public void setJmsHandlerMethodFactory(JmsHandlerMethodFactory jmsHandlerMethodFactory) {
+ this.jmsHandlerMethodFactory = jmsHandlerMethodFactory;
+ }
+
+ /**
+ * Return the custom {@link JmsHandlerMethodFactory} to use, if any.
+ */
+ public JmsHandlerMethodFactory getJmsHandlerMethodFactory() {
+ return jmsHandlerMethodFactory;
+ }
+
+ /**
+ * Register a new {@link JmsListenerEndpoint} alongside the {@link JmsListenerContainerFactory}
+ * to use to create the underlying container.
+ *
The {@code factory} may be {@code null} if the default factory has to be used for that
+ * endpoint.
+ */
+ public void registerEndpoint(JmsListenerEndpoint endpoint, JmsListenerContainerFactory> factory) {
+ Assert.notNull(endpoint, "Endpoint must be set");
+ Assert.notNull(endpoint.getId(), "Endpoint id must be set");
+ // Factory may be null, we defer the resolution right before actually creating the container
+ this.endpointDescriptors.add(new JmsListenerEndpointDescriptor(endpoint, factory));
+ }
+
+ /**
+ * Register a new {@link JmsListenerEndpoint} using the default {@link JmsListenerContainerFactory}
+ * to create the underlying container.
+ *
+ * @see #setDefaultContainerFactory(JmsListenerContainerFactory)
+ * @see #registerEndpoint(JmsListenerEndpoint, JmsListenerContainerFactory)
+ */
+ public void registerEndpoint(JmsListenerEndpoint endpoint) {
+ registerEndpoint(endpoint, null);
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ startAllEndpoints();
+ }
+
+ protected void startAllEndpoints() throws Exception {
+ for (JmsListenerEndpointDescriptor descriptor : endpointDescriptors) {
+ endpointRegistry.createJmsListenerContainer(
+ descriptor.endpoint, resolveContainerFactory(descriptor));
+ }
+ }
+
+ private JmsListenerContainerFactory> resolveContainerFactory(JmsListenerEndpointDescriptor descriptor) {
+ if (descriptor.containerFactory != null) {
+ return descriptor.containerFactory;
+ }
+ else if (defaultContainerFactory != null) {
+ return defaultContainerFactory;
+ }
+ else {
+ throw new IllegalStateException("Could not resolve the "
+ + JmsListenerContainerFactory.class.getSimpleName() + " to use for ["
+ + descriptor.endpoint + "] no factory was given and no default is set.");
+ }
+ }
+
+
+ private static class JmsListenerEndpointDescriptor {
+ private final JmsListenerEndpoint endpoint;
+
+ private final JmsListenerContainerFactory> containerFactory;
+
+ private JmsListenerEndpointDescriptor(JmsListenerEndpoint endpoint,
+ JmsListenerContainerFactory> containerFactory) {
+ this.endpoint = endpoint;
+ this.containerFactory = containerFactory;
+ }
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerEndpointRegistry.java b/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerEndpointRegistry.java
new file mode 100644
index 0000000000..20e6026197
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/JmsListenerEndpointRegistry.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.beans.factory.BeanInitializationException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.jms.listener.MessageListenerContainer;
+import org.springframework.util.Assert;
+
+/**
+ * Create the necessary {@link MessageListenerContainer} instances
+ * for the registered {@linkplain JmsListenerEndpoint endpoints}. Also
+ * manage the lifecycle of the containers, in particular with the
+ * lifecycle of the application context.
+ *
+ *
Contrary to {@link MessageListenerContainer} created manually,
+ * containers managed by this instances are not registered in the
+ * application context and are not candidates for autowiring. Use
+ * {@link #getContainers()} if you need to access the containers
+ * of this instance for management purposes. If you need to access
+ * a particular container, use {@link #getContainer(String)} with the
+ * id of the endpoint.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see JmsListenerEndpoint
+ * @see MessageListenerContainer
+ * @see JmsListenerContainerFactory
+ */
+public class JmsListenerEndpointRegistry implements DisposableBean {
+
+ private final Map containers =
+ new HashMap();
+
+ /**
+ * Return the {@link MessageListenerContainer} with the specified id or
+ * {@code null} if no such container exists.
+ * @param id the id of the container
+ * @return the container or {@code null} if no container with that id exists
+ * @see JmsListenerEndpoint#getId()
+ */
+ public MessageListenerContainer getContainer(String id) {
+ Assert.notNull(id, "the container identifier must be set.");
+ return containers.get(id);
+ }
+
+ /**
+ * Return the managed {@link MessageListenerContainer} instance(s).
+ */
+ public Collection getContainers() {
+ return Collections.unmodifiableCollection(containers.values());
+ }
+
+ /**
+ * Create a message listener container for the given {@link JmsListenerEndpoint}.
+ *
This create the necessary infrastructure to honor that endpoint
+ * with regards to its configuration.
+ * @param endpoint the endpoint to add
+ * @see #getContainers()
+ * @see #getContainer(String)
+ */
+ public void createJmsListenerContainer(JmsListenerEndpoint endpoint, JmsListenerContainerFactory> factory) {
+ Assert.notNull(endpoint, "Endpoint must not be null");
+ Assert.notNull(factory, "Factory must not be null");
+
+ String id = endpoint.getId();
+ Assert.notNull(id, "Endpoint id must not be null");
+ Assert.state(!containers.containsKey(id), "another endpoint is already " +
+ "registered with id '" + id + "'");
+
+ MessageListenerContainer container = doCreateJmsListenerContainer(endpoint, factory);
+ containers.put(id, container);
+ }
+
+ /**
+ * Create and start a new container using the specified factory.
+ */
+ protected MessageListenerContainer doCreateJmsListenerContainer(JmsListenerEndpoint endpoint,
+ JmsListenerContainerFactory> factory) {
+ MessageListenerContainer container = factory.createMessageListenerContainer(endpoint);
+ initializeContainer(container);
+ return container;
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ for (MessageListenerContainer container : getContainers()) {
+ stopContainer(container);
+ }
+ }
+
+ protected void initializeContainer(MessageListenerContainer container) {
+ container.start();
+ if (container instanceof InitializingBean) {
+ try {
+ ((InitializingBean) container).afterPropertiesSet();
+ }
+ catch (Exception e) {
+ throw new BeanInitializationException("Could not start message listener container", e);
+ }
+ }
+ }
+
+ protected void stopContainer(MessageListenerContainer container) throws Exception {
+ container.stop();
+ if (container instanceof DisposableBean) {
+ ((DisposableBean) container).destroy();
+ }
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/JmsNamespaceHandler.java b/spring-jms/src/main/java/org/springframework/jms/config/JmsNamespaceHandler.java
index 7b78bd093b..f23ef6a56b 100644
--- a/spring-jms/src/main/java/org/springframework/jms/config/JmsNamespaceHandler.java
+++ b/spring-jms/src/main/java/org/springframework/jms/config/JmsNamespaceHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2014 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,7 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
*
* @author Mark Fisher
* @author Juergen Hoeller
+ * @author Stephane Nicoll
* @since 2.5
*/
public class JmsNamespaceHandler extends NamespaceHandlerSupport {
@@ -32,6 +33,7 @@ public class JmsNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("listener-container", new JmsListenerContainerParser());
registerBeanDefinitionParser("jca-listener-container", new JcaListenerContainerParser());
+ registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenJmsBeanDefinitionParser());
}
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java b/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java
new file mode 100644
index 0000000000..f8ff3e4173
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import java.lang.reflect.Method;
+
+import org.springframework.jms.listener.MessageListenerContainer;
+import org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter;
+import org.springframework.jms.support.converter.MessageConverter;
+import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
+import org.springframework.util.Assert;
+
+/**
+ * A {@link JmsListenerEndpoint} providing the method to invoke to process
+ * an incoming message for this endpoint.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint {
+
+ private Object bean;
+
+ private Method method;
+
+ private String responseDestination;
+
+ private JmsHandlerMethodFactory jmsHandlerMethodFactory;
+
+ /**
+ * Set the object instance that should manage this endpoint.
+ */
+ public void setBean(Object bean) {
+ this.bean = bean;
+ }
+
+ public Object getBean() {
+ return bean;
+ }
+
+ /**
+ * Set the method to invoke to process a message managed by this
+ * endpoint.
+ */
+ public void setMethod(Method method) {
+ this.method = method;
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+
+ /**
+ * Set the name of the default response destination to send response messages to.
+ */
+ public void setResponseDestination(String responseDestination) {
+ this.responseDestination = responseDestination;
+ }
+
+ /**
+ * Return the name of the default response destination to send response messages to.
+ */
+ public String getResponseDestination() {
+ return responseDestination;
+ }
+
+ /**
+ * Set the {@link DefaultJmsHandlerMethodFactory} to use to build the
+ * {@link InvocableHandlerMethod} responsible to manage the invocation
+ * of this endpoint.
+ */
+ public void setJmsHandlerMethodFactory(JmsHandlerMethodFactory jmsHandlerMethodFactory) {
+ this.jmsHandlerMethodFactory = jmsHandlerMethodFactory;
+ }
+
+ @Override
+ protected MessagingMessageListenerAdapter createMessageListener(MessageListenerContainer container) {
+ Assert.state(jmsHandlerMethodFactory != null,
+ "Could not create message listener, message listener factory not set.");
+ MessagingMessageListenerAdapter messageListener = new MessagingMessageListenerAdapter();
+ InvocableHandlerMethod invocableHandlerMethod =
+ jmsHandlerMethodFactory.createInvocableHandlerMethod(getBean(), getMethod());
+ messageListener.setHandlerMethod(invocableHandlerMethod);
+ String responseDestination = getResponseDestination();
+ if (responseDestination != null) {
+ if (isQueue()) {
+ messageListener.setDefaultResponseQueueName(responseDestination);
+ }
+ else {
+ messageListener.setDefaultResponseTopicName(responseDestination);
+ }
+ }
+ MessageConverter messageConverter = container.getMessageConverter();
+ if (messageConverter != null) {
+ messageListener.setMessageConverter(messageConverter);
+ }
+ return messageListener;
+ }
+
+ @Override
+ protected StringBuilder getEndpointDescription() {
+ return super.getEndpointDescription()
+ .append(" | bean='")
+ .append(this.bean)
+ .append("'")
+ .append(" | method='")
+ .append(this.method)
+ .append("'");
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/SimpleJmsListenerContainerFactory.java b/spring-jms/src/main/java/org/springframework/jms/config/SimpleJmsListenerContainerFactory.java
new file mode 100644
index 0000000000..32bd777006
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/SimpleJmsListenerContainerFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import org.springframework.jms.listener.SimpleMessageListenerContainer;
+
+/**
+ * A {@link JmsListenerContainerFactory} implementation to build
+ * a {@link SimpleMessageListenerContainer}.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public class SimpleJmsListenerContainerFactory
+ extends AbstractJmsListenerContainerFactory {
+
+ @Override
+ protected SimpleMessageListenerContainer createContainerInstance() {
+ return new SimpleMessageListenerContainer();
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/SimpleJmsListenerEndpoint.java b/spring-jms/src/main/java/org/springframework/jms/config/SimpleJmsListenerEndpoint.java
new file mode 100644
index 0000000000..2bc184065e
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/config/SimpleJmsListenerEndpoint.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.config;
+
+import javax.jms.MessageListener;
+
+import org.springframework.jms.listener.MessageListenerContainer;
+
+/**
+ * A {@link JmsListenerEndpoint} simply providing the {@link MessageListener} to
+ * invoke to process an incoming message for this endpoint.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public class SimpleJmsListenerEndpoint extends AbstractJmsListenerEndpoint {
+
+ private MessageListener messageListener;
+
+ /**
+ * Return the {@link MessageListener} to invoke when a message matching
+ * the endpoint is received.
+ */
+ public MessageListener getMessageListener() {
+ return messageListener;
+ }
+
+ public void setMessageListener(MessageListener messageListener) {
+ this.messageListener = messageListener;
+ }
+
+ @Override
+ protected MessageListener createMessageListener(MessageListenerContainer container) {
+ return getMessageListener();
+ }
+
+ @Override
+ protected StringBuilder getEndpointDescription() {
+ return super.getEndpointDescription()
+ .append(" | messageListener='")
+ .append(this.messageListener)
+ .append("'");
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/config/package-info.java b/spring-jms/src/main/java/org/springframework/jms/config/package-info.java
index 0c7ba80b05..4a8023ab98 100644
--- a/spring-jms/src/main/java/org/springframework/jms/config/package-info.java
+++ b/spring-jms/src/main/java/org/springframework/jms/config/package-info.java
@@ -1,9 +1,6 @@
-
/**
- *
* Support package for declarative messaging configuration,
- * with XML schema being the primary configuration format.
- *
+ * with Java configuration and XML schema support.
*/
package org.springframework.jms.config;
diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java b/spring-jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java
index 120660a985..2d6322a26f 100644
--- a/spring-jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java
+++ b/spring-jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -27,6 +27,7 @@ import javax.jms.Session;
import javax.jms.Topic;
import org.springframework.jms.support.JmsUtils;
+import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ErrorHandler;
@@ -114,6 +115,7 @@ import org.springframework.util.ErrorHandler;
* not just direct JMS Session usage in a {@link SessionAwareMessageListener}.
*
* @author Juergen Hoeller
+ * @author Stephane Nicoll
* @since 2.0
* @see #setMessageListener
* @see javax.jms.MessageListener
@@ -123,7 +125,8 @@ import org.springframework.util.ErrorHandler;
* @see SimpleMessageListenerContainer
* @see org.springframework.jms.listener.endpoint.JmsMessageEndpointManager
*/
-public abstract class AbstractMessageListenerContainer extends AbstractJmsListeningContainer {
+public abstract class AbstractMessageListenerContainer
+ extends AbstractJmsListeningContainer implements MessageListenerContainer {
private volatile Object destination;
@@ -139,6 +142,8 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
private ErrorHandler errorHandler;
+ private MessageConverter messageConverter;
+
private boolean exposeListenerSession = true;
private boolean acceptMessagesWhileStopping = false;
@@ -358,6 +363,19 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
this.errorHandler = errorHandler;
}
+ /**
+ * Set the {@link MessageConverter} strategy for converting JMS Messages.
+ * @param messageConverter the message converter to use
+ */
+ public void setMessageConverter(MessageConverter messageConverter) {
+ this.messageConverter = messageConverter;
+ }
+
+ @Override
+ public MessageConverter getMessageConverter() {
+ return messageConverter;
+ }
+
/**
* Set whether to expose the listener JMS Session to a registered
* {@link SessionAwareMessageListener} as well as to
@@ -420,6 +438,10 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
}
}
+ @Override
+ public void setupMessageListener(Object messageListener) {
+ setMessageListener(messageListener);
+ }
//-------------------------------------------------------------------------
// Template methods for listener execution
diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/MessageListenerContainer.java b/spring-jms/src/main/java/org/springframework/jms/listener/MessageListenerContainer.java
new file mode 100644
index 0000000000..bdae48d121
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/listener/MessageListenerContainer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.listener;
+
+import org.springframework.context.Lifecycle;
+import org.springframework.jms.support.converter.MessageConverter;
+
+/**
+ * Internal abstraction used by the framework representing a message
+ * listener container. Not meant to be implemented externally with
+ * support for both JMS and JCA style containers.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ */
+public interface MessageListenerContainer extends Lifecycle {
+
+ /**
+ * Setup the message listener to use. Throws an {@link IllegalArgumentException}
+ * if that message listener type is not supported.
+ */
+ void setupMessageListener(Object messageListener);
+
+ /**
+ * Return the {@link MessageConverter} that can be used to
+ * convert {@link javax.jms.Message}, if any.
+ */
+ MessageConverter getMessageConverter();
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java
new file mode 100644
index 0000000000..82faef5c3c
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/AbstractAdaptableMessageListener.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.listener.adapter;
+
+import javax.jms.Destination;
+import javax.jms.InvalidDestinationException;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.jms.listener.SessionAwareMessageListener;
+import org.springframework.jms.support.JmsUtils;
+import org.springframework.jms.support.converter.JmsHeaderMapper;
+import org.springframework.jms.support.converter.MessageConversionException;
+import org.springframework.jms.support.converter.MessageConverter;
+import org.springframework.jms.support.converter.SimpleJmsHeaderMapper;
+import org.springframework.jms.support.converter.SimpleMessageConverter;
+import org.springframework.jms.support.destination.DestinationResolver;
+import org.springframework.jms.support.destination.DynamicDestinationResolver;
+import org.springframework.util.Assert;
+
+/**
+ * An abstract {@link MessageListener} adapter providing the necessary infrastructure
+ * to extract the payload of a {@link Message}
+ *
+ * @author Juergen Hoeller
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see MessageListener
+ * @see SessionAwareMessageListener
+ */
+public abstract class AbstractAdaptableMessageListener
+ implements MessageListener, SessionAwareMessageListener {
+
+ /** Logger available to subclasses */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private Object defaultResponseDestination;
+
+ private DestinationResolver destinationResolver = new DynamicDestinationResolver();
+
+ private MessageConverter messageConverter;
+
+ private JmsHeaderMapper headerMapper = new SimpleJmsHeaderMapper();
+
+
+ /**
+ * Create a new instance with default settings.
+ */
+ protected AbstractAdaptableMessageListener() {
+ initDefaultStrategies();
+ }
+
+ /**
+ * Set the default destination to send response messages to. This will be applied
+ * in case of a request message that does not carry a "JMSReplyTo" field.
+ *
Response destinations are only relevant for listener methods that return
+ * result objects, which will be wrapped in a response message and sent to a
+ * response destination.
+ *
Alternatively, specify a "defaultResponseQueueName" or "defaultResponseTopicName",
+ * to be dynamically resolved via the DestinationResolver.
+ * @see #setDefaultResponseQueueName(String)
+ * @see #setDefaultResponseTopicName(String)
+ * @see #getResponseDestination
+ */
+ public void setDefaultResponseDestination(Destination destination) {
+ this.defaultResponseDestination = destination;
+ }
+
+ /**
+ * Set the name of the default response queue to send response messages to.
+ * This will be applied in case of a request message that does not carry a
+ * "JMSReplyTo" field.
+ *
Alternatively, specify a JMS Destination object as "defaultResponseDestination".
+ * @see #setDestinationResolver
+ * @see #setDefaultResponseDestination(javax.jms.Destination)
+ */
+ public void setDefaultResponseQueueName(String destinationName) {
+ this.defaultResponseDestination = new DestinationNameHolder(destinationName, false);
+ }
+
+ /**
+ * Set the name of the default response topic to send response messages to.
+ * This will be applied in case of a request message that does not carry a
+ * "JMSReplyTo" field.
+ *
Alternatively, specify a JMS Destination object as "defaultResponseDestination".
+ * @see #setDestinationResolver
+ * @see #setDefaultResponseDestination(javax.jms.Destination)
+ */
+ public void setDefaultResponseTopicName(String destinationName) {
+ this.defaultResponseDestination = new DestinationNameHolder(destinationName, true);
+ }
+
+ /**
+ * Set the DestinationResolver that should be used to resolve response
+ * destination names for this adapter.
+ *
The default resolver is a DynamicDestinationResolver. Specify a
+ * JndiDestinationResolver for resolving destination names as JNDI locations.
+ * @see org.springframework.jms.support.destination.DynamicDestinationResolver
+ * @see org.springframework.jms.support.destination.JndiDestinationResolver
+ */
+ public void setDestinationResolver(DestinationResolver destinationResolver) {
+ Assert.notNull(destinationResolver, "DestinationResolver must not be null");
+ this.destinationResolver = destinationResolver;
+ }
+
+ /**
+ * Return the DestinationResolver for this adapter.
+ */
+ protected DestinationResolver getDestinationResolver() {
+ return this.destinationResolver;
+ }
+
+ /**
+ * Set the converter that will convert incoming JMS messages to
+ * listener method arguments, and objects returned from listener
+ * methods back to JMS messages.
+ *
The default converter is a {@link SimpleMessageConverter}, which is able
+ * to handle {@link javax.jms.BytesMessage BytesMessages},
+ * {@link javax.jms.TextMessage TextMessages} and
+ * {@link javax.jms.ObjectMessage ObjectMessages}.
+ */
+ public void setMessageConverter(MessageConverter messageConverter) {
+ this.messageConverter = messageConverter;
+ }
+
+ /**
+ * Return the converter that will convert incoming JMS messages to
+ * listener method arguments, and objects returned from listener
+ * methods back to JMS messages.
+ */
+ protected MessageConverter getMessageConverter() {
+ return this.messageConverter;
+ }
+
+ /**
+ * Set the {@link JmsHeaderMapper} implementation to use to map the
+ * standard JMS headers. By default {@link SimpleJmsHeaderMapper} is
+ * used
+ * @see SimpleJmsHeaderMapper
+ */
+ public void setHeaderMapper(JmsHeaderMapper headerMapper) {
+ Assert.notNull(headerMapper, "HeaderMapper must not be null");
+ this.headerMapper = headerMapper;
+ }
+
+ /**
+ * Return the {@link JmsHeaderMapper} that converts headers from
+ * and to the messaging abstraction.
+ */
+ protected JmsHeaderMapper getHeaderMapper() {
+ return headerMapper;
+ }
+
+ /**
+ * Standard JMS {@link MessageListener} entry point.
+ *
Delegates the message to the target listener method, with appropriate
+ * conversion of the message argument. In case of an exception, the
+ * {@link #handleListenerException(Throwable)} method will be invoked.
+ *
Note: Does not support sending response messages based on
+ * result objects returned from listener methods. Use the
+ * {@link SessionAwareMessageListener} entry point (typically through a Spring
+ * message listener container) for handling result objects as well.
+ * @param message the incoming JMS message
+ * @see #handleListenerException
+ * @see #onMessage(javax.jms.Message, javax.jms.Session)
+ */
+ @Override
+ public void onMessage(Message message) {
+ try {
+ onMessage(message, null);
+ }
+ catch (Throwable ex) {
+ handleListenerException(ex);
+ }
+ }
+
+ /**
+ * Initialize the default implementations for the adapter's strategies.
+ * @see #setMessageConverter
+ * @see org.springframework.jms.support.converter.SimpleMessageConverter
+ */
+ protected void initDefaultStrategies() {
+ setMessageConverter(new SimpleMessageConverter());
+ }
+
+ /**
+ * Handle the given exception that arose during listener execution.
+ * The default implementation logs the exception at error level.
+ *
This method only applies when used as standard JMS {@link MessageListener}.
+ * In case of the Spring {@link SessionAwareMessageListener} mechanism,
+ * exceptions get handled by the caller instead.
+ * @param ex the exception to handle
+ * @see #onMessage(javax.jms.Message)
+ */
+ protected void handleListenerException(Throwable ex) {
+ logger.error("Listener execution failed", ex);
+ }
+
+ /**
+ * Extract the message body from the given JMS message.
+ * @param message the JMS {@code Message}
+ * @return the content of the message, to be passed into the
+ * listener method as argument
+ * @throws JMSException if thrown by JMS API methods
+ */
+ protected Object extractMessage(Message message) throws JMSException {
+ MessageConverter converter = getMessageConverter();
+ if (converter != null) {
+ return converter.fromMessage(message);
+ }
+ return message;
+ }
+
+ /**
+ * Handle the given result object returned from the listener method,
+ * sending a response message back.
+ * @param result the result object to handle (never {@code null})
+ * @param request the original request message
+ * @param session the JMS Session to operate on (may be {@code null})
+ * @throws JMSException if thrown by JMS API methods
+ * @see #buildMessage
+ * @see #postProcessResponse
+ * @see #getResponseDestination
+ * @see #sendResponse
+ */
+ protected void handleResult(Object result, Message request, Session session) throws JMSException {
+ if (session != null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Listener method returned result [" + result +
+ "] - generating response message for it");
+ }
+ Message response = buildMessage(session, result);
+ postProcessResponse(request, response);
+ Destination destination = getResponseDestination(request, response, session);
+ sendResponse(session, destination, response);
+ }
+ else {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Listener method returned result [" + result +
+ "]: not generating response message for it because of no JMS Session given");
+ }
+ }
+ }
+
+ /**
+ * Build a JMS message to be sent as response based on the given result object.
+ * @param session the JMS Session to operate on
+ * @param result the content of the message, as returned from the listener method
+ * @return the JMS {@code Message} (never {@code null})
+ * @throws JMSException if thrown by JMS API methods
+ * @see #setMessageConverter
+ */
+ protected Message buildMessage(Session session, Object result) throws JMSException {
+ MessageConverter converter = getMessageConverter();
+ if (converter != null) {
+ if (result instanceof org.springframework.messaging.Message) {
+ org.springframework.messaging.Message> message = (org.springframework.messaging.Message>) result;
+ Message reply = converter.toMessage(message.getPayload(), session);
+ getHeaderMapper().fromHeaders(message.getHeaders(), reply);
+ return reply;
+ }
+ else {
+ return converter.toMessage(result, session);
+ }
+ }
+ else {
+ if (!(result instanceof Message)) {
+ throw new MessageConversionException(
+ "No MessageConverter specified - cannot handle message [" + result + "]");
+ }
+ return (Message) result;
+ }
+ }
+
+ /**
+ * Post-process the given response message before it will be sent.
+ *
The default implementation sets the response's correlation id
+ * to the request message's correlation id, if any; otherwise to the
+ * request message id.
+ * @param request the original incoming JMS message
+ * @param response the outgoing JMS message about to be sent
+ * @throws JMSException if thrown by JMS API methods
+ * @see javax.jms.Message#setJMSCorrelationID
+ */
+ protected void postProcessResponse(Message request, Message response) throws JMSException {
+ String correlation = request.getJMSCorrelationID();
+ if (correlation == null) {
+ correlation = request.getJMSMessageID();
+ }
+ response.setJMSCorrelationID(correlation);
+ }
+
+ /**
+ * Determine a response destination for the given message.
+ *
The default implementation first checks the JMS Reply-To
+ * {@link Destination} of the supplied request; if that is not {@code null}
+ * it is returned; if it is {@code null}, then the configured
+ * {@link #resolveDefaultResponseDestination default response destination}
+ * is returned; if this too is {@code null}, then an
+ * {@link javax.jms.InvalidDestinationException} is thrown.
+ * @param request the original incoming JMS message
+ * @param response the outgoing JMS message about to be sent
+ * @param session the JMS Session to operate on
+ * @return the response destination (never {@code null})
+ * @throws JMSException if thrown by JMS API methods
+ * @throws javax.jms.InvalidDestinationException if no {@link Destination} can be determined
+ * @see #setDefaultResponseDestination
+ * @see javax.jms.Message#getJMSReplyTo()
+ */
+ protected Destination getResponseDestination(Message request, Message response, Session session)
+ throws JMSException {
+
+ Destination replyTo = request.getJMSReplyTo();
+ if (replyTo == null) {
+ replyTo = resolveDefaultResponseDestination(session);
+ if (replyTo == null) {
+ throw new InvalidDestinationException("Cannot determine response destination: " +
+ "Request message does not contain reply-to destination, and no default response destination set.");
+ }
+ }
+ return replyTo;
+ }
+
+ /**
+ * Resolve the default response destination into a JMS {@link Destination}, using this
+ * accessor's {@link DestinationResolver} in case of a destination name.
+ * @return the located {@link Destination}
+ * @throws javax.jms.JMSException if resolution failed
+ * @see #setDefaultResponseDestination
+ * @see #setDefaultResponseQueueName
+ * @see #setDefaultResponseTopicName
+ * @see #setDestinationResolver
+ */
+ protected Destination resolveDefaultResponseDestination(Session session) throws JMSException {
+ if (this.defaultResponseDestination instanceof Destination) {
+ return (Destination) this.defaultResponseDestination;
+ }
+ if (this.defaultResponseDestination instanceof DestinationNameHolder) {
+ DestinationNameHolder nameHolder = (DestinationNameHolder) this.defaultResponseDestination;
+ return getDestinationResolver().resolveDestinationName(session, nameHolder.name, nameHolder.isTopic);
+ }
+ return null;
+ }
+
+ /**
+ * Send the given response message to the given destination.
+ * @param response the JMS message to send
+ * @param destination the JMS destination to send to
+ * @param session the JMS session to operate on
+ * @throws JMSException if thrown by JMS API methods
+ * @see #postProcessProducer
+ * @see javax.jms.Session#createProducer
+ * @see javax.jms.MessageProducer#send
+ */
+ protected void sendResponse(Session session, Destination destination, Message response) throws JMSException {
+ MessageProducer producer = session.createProducer(destination);
+ try {
+ postProcessProducer(producer, response);
+ producer.send(response);
+ }
+ finally {
+ JmsUtils.closeMessageProducer(producer);
+ }
+ }
+
+ /**
+ * Post-process the given message producer before using it to send the response.
+ *
The default implementation is empty.
+ * @param producer the JMS message producer that will be used to send the message
+ * @param response the outgoing JMS message about to be sent
+ * @throws JMSException if thrown by JMS API methods
+ */
+ protected void postProcessProducer(MessageProducer producer, Message response) throws JMSException {
+ }
+
+
+ /**
+ * Internal class combining a destination name
+ * and its target destination type (queue or topic).
+ */
+ private static class DestinationNameHolder {
+
+ public final String name;
+
+ public final boolean isTopic;
+
+ public DestinationNameHolder(String name, boolean isTopic) {
+ this.name = name;
+ this.isTopic = isTopic;
+ }
+ }
+
+}
diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessageListenerAdapter.java b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessageListenerAdapter.java
index a35b56149b..d21974dd7b 100644
--- a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessageListenerAdapter.java
+++ b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessageListenerAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -17,25 +17,16 @@
package org.springframework.jms.listener.adapter;
import java.lang.reflect.InvocationTargetException;
-import javax.jms.Destination;
-import javax.jms.InvalidDestinationException;
+
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
-import javax.jms.MessageProducer;
import javax.jms.Session;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
import org.springframework.jms.listener.SessionAwareMessageListener;
import org.springframework.jms.listener.SubscriptionNameProvider;
-import org.springframework.jms.support.JmsUtils;
-import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.SimpleMessageConverter;
-import org.springframework.jms.support.destination.DestinationResolver;
-import org.springframework.jms.support.destination.DynamicDestinationResolver;
import org.springframework.util.Assert;
import org.springframework.util.MethodInvoker;
import org.springframework.util.ObjectUtils;
@@ -128,8 +119,8 @@ import org.springframework.util.ObjectUtils;
* @see org.springframework.jms.listener.SessionAwareMessageListener
* @see org.springframework.jms.listener.AbstractMessageListenerContainer#setMessageListener
*/
-public class MessageListenerAdapter
- implements MessageListener, SessionAwareMessageListener, SubscriptionNameProvider {
+public class MessageListenerAdapter extends AbstractAdaptableMessageListener
+ implements SubscriptionNameProvider {
/**
* Out-of-the-box value for the default listener method: "handleMessage".
@@ -137,19 +128,10 @@ public class MessageListenerAdapter
public static final String ORIGINAL_DEFAULT_LISTENER_METHOD = "handleMessage";
- /** Logger available to subclasses */
- protected final Log logger = LogFactory.getLog(getClass());
-
private Object delegate;
private String defaultListenerMethod = ORIGINAL_DEFAULT_LISTENER_METHOD;
- private Object defaultResponseDestination;
-
- private DestinationResolver destinationResolver = new DynamicDestinationResolver();
-
- private MessageConverter messageConverter;
-
/**
* Create a new {@link MessageListenerAdapter} with default settings.
@@ -205,112 +187,6 @@ public class MessageListenerAdapter
return this.defaultListenerMethod;
}
- /**
- * Set the default destination to send response messages to. This will be applied
- * in case of a request message that does not carry a "JMSReplyTo" field.
- *
Response destinations are only relevant for listener methods that return
- * result objects, which will be wrapped in a response message and sent to a
- * response destination.
- *
Alternatively, specify a "defaultResponseQueueName" or "defaultResponseTopicName",
- * to be dynamically resolved via the DestinationResolver.
- * @see #setDefaultResponseQueueName(String)
- * @see #setDefaultResponseTopicName(String)
- * @see #getResponseDestination
- */
- public void setDefaultResponseDestination(Destination destination) {
- this.defaultResponseDestination = destination;
- }
-
- /**
- * Set the name of the default response queue to send response messages to.
- * This will be applied in case of a request message that does not carry a
- * "JMSReplyTo" field.
- *
Alternatively, specify a JMS Destination object as "defaultResponseDestination".
- * @see #setDestinationResolver
- * @see #setDefaultResponseDestination(javax.jms.Destination)
- */
- public void setDefaultResponseQueueName(String destinationName) {
- this.defaultResponseDestination = new DestinationNameHolder(destinationName, false);
- }
-
- /**
- * Set the name of the default response topic to send response messages to.
- * This will be applied in case of a request message that does not carry a
- * "JMSReplyTo" field.
- *
Alternatively, specify a JMS Destination object as "defaultResponseDestination".
- * @see #setDestinationResolver
- * @see #setDefaultResponseDestination(javax.jms.Destination)
- */
- public void setDefaultResponseTopicName(String destinationName) {
- this.defaultResponseDestination = new DestinationNameHolder(destinationName, true);
- }
-
- /**
- * Set the DestinationResolver that should be used to resolve response
- * destination names for this adapter.
- *
The default resolver is a DynamicDestinationResolver. Specify a
- * JndiDestinationResolver for resolving destination names as JNDI locations.
- * @see org.springframework.jms.support.destination.DynamicDestinationResolver
- * @see org.springframework.jms.support.destination.JndiDestinationResolver
- */
- public void setDestinationResolver(DestinationResolver destinationResolver) {
- Assert.notNull(destinationResolver, "DestinationResolver must not be null");
- this.destinationResolver = destinationResolver;
- }
-
- /**
- * Return the DestinationResolver for this adapter.
- */
- protected DestinationResolver getDestinationResolver() {
- return this.destinationResolver;
- }
-
- /**
- * Set the converter that will convert incoming JMS messages to
- * listener method arguments, and objects returned from listener
- * methods back to JMS messages.
- *
The default converter is a {@link SimpleMessageConverter}, which is able
- * to handle {@link javax.jms.BytesMessage BytesMessages},
- * {@link javax.jms.TextMessage TextMessages} and
- * {@link javax.jms.ObjectMessage ObjectMessages}.
- */
- public void setMessageConverter(MessageConverter messageConverter) {
- this.messageConverter = messageConverter;
- }
-
- /**
- * Return the converter that will convert incoming JMS messages to
- * listener method arguments, and objects returned from listener
- * methods back to JMS messages.
- */
- protected MessageConverter getMessageConverter() {
- return this.messageConverter;
- }
-
-
- /**
- * Standard JMS {@link MessageListener} entry point.
- *
Delegates the message to the target listener method, with appropriate
- * conversion of the message argument. In case of an exception, the
- * {@link #handleListenerException(Throwable)} method will be invoked.
- *
Note: Does not support sending response messages based on
- * result objects returned from listener methods. Use the
- * {@link SessionAwareMessageListener} entry point (typically through a Spring
- * message listener container) for handling result objects as well.
- * @param message the incoming JMS message
- * @see #handleListenerException
- * @see #onMessage(javax.jms.Message, javax.jms.Session)
- */
- @Override
- public void onMessage(Message message) {
- try {
- onMessage(message, null);
- }
- catch (Throwable ex) {
- handleListenerException(ex);
- }
- }
-
/**
* Spring {@link SessionAwareMessageListener} entry point.
*
Delegates the message to the target listener method, with appropriate
@@ -374,44 +250,6 @@ public class MessageListenerAdapter
}
}
-
- /**
- * Initialize the default implementations for the adapter's strategies.
- * @see #setMessageConverter
- * @see org.springframework.jms.support.converter.SimpleMessageConverter
- */
- protected void initDefaultStrategies() {
- setMessageConverter(new SimpleMessageConverter());
- }
-
- /**
- * Handle the given exception that arose during listener execution.
- * The default implementation logs the exception at error level.
- *
This method only applies when used as standard JMS {@link MessageListener}.
- * In case of the Spring {@link SessionAwareMessageListener} mechanism,
- * exceptions get handled by the caller instead.
- * @param ex the exception to handle
- * @see #onMessage(javax.jms.Message)
- */
- protected void handleListenerException(Throwable ex) {
- logger.error("Listener execution failed", ex);
- }
-
- /**
- * Extract the message body from the given JMS message.
- * @param message the JMS {@code Message}
- * @return the content of the message, to be passed into the
- * listener method as argument
- * @throws JMSException if thrown by JMS API methods
- */
- protected Object extractMessage(Message message) throws JMSException {
- MessageConverter converter = getMessageConverter();
- if (converter != null) {
- return converter.fromMessage(message);
- }
- return message;
- }
-
/**
* Determine the name of the listener method that is supposed to
* handle the given message.
@@ -481,176 +319,4 @@ public class MessageListenerAdapter
}
}
-
- /**
- * Handle the given result object returned from the listener method,
- * sending a response message back.
- * @param result the result object to handle (never {@code null})
- * @param request the original request message
- * @param session the JMS Session to operate on (may be {@code null})
- * @throws JMSException if thrown by JMS API methods
- * @see #buildMessage
- * @see #postProcessResponse
- * @see #getResponseDestination
- * @see #sendResponse
- */
- protected void handleResult(Object result, Message request, Session session) throws JMSException {
- if (session != null) {
- if (logger.isDebugEnabled()) {
- logger.debug("Listener method returned result [" + result +
- "] - generating response message for it");
- }
- Message response = buildMessage(session, result);
- postProcessResponse(request, response);
- Destination destination = getResponseDestination(request, response, session);
- sendResponse(session, destination, response);
- }
- else {
- if (logger.isWarnEnabled()) {
- logger.warn("Listener method returned result [" + result +
- "]: not generating response message for it because of no JMS Session given");
- }
- }
- }
-
- /**
- * Build a JMS message to be sent as response based on the given result object.
- * @param session the JMS Session to operate on
- * @param result the content of the message, as returned from the listener method
- * @return the JMS {@code Message} (never {@code null})
- * @throws JMSException if thrown by JMS API methods
- * @see #setMessageConverter
- */
- protected Message buildMessage(Session session, Object result) throws JMSException {
- MessageConverter converter = getMessageConverter();
- if (converter != null) {
- return converter.toMessage(result, session);
- }
- else {
- if (!(result instanceof Message)) {
- throw new MessageConversionException(
- "No MessageConverter specified - cannot handle message [" + result + "]");
- }
- return (Message) result;
- }
- }
-
- /**
- * Post-process the given response message before it will be sent.
- *
The default implementation sets the response's correlation id
- * to the request message's correlation id, if any; otherwise to the
- * request message id.
- * @param request the original incoming JMS message
- * @param response the outgoing JMS message about to be sent
- * @throws JMSException if thrown by JMS API methods
- * @see javax.jms.Message#setJMSCorrelationID
- */
- protected void postProcessResponse(Message request, Message response) throws JMSException {
- String correlation = request.getJMSCorrelationID();
- if (correlation == null) {
- correlation = request.getJMSMessageID();
- }
- response.setJMSCorrelationID(correlation);
- }
-
- /**
- * Determine a response destination for the given message.
- *
The default implementation first checks the JMS Reply-To
- * {@link Destination} of the supplied request; if that is not {@code null}
- * it is returned; if it is {@code null}, then the configured
- * {@link #resolveDefaultResponseDestination default response destination}
- * is returned; if this too is {@code null}, then an
- * {@link InvalidDestinationException} is thrown.
- * @param request the original incoming JMS message
- * @param response the outgoing JMS message about to be sent
- * @param session the JMS Session to operate on
- * @return the response destination (never {@code null})
- * @throws JMSException if thrown by JMS API methods
- * @throws InvalidDestinationException if no {@link Destination} can be determined
- * @see #setDefaultResponseDestination
- * @see javax.jms.Message#getJMSReplyTo()
- */
- protected Destination getResponseDestination(Message request, Message response, Session session)
- throws JMSException {
-
- Destination replyTo = request.getJMSReplyTo();
- if (replyTo == null) {
- replyTo = resolveDefaultResponseDestination(session);
- if (replyTo == null) {
- throw new InvalidDestinationException("Cannot determine response destination: " +
- "Request message does not contain reply-to destination, and no default response destination set.");
- }
- }
- return replyTo;
- }
-
- /**
- * Resolve the default response destination into a JMS {@link Destination}, using this
- * accessor's {@link DestinationResolver} in case of a destination name.
- * @return the located {@link Destination}
- * @throws javax.jms.JMSException if resolution failed
- * @see #setDefaultResponseDestination
- * @see #setDefaultResponseQueueName
- * @see #setDefaultResponseTopicName
- * @see #setDestinationResolver
- */
- protected Destination resolveDefaultResponseDestination(Session session) throws JMSException {
- if (this.defaultResponseDestination instanceof Destination) {
- return (Destination) this.defaultResponseDestination;
- }
- if (this.defaultResponseDestination instanceof DestinationNameHolder) {
- DestinationNameHolder nameHolder = (DestinationNameHolder) this.defaultResponseDestination;
- return getDestinationResolver().resolveDestinationName(session, nameHolder.name, nameHolder.isTopic);
- }
- return null;
- }
-
- /**
- * Send the given response message to the given destination.
- * @param response the JMS message to send
- * @param destination the JMS destination to send to
- * @param session the JMS session to operate on
- * @throws JMSException if thrown by JMS API methods
- * @see #postProcessProducer
- * @see javax.jms.Session#createProducer
- * @see javax.jms.MessageProducer#send
- */
- protected void sendResponse(Session session, Destination destination, Message response) throws JMSException {
- MessageProducer producer = session.createProducer(destination);
- try {
- postProcessProducer(producer, response);
- producer.send(response);
- }
- finally {
- JmsUtils.closeMessageProducer(producer);
- }
- }
-
- /**
- * Post-process the given message producer before using it to send the response.
- *
The default implementation is empty.
- * @param producer the JMS message producer that will be used to send the message
- * @param response the outgoing JMS message about to be sent
- * @throws JMSException if thrown by JMS API methods
- */
- protected void postProcessProducer(MessageProducer producer, Message response) throws JMSException {
- }
-
-
- /**
- * Internal class combining a destination name
- * and its target destination type (queue or topic).
- */
- private static class DestinationNameHolder {
-
- public final String name;
-
- public final boolean isTopic;
-
- public DestinationNameHolder(String name, boolean isTopic) {
- this.name = name;
- this.isTopic = isTopic;
- }
- }
-
}
diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java
new file mode 100644
index 0000000000..f95a9ca14e
--- /dev/null
+++ b/spring-jms/src/main/java/org/springframework/jms/listener/adapter/MessagingMessageListenerAdapter.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2014 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.jms.listener.adapter;
+
+import java.util.Map;
+
+import javax.jms.JMSException;
+import javax.jms.Session;
+
+import org.springframework.jms.support.converter.JmsHeaderMapper;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessagingException;
+import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
+import org.springframework.messaging.support.MessageBuilder;
+
+/**
+ * A {@link javax.jms.MessageListener} adapter that invokes a configurable
+ * {@link InvocableHandlerMethod}.
+ *
+ *
Wraps the incoming {@link javax.jms.Message} to Spring's {@link Message}
+ * abstraction, copying the JMS standard headers using a configurable
+ * {@link JmsHeaderMapper}.
+ *
+ *
The original {@link javax.jms.Message} and the {@link javax.jms.Session}
+ * are provided as additional arguments so that these can be injected as
+ * method arguments if necessary.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see Message
+ * @see JmsHeaderMapper
+ * @see InvocableHandlerMethod
+ */
+public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageListener {
+
+ private InvocableHandlerMethod handlerMethod;
+
+
+ /**
+ * Set the {@link InvocableHandlerMethod} to use to invoke the method
+ * processing an incoming {@link javax.jms.Message}.
+ */
+ public void setHandlerMethod(InvocableHandlerMethod handlerMethod) {
+ this.handlerMethod = handlerMethod;
+ }
+
+ @Override
+ public void onMessage(javax.jms.Message jmsMessage, Session session) throws JMSException {
+ try {
+ Message> message = toMessagingMessage(jmsMessage);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Processing [" + message + "]");
+ }
+ Object result = handlerMethod.invoke(message, jmsMessage, session);
+ if (result != null) {
+ handleResult(result, jmsMessage, session);
+ }
+ else {
+ logger.trace("No result object given - no result to handle");
+ }
+ }
+ catch (MessagingException e) {
+ throw new ListenerExecutionFailedException(createMessagingErrorMessage("Listener method could not " +
+ "be invoked with the incoming message"), e);
+ }
+ catch (Exception e) {
+ throw new ListenerExecutionFailedException("Listener method '"
+ + handlerMethod.getMethod().toGenericString() + "' threw exception", e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected Message> toMessagingMessage(javax.jms.Message jmsMessage) throws JMSException {
+ Map mappedHeaders = getHeaderMapper().toHeaders(jmsMessage);
+ Object convertedObject = extractMessage(jmsMessage);
+ MessageBuilder