JMS annotation-driven endpoints.

This commit adds the support of JMS annotated endpoint. Can be
activated both by @EnableJms or <jms:annotation-driven/> and
detects methods of managed beans annotated with @JmsListener,
either directly or through a meta-annotation.

Containers are created and managed under the cover by a registry
at application startup time. Container creation is delegated to a
JmsListenerContainerFactory that is identified by the containerFactory
attribute of the JmsListener annotation. Containers can be
retrieved from the registry using a custom id that can be specified
directly on the annotation.

A "factory-id" attribute is available on the container element of
the XML namespace. When it is present, the configuration defined at
the namespace level is used to build a JmsListenerContainerFactory
that is exposed with the value of the "factory-id" attribute. This can
be used as a smooth migration path for users having listener containers
defined at the namespace level. It is also possible to migrate all
listeners to annotated endpoints and yet keep the
<jms:listener-container> or <jms:jca-listener-container> element to
share the container configuration.

The configuration can be fine-tuned by implementing the
JmsListenerConfigurer interface which gives access to the registrar
used to register endpoints. This includes a programmatic registration
of endpoints in complement to the declarative approach. A default
JmsListenerContainerFactory can also be specified to be used if no
containerFactory has been set on the annotation.

Annotated methods can have flexible method arguments that are similar
to what @MessageMapping provides. In particular, jms listener endpoint
methods can fully use the messaging abstraction, including convenient
header accessors. It is also possible to inject the raw
javax.jms.Message and the Session for more advanced use cases. The
payload can be injected as long as the conversion service is able to
convert it from the original type of the JMS payload. By
default, a DefaultJmsHandlerMethodFactory is used but it can be
configured further to support additional method arguments or to
customize conversion and validation support.

The return type of an annotated method can also be an instance of
Spring's Message abstraction. Instead of just converting the payload,
such response type allows to communicate standard and custom headers.

The JmsHeaderMapper infrastructure from Spring integration has also
been migrated to the Spring framework. SimpleJmsHeaderMapper is based
on SI's DefaultJmsHeaderMapper. The simple implementation maps all
JMS headers so that the generated Message abstraction has all the
information stored in the protocol specific message.

Issue: SPR-9882
This commit is contained in:
Stephane Nicoll
2014-03-12 17:43:14 +01:00
parent 6cb9a144db
commit 713dd60fa7
71 changed files with 7983 additions and 525 deletions

View File

@@ -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"))

View File

@@ -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.
*/

View File

@@ -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:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJms
* public class AppConfig {
* &#064;Bean
* public DefaultJmsListenerContainerFactory myJmsListenerContainerFactory() {
* DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
* factory.setConnectionFactory(connectionFactory());
* factory.setDestinationResolver(destinationResolver());
* factory.setConcurrency("5");
* return factory;
* }
* // other &#064;Bean definitions
* }</pre>
*
* 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}.
*
* <p>{@code @EnableJms} enables detection of @{@link JmsListener} annotations on
* any Spring-managed bean in the container. For example, given a class {@code MyService}
*
* <pre class="code">
* package com.acme.foo;
*
* public class MyService {
* &#064;JmsListener(containerFactory = "myJmsListenerContainerFactory", destination="myQueue")
* public void process(String msg) {
* // process incoming message
* }
* }</pre>
*
* The container factory to use is identified by the {@link JmsListener#containerFactory() containerFactory}
* attribute defining the name of the {@code JmsListenerContainerFactory} bean to use.
*
* <p>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:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJms
* public class AppConfig {
* &#064;Bean
* public MyService myService() {
* return new MyService();
* }
*
* // JMS infrastructure setup
* }</pre>
*
* 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:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJms
* &#064;ComponentScan(basePackages="com.acme.foo")
* public class AppConfig {
* }</pre>
*
* 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}.
*
* <p>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:
*
* <pre class="code">
* &#064;JmsListener(containerFactory = "myJmsListenerContainerFactory", destination="myQueue")
* public void process(String msg, @Header("myCounter") int counter) {
* // process incoming message
* }</pre>
*
* 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.
*
* <p>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 <em>default</em> container factory.
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJms
* public class AppConfig implements JmsListenerConfigurer {
* &#064;Override
* public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
* registrar.setDefaultContainerFactory(myJmsListenerContainerFactory());
* }
*
* &#064;Bean
* public JmsListenerContainerFactory<?> myJmsListenerContainerFactory() {
* // factory settings
* }
*
* &#064;Bean
* public MyService myService() {
* return new MyService();
* }
* }</pre>
*
* For reference, the example above can be compared to the following Spring XML
* configuration:
* <pre class="code">
* {@code <beans>
* <jms:annotation-driven default-container-factory="myJmsListenerContainerFactory"/>
*
* <bean id="myJmsListenerContainerFactory"
* class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
* // factory settings
* </bean>
*
* <bean id="myService" class="com.acme.foo.MyService"/>
* </beans>
* }</pre>
*
* 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}.
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJms
* public class AppConfig implements JmsListenerConfigurer {
* &#064;Override
* public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
* registrar.setEndpointRegistry(myJmsListenerEndpointRegistry());
* registrar.setJmsHandlerMethodFactory(myJmsHandlerMethodFactory);
* }
*
* &#064;Bean
* public JmsListenerEndpointRegistry<?> myJmsListenerEndpointRegistry() {
* // registry configuration
* }
*
* &#064;Bean
* public JmsHandlerMethodFactory myJmsHandlerMethodFactory() {
* DefaultJmsHandlerMethodFactory factory = new DefaultJmsHandlerMethodFactory();
* factory.setValidator(new MyValidator());
* return factory;
* }
*
* &#064;Bean
* public MyService myService() {
* return new MyService();
* }
* }</pre>
*
* For reference, the example above can be compared to the following Spring XML
* configuration:
* <pre class="code">
* {@code <beans>
* <jms:annotation-driven registry="myJmsListenerEndpointRegistry"
* handler-method-factory="myJmsHandlerMethodFactory"/&gt;
*
* <bean id="myJmsListenerEndpointRegistry"
* class="org.springframework.jms.config.JmsListenerEndpointRegistry">
* // registry configuration
* </bean>
*
* <bean id="myJmsHandlerMethodFactory"
* class="org.springframework.jms.config.DefaultJmsHandlerMethodFactory">
* <property name="validator" ref="myValidator"/>
* </bean>
*
* <bean id="myService" class="com.acme.foo.MyService"/>
* </beans>
* }</pre>
*
* Implementing {@code JmsListenerConfigurer} also allows for fine-grained
* control over endpoints registration via the {@code JmsListenerEndpointRegistrar}.
* For example, the following configures an extra endpoint:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJms
* public class AppConfig implements JmsListenerConfigurer {
* &#064;Override
* public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
* SimpleJmsListenerEndpoint myEndpoint = new SimpleJmsListenerEndpoint();
* // ... configure the endpoint
* registrar.registerEndpoint(endpoint, anotherJmsListenerContainerFactory());
* }
*
* &#064;Bean
* public MyService myService() {
* return new MyService();
* }
*
* &#064;Bean
* public JmsListenerContainerFactory<?> anotherJmsListenerContainerFactory() {
* // ...
* }
*
* // JMS infrastructure setup
* }</pre>
*
* 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 {
}

View File

@@ -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}.
*
* <p>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();
}
}

View File

@@ -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 <em>default</em> container factory has been defined.
*
* <p>Processing of {@code @JmsListener} annotations is performed by
* registering a {@link JmsListenerAnnotationBeanPostProcessor}. This can be
* done manually or, more conveniently, through the {@code <jms:annotation-driven/>}
* element or {@link EnableJms} annotation.
*
* <p>Annotated methods are allowed to have flexible signatures similar to what
* {@link MessageMapping} provides, that is
* <ul>
* <li>{@link javax.jms.Session} to get access to the JMS session</li>
* <li>{@link javax.jms.Message} or one if subclass to get access to the raw JMS message</li>
* <li>{@link org.springframework.messaging.Message} to use the messaging abstraction counterpart</li>
* <li>{@link org.springframework.messaging.handler.annotation.Payload @Payload}-annotated method
* arguments including the support of validation</li>
* <li>{@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}</li>
* <li>{@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.</li>
* <li>{@link org.springframework.messaging.MessageHeaders MessageHeaders} arguments for
* getting access to all headers.</li>
* <li>{@link org.springframework.messaging.support.MessageHeaderAccessor MessageHeaderAccessor}
* or {@link org.springframework.jms.support.JmsMessageHeaderAccessor JmsMessageHeaderAccessor}
* for convenient access to all method arguments.</li>
* </ul>
*
* <p>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.
* <p>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.
* <p>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
* <p>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.
* <p>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.
* <p>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 "";
}

View File

@@ -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.
*
* <p>Annotated methods can use flexible arguments as defined by @{@link JmsListener}.
*
* <p>This post-processor is automatically registered by Spring's
* {@code <jms:annotation-driven>} XML element, and also by the @{@link EnableJms}
* annotation.
*
* <p>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<ContextRefreshedEvent> {
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.
* <p>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<String, JmsListenerConfigurer> 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;
}
}
}

View File

@@ -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 <em>programmatic</em> fashion as opposed to the <em>declarative</em>
* approach of using the @{@link JmsListener} annotation.
*
* <p>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);
}

View File

@@ -0,0 +1,5 @@
/**
* Annotations and supporting classes for declarative jms listener
* endpoint
*/
package org.springframework.jms.annotation;

View File

@@ -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<C extends AbstractMessageListenerContainer>
implements JmsListenerContainerFactory<C> {
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.
* <p>Subclasses can inherit from this method to apply extra
* configuration if necessary.
*/
protected void initializeContainer(C instance) {
}
}

View File

@@ -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.
* <p>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.
* <p>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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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 <jms:annotation-driven> 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));
}
}

View File

@@ -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<JmsMessageEndpointManager> {
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;
}
}

View File

@@ -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}.
*
* <p>Extra method argument resolvers can be added to customize the method
* signature that can be handled.
*
* <p>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<HandlerMethodArgumentResolver> customArgumentResolvers
= new ArrayList<HandlerMethodArgumentResolver>();
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<HandlerMethodArgumentResolver> customArgumentResolvers) {
Assert.notNull(customArgumentResolvers, "The 'customArgumentResolvers' cannot be null.");
this.customArgumentResolvers = customArgumentResolvers;
}
public List<HandlerMethodArgumentResolver> 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<HandlerMethodArgumentResolver> argumentResolvers) {
if (argumentResolvers == null) {
this.argumentResolvers.clear();
return;
}
this.argumentResolvers.addResolvers(argumentResolvers);
}
public List<HandlerMethodArgumentResolver> 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<HandlerMethodArgumentResolver> initArgumentResolvers() {
ConfigurableBeanFactory beanFactory =
(ClassUtils.isAssignableValue(ConfigurableApplicationContext.class, applicationContext)) ?
((ConfigurableApplicationContext) applicationContext).getBeanFactory() : null;
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// 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) {
}
}
}

View File

@@ -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}.
*
* <p>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<DefaultMessageListenerContainer> {
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);
}
}
}

View File

@@ -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 &lt;jca-listener-container&gt;} 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;
}
}

View File

@@ -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);
}

View File

@@ -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<C extends MessageListenerContainer> {
/**
* Create a {@link MessageListenerContainer} for the given {@link JmsListenerEndpoint}.
* @param endpoint the endpoint to configure
* @return the created container
*/
C createMessageListenerContainer(JmsListenerEndpoint endpoint);
}

View File

@@ -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");
}
}

View File

@@ -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.
* <p>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);
}

View File

@@ -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<JmsListenerEndpointDescriptor> endpointDescriptors
= new ArrayList<JmsListenerEndpointDescriptor>();
/**
* 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.
* <p>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.
* <p>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;
}
}
}

View File

@@ -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.
*
* <p>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<String, MessageListenerContainer> containers =
new HashMap<String, MessageListenerContainer>();
/**
* 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<MessageListenerContainer> getContainers() {
return Collections.unmodifiableCollection(containers.values());
}
/**
* Create a message listener container for the given {@link JmsListenerEndpoint}.
* <p>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();
}
}
}

View File

@@ -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());
}
}

View File

@@ -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("'");
}
}

View File

@@ -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<SimpleMessageListenerContainer> {
@Override
protected SimpleMessageListenerContainer createContainerInstance() {
return new SimpleMessageListenerContainer();
}
}

View File

@@ -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("'");
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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<Message> {
/** 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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p><b>Note:</b> 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.
* <p>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.
* <p>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.
* <p>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.
* <p>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;
}
}
}

View File

@@ -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<Message>, 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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p><b>Note:</b> 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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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;
}
}
}

View File

@@ -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}.
*
* <p>Wraps the incoming {@link javax.jms.Message} to Spring's {@link Message}
* abstraction, copying the JMS standard headers using a configurable
* {@link JmsHeaderMapper}.
*
* <p>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<String, Object> mappedHeaders = getHeaderMapper().toHeaders(jmsMessage);
Object convertedObject = extractMessage(jmsMessage);
MessageBuilder<Object> builder = (convertedObject instanceof org.springframework.messaging.Message) ?
MessageBuilder.fromMessage((org.springframework.messaging.Message<Object>) convertedObject) :
MessageBuilder.withPayload(convertedObject);
return builder.copyHeadersIfAbsent(mappedHeaders).build();
}
private String createMessagingErrorMessage(String description) {
StringBuilder sb = new StringBuilder(description).append("\n")
.append("Endpoint handler details:\n")
.append("Method [").append(handlerMethod.getMethod()).append("]\n")
.append("Bean [").append(handlerMethod.getBean()).append("]\n");
return sb.toString();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 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.
@@ -19,6 +19,7 @@ package org.springframework.jms.listener.endpoint;
import javax.jms.Session;
import org.springframework.core.Constants;
import org.springframework.jms.support.converter.MessageConverter;
/**
* Common configuration object for activating a JMS message endpoint.
@@ -29,6 +30,7 @@ import org.springframework.core.Constants;
* but not tied to it.
*
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 2.5
* @see JmsActivationSpecFactory
* @see JmsMessageEndpointManager#setActivationSpecConfig
@@ -58,6 +60,8 @@ public class JmsActivationSpecConfig {
private int prefetchSize = -1;
private MessageConverter messageConverter;
public void setDestinationName(String destinationName) {
this.destinationName = destinationName;
@@ -201,4 +205,19 @@ public class JmsActivationSpecConfig {
return this.prefetchSize;
}
/**
* Set the {@link MessageConverter} strategy for converting JMS Messages.
* @param messageConverter the message converter to use
*/
public void setMessageConverter(MessageConverter messageConverter) {
this.messageConverter = messageConverter;
}
/**
* Return the {@link MessageConverter} to use, if any.
*/
public MessageConverter getMessageConverter() {
return messageConverter;
}
}

View File

@@ -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.
@@ -39,6 +39,7 @@ import org.springframework.jca.endpoint.AbstractMessageEndpointFactory;
* {@link javax.resource.spi.ResourceAdapter} instance.
*
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 2.5
* @see #setMessageListener
* @see #setTransactionManager
@@ -56,6 +57,13 @@ public class JmsMessageEndpointFactory extends AbstractMessageEndpointFactory {
this.messageListener = messageListener;
}
/**
* Return the JMS MessageListener for this endpoint.
*/
protected MessageListener getMessageListener() {
return messageListener;
}
/**
* Creates a concrete JMS message endpoint, internal to this factory.
*/

View File

@@ -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.
@@ -21,6 +21,8 @@ import javax.resource.ResourceException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.jca.endpoint.GenericMessageEndpointManager;
import org.springframework.jms.listener.MessageListenerContainer;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.destination.DestinationResolver;
/**
@@ -39,6 +41,7 @@ import org.springframework.jms.support.destination.DestinationResolver;
* for obtaining the current JMS {@link javax.jms.Session}.
*
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 2.5
* @see javax.jms.MessageListener
* @see #setActivationSpecConfig
@@ -46,7 +49,8 @@ import org.springframework.jms.support.destination.DestinationResolver;
* @see JmsActivationSpecFactory
* @see JmsMessageEndpointFactory
*/
public class JmsMessageEndpointManager extends GenericMessageEndpointManager implements BeanNameAware {
public class JmsMessageEndpointManager extends GenericMessageEndpointManager
implements BeanNameAware, MessageListenerContainer {
private final JmsMessageEndpointFactory endpointFactory = new JmsMessageEndpointFactory();
@@ -67,6 +71,13 @@ public class JmsMessageEndpointManager extends GenericMessageEndpointManager imp
this.messageListenerSet = true;
}
/**
* Return the JMS MessageListener for this endpoint.
*/
public MessageListener getMessageListener() {
return this.endpointFactory.getMessageListener();
}
/**
* Set the XA transaction manager to use for wrapping endpoint
* invocations, enlisting the endpoint resource in each such transaction.
@@ -128,6 +139,23 @@ public class JmsMessageEndpointManager extends GenericMessageEndpointManager imp
this.activationSpecConfig = activationSpecConfig;
}
/**
* Return the {@link JmsActivationSpecConfig} object that this endpoint manager
* should use for activating its listener. Return {@code null} if none is set.
*/
public JmsActivationSpecConfig getActivationSpecConfig() {
return activationSpecConfig;
}
@Override
public MessageConverter getMessageConverter() {
JmsActivationSpecConfig config = getActivationSpecConfig();
if (config != null) {
return config.getMessageConverter();
}
return null;
}
/**
* Set the name of this message endpoint. Populated with the bean name
* automatically when defined within Spring's bean factory.
@@ -137,6 +165,18 @@ public class JmsMessageEndpointManager extends GenericMessageEndpointManager imp
this.endpointFactory.setBeanName(beanName);
}
@Override
public void setupMessageListener(Object messageListener) {
if (messageListener instanceof MessageListener) {
setMessageListener((MessageListener) messageListener);
}
else {
throw new IllegalArgumentException("Unsupported message listener '"
+ messageListener.getClass().getName() + "': only '"
+ MessageListener.class.getName() + "' type is supported");
}
}
@Override
public void afterPropertiesSet() throws ResourceException {
if (this.messageListenerSet) {

View File

@@ -0,0 +1,99 @@
/*
* 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.support;
import java.util.List;
import java.util.Map;
import javax.jms.Destination;
import org.springframework.jms.support.converter.JmsHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.NativeMessageHeaderAccessor;
/**
* A {@link org.springframework.messaging.support.MessageHeaderAccessor}
* implementation giving access to JMS-specific headers.
*
* @author Stephane Nicoll
* @since 4.1
*/
public class JmsMessageHeaderAccessor extends NativeMessageHeaderAccessor {
protected JmsMessageHeaderAccessor(Map<String, List<String>> nativeHeaders) {
super(nativeHeaders);
}
protected JmsMessageHeaderAccessor(Message<?> message) {
super(message);
}
/**
* Create {@link JmsMessageHeaderAccessor} from the headers of an existing message.
*/
public static JmsMessageHeaderAccessor wrap(Message<?> message) {
return new JmsMessageHeaderAccessor(message);
}
@Override
public Object getReplyChannel() {
return getReplyTo();
}
public String getCorrelationId() {
return (String) getHeader(JmsHeaders.CORRELATION_ID);
}
public Destination getDestination() {
return (Destination) getHeader(JmsHeaders.DESTINATION);
}
public Integer getDeliveryMode() {
return (Integer) getHeader(JmsHeaders.DELIVERY_MODE);
}
public Long getExpiration() {
return (Long) getHeader(JmsHeaders.EXPIRATION);
}
public String getMessageId() {
return (String) getHeader(JmsHeaders.MESSAGE_ID);
}
public Integer getPriority() {
return (Integer) getHeader(JmsHeaders.PRIORITY);
}
public Destination getReplyTo() {
return (Destination) getHeader(JmsHeaders.REPLY_TO);
}
public Boolean getRedelivered() {
return (Boolean) getHeader(JmsHeaders.REDELIVERED);
}
public String getType() {
return (String) getHeader(JmsHeaders.TYPE);
}
public Long getTimestamp() {
return (Long) getHeader(JmsHeaders.TIMESTAMP);
}
}

View File

@@ -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.support.converter;
import javax.jms.Message;
import org.springframework.messaging.mapping.HeaderMapper;
/**
* Strategy interface for mapping messaging Message headers to an outbound
* JMS Message (e.g. to configure JMS properties) or extracting messaging
* header values from an inbound JMS Message.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @since 4.1
*/
public interface JmsHeaderMapper extends HeaderMapper<Message> {
static final String CONTENT_TYPE_PROPERTY = "content_type";
}

View File

@@ -0,0 +1,112 @@
/*
* 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.support.converter;
/**
* Pre-defined names and prefixes to be used for setting and/or retrieving JMS
* attributes from/to integration Message Headers.
*
* @author Mark Fisher
* @author Stephane Nicoll
* @since 4.1
*/
public interface JmsHeaders {
/**
* Prefix used for JMS API related headers in order to distinguish from
* user-defined headers and other internal headers (e.g. correlationId).
* @see SimpleJmsHeaderMapper
*/
public static final String PREFIX = "jms_";
/**
* Correlation ID for the message. This may be the {@link #MESSAGE_ID} of
* the message that this message replies to. It may also be an
* application-specific identifier.
* @see javax.jms.Message#getJMSCorrelationID()
*/
public static final String CORRELATION_ID = PREFIX + "correlationId";
/**
* Name of the destination (topic or queue) of the message.
* <p>Read only value.
* @see javax.jms.Message#getJMSDestination()
* @see javax.jms.Destination
* @see javax.jms.Queue
* @see javax.jms.Topic
*/
public static final String DESTINATION = PREFIX + "destination";
/**
* Distribution mode.
* <p>Read only value.
* @see javax.jms.Message#getJMSDeliveryMode()
* @see javax.jms.DeliveryMode
*/
public static final String DELIVERY_MODE = PREFIX + "deliveryMode";
/**
* Message expiration date and time.
* <p>Read only value.
* @see javax.jms.Message#getJMSExpiration()
*/
public static final String EXPIRATION = PREFIX + "expiration";
/**
* Unique Identifier for a message.
* <p>Read only value.
* @see javax.jms.Message#getJMSMessageID()
*/
public static final String MESSAGE_ID = PREFIX + "messageId";
/**
* The message priority level.
* <p>Read only value.
* @see javax.jms.Message#getJMSPriority()
*/
public static final String PRIORITY = PREFIX + "priority";
/**
* Name of the destination (topic or queue) the message replies should
* be sent to.
* @see javax.jms.Message#getJMSReplyTo()
*/
public static final String REPLY_TO = PREFIX + "replyTo";
/**
* Specify if the message was resent. This occurs when a message
* consumer fails to acknowledge the message reception.
* <p>Read only value.
* @see javax.jms.Message#getJMSRedelivered()
*/
public static final String REDELIVERED = PREFIX + "redelivered";
/**
* Message type label. This type is a string value describing the message
* in a functional manner.
* @see javax.jms.Message#getJMSType()
*/
public static final String TYPE = PREFIX + "type";
/**
* Date and time of the message sending operation.
* <p>Read only value.
* @see javax.jms.Message#getJMSTimestamp()
*/
public static final String TIMESTAMP = PREFIX + "timestamp";
}

View File

@@ -0,0 +1,300 @@
/*
* 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.support.converter;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jms.Destination;
import javax.jms.JMSException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.StringUtils;
/**
* Simple implementation of {@link JmsHeaderMapper}.
* <p>
* This implementation copies JMS API headers (e.g. JMSReplyTo) to and from
* {@link org.springframework.messaging.Message Messages}. Any user-defined
* properties will also be copied from a JMS Message to a Message, and any
* other headers on a Message (beyond the JMS API headers) will likewise
* be copied to a JMS Message. Those other headers will be copied to the
* general properties of a JMS Message whereas the JMS API headers are passed
* to the appropriate setter methods (e.g. setJMSReplyTo).
* <p>
* Constants for the JMS API headers are defined in {@link JmsHeaders}.
* Note that most of the JMS headers are read-only: the JMSDestination,
* JMSDeliveryMode, JMSExpiration, JMSMessageID, JMSPriority, JMSRedelivered
* and JMSTimestamp flags are only copied <em>from</em> a JMS Message. Those
* values will <em>not</em> be passed along from a Message to an outbound
* JMS Message.
*
* @author Mark Fisher
* @author Gary Russel
* @since 4.1
*/
public class SimpleJmsHeaderMapper implements JmsHeaderMapper {
private static List<Class<?>> SUPPORTED_PROPERTY_TYPES = Arrays.asList(new Class<?>[] {
Boolean.class, Byte.class, Double.class, Float.class, Integer.class, Long.class, Short.class, String.class});
private final Log logger = LogFactory.getLog(this.getClass());
private volatile String inboundPrefix = "";
private volatile String outboundPrefix = "";
/**
* Specify a prefix to be appended to the message header name for any
* JMS property that is being mapped into the MessageHeaders. The
* default is an empty string (no prefix).
* <p>
* This does not affect the JMS properties covered by the specification/API,
* such as JMSCorrelationID, etc. The header names used for mapping such
* properties are all defined in our {@link JmsHeaders}.
*
* @param inboundPrefix The inbound prefix.
*/
public void setInboundPrefix(String inboundPrefix) {
this.inboundPrefix = (inboundPrefix != null) ? inboundPrefix : "";
}
/**
* Specify a prefix to be appended to the JMS property name for any
* message header that is being mapped into the JMS Message. The
* default is an empty string (no prefix).
* <p>
* This does not affect the JMS properties covered by the specification/API,
* such as JMSCorrelationID, etc. The header names used for mapping such
* properties are all defined in our {@link JmsHeaders}.
*
* @param outboundPrefix The outbound prefix.
*/
public void setOutboundPrefix(String outboundPrefix) {
this.outboundPrefix = (outboundPrefix != null) ? outboundPrefix : "";
}
@Override
public void fromHeaders(MessageHeaders headers, javax.jms.Message jmsMessage) {
try {
Object jmsCorrelationId = headers.get(JmsHeaders.CORRELATION_ID);
if (jmsCorrelationId instanceof Number) {
jmsCorrelationId = jmsCorrelationId.toString();
}
if (jmsCorrelationId instanceof String) {
try {
jmsMessage.setJMSCorrelationID((String) jmsCorrelationId);
}
catch (Exception e) {
logger.info("failed to set JMSCorrelationID, skipping", e);
}
}
Object jmsReplyTo = headers.get(JmsHeaders.REPLY_TO);
if (jmsReplyTo instanceof Destination) {
try {
jmsMessage.setJMSReplyTo((Destination) jmsReplyTo);
}
catch (Exception e) {
logger.info("failed to set JMSReplyTo, skipping", e);
}
}
Object jmsType = headers.get(JmsHeaders.TYPE);
if (jmsType instanceof String) {
try {
jmsMessage.setJMSType((String) jmsType);
}
catch (Exception e) {
logger.info("failed to set JMSType, skipping", e);
}
}
Set<String> headerNames = headers.keySet();
for (String headerName : headerNames) {
if (StringUtils.hasText(headerName) && !headerName.startsWith(JmsHeaders.PREFIX)) {
Object value = headers.get(headerName);
if (value != null && SUPPORTED_PROPERTY_TYPES.contains(value.getClass())) {
try {
String propertyName = this.fromHeaderName(headerName);
jmsMessage.setObjectProperty(propertyName, value);
}
catch (Exception e) {
if (headerName.startsWith("JMSX")) {
if (logger.isTraceEnabled()) {
logger.trace("skipping reserved header, it cannot be set by client: " + headerName);
}
}
else if (logger.isWarnEnabled()) {
logger.warn("failed to map Message header '" + headerName + "' to JMS property", e);
}
}
}
}
}
}
catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("error occurred while mapping from MessageHeaders to JMS properties", e);
}
}
}
@Override
public Map<String, Object> toHeaders(javax.jms.Message jmsMessage) {
Map<String, Object> headers = new HashMap<String, Object>();
try {
try {
String correlationId = jmsMessage.getJMSCorrelationID();
if (correlationId != null) {
headers.put(JmsHeaders.CORRELATION_ID, correlationId);
}
}
catch (Exception e) {
logger.info("failed to read JMSCorrelationID property, skipping", e);
}
try {
Destination destination = jmsMessage.getJMSDestination();
if (destination != null) {
headers.put(JmsHeaders.DESTINATION, destination);
}
}
catch (Exception e) {
logger.info("failed to read JMSDestination property, skipping", e);
}
try {
int deliveryMode = jmsMessage.getJMSDeliveryMode();
headers.put(JmsHeaders.DELIVERY_MODE, deliveryMode);
}
catch (Exception e) {
logger.info("failed to read JMSDeliveryMode property, skipping", e);
}
try {
long expiration = jmsMessage.getJMSExpiration();
headers.put(JmsHeaders.EXPIRATION, expiration);
}
catch (Exception e) {
logger.info("failed to read JMSExpiration property, skipping", e);
}
try {
String messageId = jmsMessage.getJMSMessageID();
if (messageId != null) {
headers.put(JmsHeaders.MESSAGE_ID, messageId);
}
}
catch (Exception e) {
logger.info("failed to read JMSMessageID property, skipping", e);
}
try {
headers.put(JmsHeaders.PRIORITY, jmsMessage.getJMSPriority());
}
catch (Exception e) {
logger.info("failed to read JMSPriority property, skipping", e);
}
try {
Destination replyTo = jmsMessage.getJMSReplyTo();
if (replyTo != null) {
headers.put(JmsHeaders.REPLY_TO, replyTo);
}
}
catch (Exception e) {
logger.info("failed to read JMSReplyTo property, skipping", e);
}
try {
headers.put(JmsHeaders.REDELIVERED, jmsMessage.getJMSRedelivered());
}
catch (Exception e) {
logger.info("failed to read JMSRedelivered property, skipping", e);
}
try {
String type = jmsMessage.getJMSType();
if (type != null) {
headers.put(JmsHeaders.TYPE, type);
}
}
catch (Exception e) {
logger.info("failed to read JMSType property, skipping", e);
}
try {
headers.put(JmsHeaders.TIMESTAMP, jmsMessage.getJMSTimestamp());
}
catch (Exception e) {
logger.info("failed to read JMSTimestamp property, skipping", e);
}
Enumeration<?> jmsPropertyNames = jmsMessage.getPropertyNames();
if (jmsPropertyNames != null) {
while (jmsPropertyNames.hasMoreElements()) {
String propertyName = jmsPropertyNames.nextElement().toString();
try {
String headerName = this.toHeaderName(propertyName);
headers.put(headerName, jmsMessage.getObjectProperty(propertyName));
}
catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("error occurred while mapping JMS property '"
+ propertyName + "' to Message header", e);
}
}
}
}
}
catch (JMSException e) {
if (logger.isWarnEnabled()) {
logger.warn("error occurred while mapping from JMS properties to MessageHeaders", e);
}
}
return headers;
}
/**
* Add the outbound prefix if necessary.
* <p>Convert {@link MessageHeaders#CONTENT_TYPE} to content_type for JMS compliance.
*/
private String fromHeaderName(String headerName) {
String propertyName = headerName;
if (StringUtils.hasText(this.outboundPrefix) && !propertyName.startsWith(this.outboundPrefix)) {
propertyName = this.outboundPrefix + headerName;
}
else if (MessageHeaders.CONTENT_TYPE.equals(headerName)) {
propertyName = CONTENT_TYPE_PROPERTY;
}
return propertyName;
}
/**
* Add the inbound prefix if necessary.
* <p>Convert content_type to {@link MessageHeaders#CONTENT_TYPE}.
*/
private String toHeaderName(String propertyName) {
String headerName = propertyName;
if (StringUtils.hasText(this.inboundPrefix) && !headerName.startsWith(this.inboundPrefix)) {
headerName = this.inboundPrefix + propertyName;
}
else if (CONTENT_TYPE_PROPERTY.equals(propertyName)) {
headerName = MessageHeaders.CONTENT_TYPE;
}
return headerName;
}
}

View File

@@ -3,4 +3,5 @@ http\://www.springframework.org/schema/jms/spring-jms-3.0.xsd=org/springframewor
http\://www.springframework.org/schema/jms/spring-jms-3.1.xsd=org/springframework/jms/config/spring-jms-3.1.xsd
http\://www.springframework.org/schema/jms/spring-jms-3.2.xsd=org/springframework/jms/config/spring-jms-3.2.xsd
http\://www.springframework.org/schema/jms/spring-jms-4.0.xsd=org/springframework/jms/config/spring-jms-4.0.xsd
http\://www.springframework.org/schema/jms/spring-jms.xsd=org/springframework/jms/config/spring-jms-4.0.xsd
http\://www.springframework.org/schema/jms/spring-jms-4.1.xsd=org/springframework/jms/config/spring-jms-4.1.xsd
http\://www.springframework.org/schema/jms/spring-jms.xsd=org/springframework/jms/config/spring-jms-4.1.xsd

View File

@@ -0,0 +1,575 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/jms"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://www.springframework.org/schema/jms"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.1.xsd"/>
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the configuration elements for the Spring Framework's JMS support.
Allows for configuring JMS listener containers in XML 'shortcut' style as
well as through annotation.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="annotation-driven">
<xsd:annotation>
<xsd:documentation><![CDATA[
Enables the detection of @JmsListener annotation on any Spring-managed object. If
present, a message listener container will be created to receive the relevant
messages and invoke the annotated method accordingly.
See Javadoc for the org.springframework.jms.annotation.EnableJms annotation for
information on code-based alternatives to this XML element.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="registry" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the org.springframework.jms.config.JmsListenerEndpointRegistry instance to
use to register annotated jms listener endpoints. If not provided, a default instance
will be used by default.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jms.config.JmsListenerEndpointRegistry"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="default-container-factory" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the default org.springframework.jms.config.JmsListenerContainerFactory instance to
use to create the container for a jms listener endpoint that does not define a specific
factory. This permits in practice to omit the "containerFactory" attribute of the JmsListener
annotation. If not provided, each endpoint must define the container factory to use.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jms.config.JmsListenerContainerFactory"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="handler-method-factory" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies a custom org.springframework.jms.config.JmsHandlerMethodFactory instance to
use to configure the message listener responsible to serve an endpoint detected by this
processor. By default, DefaultJmsHandlerMethodFactory is used and it can be configured
further to support additional method arguments or to customize conversion and validation
support. See org.springframework.jms.config.DefaultJmsHandlerMethodFactory Javadoc for
more details.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jms.config.JmsHandlerMethodFactory"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="listener-container">
<xsd:annotation>
<xsd:documentation><![CDATA[
Each listener child element will be hosted by a container whose configuration
is determined by this parent element. This variant builds standard JMS
listener containers, operating against a specified JMS ConnectionFactory. When
a factory-id attribute is present, the configuration defined by this element is
exposed as a org.springframework.jms.config.JmsListenerContainerFactory. It is
therefore possible to only define this element without any child to just expose
a container factory.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.jms.listener.AbstractMessageListenerContainer"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="listener" type="listenerType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="factory-id" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Expose the settings defined by this element as a org.springframework.jms.config.JmsListenerContainerFactory
so that they can be reused with other endpoints.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="container-type" default="default">
<xsd:annotation>
<xsd:documentation><![CDATA[
The type of this listener container: "default" or "simple", choosing
between DefaultMessageListenerContainer and SimpleMessageListenerContainer.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="default"/>
<xsd:enumeration value="simple"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="container-class" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A custom listener container implementation class as fully qualified class name.
Default is Spring's standard DefaultMessageListenerContainer or
SimpleMessageListenerContainer, according to the "container-type" attribute.
Note that a custom container class will typically be a subclass of either of
those two Spring-provided standard container classes: Nake sure that the
"container-type" attribute matches the actual base type that the custom class
derives from ("default" will usually be fine anyway, since most custom classes
will derive from DefaultMessageListenerContainer).
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:expected-type type="java.lang.Class"/>
<tool:assignable-to type="org.springframework.jms.listener.AbstractMessageListenerContainer"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="connection-factory" type="xsd:string" default="connectionFactory">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the JMS ConnectionFactory bean.
Default is "connectionFactory".
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="javax.jms.ConnectionFactory"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="task-executor" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to a Spring TaskExecutor (or standard JDK 1.5 Executor) for executing
JMS listener invokers. Default is a SimpleAsyncTaskExecutor in case of a
DefaultMessageListenerContainer, using internally managed threads. For a
SimpleMessageListenerContainer, listeners will always get invoked within the
JMS provider's receive thread by default.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="java.util.concurrent.Executor"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="destination-resolver" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the DestinationResolver strategy for resolving destination names.
Default is a DynamicDestinationResolver, using the JMS provider's queue/topic
name resolution. Alternatively, specify a reference to a JndiDestinationResolver
(typically in a J2EE environment).
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jms.support.destination.DestinationResolver"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converter" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the MessageConverter strategy for converting JMS Messages to
listener method arguments. Default is a SimpleMessageConverter.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jms.support.converter.MessageConverter"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-handler" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to an ErrorHandler strategy for handling any uncaught Exceptions
that may occur during the execution of the MessageListener.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.util.ErrorHandler"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="destination-type" default="queue">
<xsd:annotation>
<xsd:documentation><![CDATA[
The JMS destination type for this listener: "queue", "topic" or "durableTopic".
The default is "queue".
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="queue"/>
<xsd:enumeration value="topic"/>
<xsd:enumeration value="durableTopic"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="client-id" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The JMS client id for this listener container.
Needs to be specified when using durable subscriptions.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cache" default="auto">
<xsd:annotation>
<xsd:documentation><![CDATA[
The cache level for JMS resources: "none", "connection", "session", "consumer"
or "auto". By default ("auto"), the cache level will effectively be "consumer",
unless an external transaction manager has been specified - in which case the
effective default will be "none" (assuming J2EE-style transaction management
where the given ConnectionFactory is an XA-aware pool).
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="none"/>
<xsd:enumeration value="connection"/>
<xsd:enumeration value="session"/>
<xsd:enumeration value="consumer"/>
<xsd:enumeration value="auto"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="acknowledge" default="auto">
<xsd:annotation>
<xsd:documentation><![CDATA[
The native JMS acknowledge mode: "auto", "client", "dups-ok" or "transacted".
A value of "transacted" effectively activates a locally transacted Session;
as alternative, specify an external "transaction-manager" via the corresponding
attribute. Default is "auto".
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="auto"/>
<xsd:enumeration value="client"/>
<xsd:enumeration value="dups-ok"/>
<xsd:enumeration value="transacted"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="transaction-manager" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to an external PlatformTransactionManager (typically an
XA-based transaction coordinator, e.g. Spring's JtaTransactionManager).
If not specified, native acknowledging will be used (see "acknowledge" attribute).
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.transaction.PlatformTransactionManager"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="concurrency" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The number of concurrent sessions/consumers to start for each listener.
Can either be a simple number indicating the maximum number (e.g. "5")
or a range indicating the lower as well as the upper limit (e.g. "3-5").
Note that a specified minimum is just a hint and might be ignored at runtime.
Default is 1; keep concurrency limited to 1 in case of a topic listener
or if message ordering is important; consider raising it for general queues.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="prefetch" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The maximum number of messages to load into a single session.
Note that raising this number might lead to starvation of concurrent consumers!
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="receive-timeout" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The timeout to use for receive calls (in milliseconds).
The default is 1000 ms (1 sec); -1 indicates no timeout at all.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="recovery-interval" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specify the interval between recovery attempts, in milliseconds.
The default is 5000 ms, that is, 5 seconds.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="phase" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The lifecycle phase within which this container should start and stop. The lower
the value the earlier this container will start and the later it will stop. The
default is Integer.MAX_VALUE meaning the container will start as late as possible
and stop as soon as possible.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="jca-listener-container">
<xsd:annotation>
<xsd:documentation><![CDATA[
Each listener child element will be hosted by a container whose configuration
is determined by this parent element. This variant builds standard JCA-based
listener containers, operating against a specified JCA ResourceAdapter
(which needs to be provided by the JMS message broker, e.g. ActiveMQ). When
a factory-id attribute is present, the configuration defined by this element is
exposed as a org.springframework.jms.config.JmsListenerContainerFactory. It is
therefore possible to only define this element without any child to just expose
a container factory.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="listener" type="listenerType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="factory-id" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Expose the settings defined by this element as a org.springframework.jms.config.JmsListenerContainerFactory
so that they can be reused with other endpoints.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="resource-adapter" type="xsd:string" default="resourceAdapter">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the JCA ResourceAdapter bean for the JMS provider.
Default is "resourceAdapter".
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="javax.resource.spi.ResourceAdapter"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="activation-spec-factory" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the JmsActivationSpecFactory.
Default is to autodetect the JMS provider and its ActivationSpec class
(see DefaultJmsActivationSpecFactory).
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jms.listener.endpoint.JmsActivationSpecFactory"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="destination-resolver" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the DestinationResolver strategy for resolving destination names.
Default is to pass in the destination name Strings into the JCA ActivationSpec as-is.
Alternatively, specify a reference to a JndiDestinationResolver (typically in a J2EE
environment, in particular if the server insists on receiving Destination objects).
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jms.support.destination.DestinationResolver"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converter" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the MessageConverter strategy for converting JMS Messages to
listener method arguments. Default is a SimpleMessageConverter.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jms.support.converter.MessageConverter"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="destination-type" default="queue">
<xsd:annotation>
<xsd:documentation><![CDATA[
The JMS destination type for this listener: "queue", "topic" or "durableTopic".
Default is "queue".
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="queue"/>
<xsd:enumeration value="topic"/>
<xsd:enumeration value="durableTopic"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="client-id" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The JMS client id for this listener container.
Needs to be specified when using durable subscriptions.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="acknowledge" default="auto">
<xsd:annotation>
<xsd:documentation><![CDATA[
The native JMS acknowledge mode: "auto", "client", "dups-ok" or "transacted".
A value of "transacted" effectively activates a locally transacted Session;
as alternative, specify an external "transaction-manager" via the corresponding
attribute. Default is "auto".
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="auto"/>
<xsd:enumeration value="client"/>
<xsd:enumeration value="dups-ok"/>
<xsd:enumeration value="transacted"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="transaction-manager" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the Spring JtaTransactionManager or [javax.transaction.TransactionManager],
for kicking off an XA transaction for each incoming message.
If not specified, native acknowledging will be used (see "acknowledge" attribute).
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref"/>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="concurrency" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The number of concurrent sessions/consumers to start for each listener.
Can either be a simple number indicating the maximum number (e.g. "5")
or a range indicating the lower as well as the upper limit (e.g. "3-5").
Note that a specified minimum is just a hint and will typically be ignored
at runtime when using a JCA listener container. Default is 1.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="prefetch" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The maximum number of messages to load into a single session.
Note that raising this number might lead to starvation of concurrent consumers!
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="phase" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The lifecycle phase within which this container should start and stop. The lower
the value the earlier this container will start and the later it will stop. The
default is Integer.MAX_VALUE meaning the container will start as late as possible
and stop as soon as possible.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="listenerType">
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The unique identifier for a listener.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="destination" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The destination name for this listener, resolved through the
container-wide DestinationResolver strategy (if any). Required.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="subscription" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name for the durable subscription, if any.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="selector" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The JMS message selector for this listener.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The bean name of the listener object, implementing
the MessageListener/SessionAwareMessageListener interface
or defining the specified listener method. Required.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref"/>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="method" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the listener method to invoke. If not specified,
the target bean is supposed to implement the MessageListener
or SessionAwareMessageListener interface.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="response-destination" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
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.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,264 @@
/*
* 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;
import java.util.Enumeration;
import java.util.concurrent.ConcurrentHashMap;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.TextMessage;
/**
* Stub JMS Message implementation intended for testing purposes only.
*
* @author Mark Fisher
* @since 4.1
*/
public class StubTextMessage implements TextMessage {
private String messageId;
private String text;
private int deliveryMode = DEFAULT_DELIVERY_MODE;
private Destination destination;
private String correlationId;
private Destination replyTo;
private String type;
private long timestamp = 0L;
private long expiration = 0L;
private int priority = DEFAULT_PRIORITY;
private boolean redelivered;
private ConcurrentHashMap<String, Object> properties = new ConcurrentHashMap<String, Object>();
public StubTextMessage() {
}
public StubTextMessage(String text) {
this.text = text;
}
public String getText() throws JMSException {
return this.text;
}
public void setText(String text) throws JMSException {
this.text = text;
}
public void acknowledge() throws JMSException {
throw new UnsupportedOperationException();
}
public void clearBody() throws JMSException {
this.text = null;
}
public void clearProperties() throws JMSException {
this.properties.clear();
}
public boolean getBooleanProperty(String name) throws JMSException {
Object value = this.properties.get(name);
return (value instanceof Boolean) ? ((Boolean) value).booleanValue() : false;
}
public byte getByteProperty(String name) throws JMSException {
Object value = this.properties.get(name);
return (value instanceof Byte) ? ((Byte) value).byteValue() : 0;
}
public double getDoubleProperty(String name) throws JMSException {
Object value = this.properties.get(name);
return (value instanceof Double) ? ((Double) value).doubleValue() : 0;
}
public float getFloatProperty(String name) throws JMSException {
Object value = this.properties.get(name);
return (value instanceof Float) ? ((Float) value).floatValue() : 0;
}
public int getIntProperty(String name) throws JMSException {
Object value = this.properties.get(name);
return (value instanceof Integer) ? ((Integer) value).intValue() : 0;
}
public String getJMSCorrelationID() throws JMSException {
return this.correlationId;
}
public byte[] getJMSCorrelationIDAsBytes() throws JMSException {
return this.correlationId.getBytes();
}
public int getJMSDeliveryMode() throws JMSException {
return this.deliveryMode;
}
public Destination getJMSDestination() throws JMSException {
return this.destination;
}
public long getJMSExpiration() throws JMSException {
return this.expiration;
}
public String getJMSMessageID() throws JMSException {
return this.messageId;
}
public int getJMSPriority() throws JMSException {
return this.priority;
}
public boolean getJMSRedelivered() throws JMSException {
return this.redelivered;
}
public Destination getJMSReplyTo() throws JMSException {
return this.replyTo;
}
public long getJMSTimestamp() throws JMSException {
return this.timestamp;
}
public String getJMSType() throws JMSException {
return this.type;
}
public long getLongProperty(String name) throws JMSException {
Object value = this.properties.get(name);
return (value instanceof Long) ? ((Long) value).longValue() : 0;
}
public Object getObjectProperty(String name) throws JMSException {
return this.properties.get(name);
}
public Enumeration<?> getPropertyNames() throws JMSException {
return this.properties.keys();
}
public short getShortProperty(String name) throws JMSException {
Object value = this.properties.get(name);
return (value instanceof Short) ? ((Short) value).shortValue() : 0;
}
public String getStringProperty(String name) throws JMSException {
Object value = this.properties.get(name);
return (value instanceof String) ? (String) value : null;
}
public boolean propertyExists(String name) throws JMSException {
return this.properties.containsKey(name);
}
public void setBooleanProperty(String name, boolean value) throws JMSException {
this.properties.put(name, value);
}
public void setByteProperty(String name, byte value) throws JMSException {
this.properties.put(name, value);
}
public void setDoubleProperty(String name, double value) throws JMSException {
this.properties.put(name, value);
}
public void setFloatProperty(String name, float value) throws JMSException {
this.properties.put(name, value);
}
public void setIntProperty(String name, int value) throws JMSException {
this.properties.put(name, value);
}
public void setJMSCorrelationID(String correlationId) throws JMSException {
this.correlationId = correlationId;
}
public void setJMSCorrelationIDAsBytes(byte[] correlationID) throws JMSException {
this.correlationId = new String(correlationID);
}
public void setJMSDeliveryMode(int deliveryMode) throws JMSException {
this.deliveryMode = deliveryMode;
}
public void setJMSDestination(Destination destination) throws JMSException {
this.destination = destination;
}
public void setJMSExpiration(long expiration) throws JMSException {
this.expiration = expiration;
}
public void setJMSMessageID(String id) throws JMSException {
this.messageId = id;
}
public void setJMSPriority(int priority) throws JMSException {
this.priority = priority;
}
public void setJMSRedelivered(boolean redelivered) throws JMSException {
this.redelivered = redelivered;
}
public void setJMSReplyTo(Destination replyTo) throws JMSException {
this.replyTo = replyTo;
}
public void setJMSTimestamp(long timestamp) throws JMSException {
this.timestamp = timestamp;
}
public void setJMSType(String type) throws JMSException {
this.type = type;
}
public void setLongProperty(String name, long value) throws JMSException {
this.properties.put(name, value);
}
public void setObjectProperty(String name, Object value) throws JMSException {
this.properties.put(name, value);
}
public void setShortProperty(String name, short value) throws JMSException {
this.properties.put(name, value);
}
public void setStringProperty(String name, String value) throws JMSException {
this.properties.put(name, value);
}
}

View File

@@ -0,0 +1,210 @@
/*
* 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 static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import javax.jms.JMSException;
import javax.jms.Session;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.context.ApplicationContext;
import org.springframework.jms.StubTextMessage;
import org.springframework.jms.config.JmsListenerContainerTestFactory;
import org.springframework.jms.config.JmsListenerEndpoint;
import org.springframework.jms.config.JmsListenerEndpointRegistry;
import org.springframework.jms.config.MethodJmsListenerEndpoint;
import org.springframework.jms.config.SimpleJmsListenerEndpoint;
import org.springframework.jms.listener.SimpleMessageListenerContainer;
import org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
/**
*
* @author Stephane Nicoll
*/
public abstract class AbstractJmsAnnotationDrivenTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public abstract void sampleConfiguration();
@Test
public abstract void fullConfiguration();
@Test
public abstract void customConfiguration();
@Test
public abstract void defaultContainerFactoryConfiguration();
@Test
public abstract void jmsHandlerMethodFactoryConfiguration() throws JMSException;
/**
* Test for {@link SampleBean} discovery.
*/
public void testSampleConfiguration(ApplicationContext context) {
JmsListenerContainerTestFactory defaultFactory =
context.getBean("defaultFactory", JmsListenerContainerTestFactory.class);
JmsListenerContainerTestFactory simpleFactory =
context.getBean("simpleFactory", JmsListenerContainerTestFactory.class);
assertEquals(1, defaultFactory.getContainers().size());
assertEquals(1, simpleFactory.getContainers().size());
}
@Component
static class SampleBean {
@JmsListener(containerFactory = "defaultFactory", destination = "myQueue")
public void defaultHandle(String msg) {
}
@JmsListener(containerFactory = "simpleFactory", destination = "myQueue")
public void simpleHandle(String msg) {
}
}
/**
* Test for {@link FullBean} discovery.
*/
public void testFullConfiguration(ApplicationContext context) {
JmsListenerContainerTestFactory simpleFactory =
context.getBean("simpleFactory", JmsListenerContainerTestFactory.class);
assertEquals(1, simpleFactory.getContainers().size());
MethodJmsListenerEndpoint endpoint = (MethodJmsListenerEndpoint)
simpleFactory.getContainers().get(0).getEndpoint();
assertEquals("listener1", endpoint.getId());
assertEquals("queueIn", endpoint.getDestination());
assertTrue(endpoint.isQueue());
assertEquals("mySelector", endpoint.getSelector());
assertEquals("mySubscription", endpoint.getSubscription());
}
@Component
static class FullBean {
@JmsListener(id = "listener1", containerFactory = "simpleFactory", destination = "queueIn",
responseDestination = "queueOut", selector = "mySelector", subscription = "mySubscription")
public String fullHandle(String msg) {
return "reply";
}
}
/**
* Test for {@link CustomBean} and an manually endpoint registered
* with "myCustomEndpointId".
*/
public void testCustomConfiguration(ApplicationContext context) {
JmsListenerContainerTestFactory defaultFactory =
context.getBean("defaultFactory", JmsListenerContainerTestFactory.class);
JmsListenerContainerTestFactory customFactory =
context.getBean("customFactory", JmsListenerContainerTestFactory.class);
assertEquals(1, defaultFactory.getContainers().size());
assertEquals(1, customFactory.getContainers().size());
JmsListenerEndpoint endpoint = defaultFactory.getContainers().get(0).getEndpoint();
assertEquals("Wrong endpoint type", SimpleJmsListenerEndpoint.class, endpoint.getClass());
assertEquals("Wrong listener set in custom endpoint", context.getBean("simpleMessageListener"),
((SimpleJmsListenerEndpoint) endpoint).getMessageListener());
JmsListenerEndpointRegistry customRegistry =
context.getBean("customRegistry", JmsListenerEndpointRegistry.class);
assertEquals("Wrong number of containers in the registry", 2,
customRegistry.getContainers().size());
assertNotNull("Container with custom id on the annotation should be found",
customRegistry.getContainer("listenerId"));
assertNotNull("Container created with custom id should be found",
customRegistry.getContainer("myCustomEndpointId"));
}
@Component
static class CustomBean {
@JmsListener(id = "listenerId", containerFactory = "customFactory", destination = "myQueue")
public void customHandle(String msg) {
}
}
/**
* Test for {@link DefaultBean} that does not define the container
* factory to use as a default is registered.
*/
public void testDefaultContainerFactoryConfiguration(ApplicationContext context) {
JmsListenerContainerTestFactory defaultFactory =
context.getBean("defaultFactory", JmsListenerContainerTestFactory.class);
assertEquals(1, defaultFactory.getContainers().size());
}
static class DefaultBean {
@JmsListener(destination = "myQueue")
public void handleIt(String msg) {
}
}
/**
* Test for {@link ValidationBean} with a validator ({@link TestValidator}) specified
* in a custom {@link org.springframework.jms.config.DefaultJmsHandlerMethodFactory}.
*
* The test should throw a {@link org.springframework.jms.listener.adapter.ListenerExecutionFailedException}
*/
public void testJmsHandlerMethodFactoryConfiguration(ApplicationContext context) throws JMSException {
JmsListenerContainerTestFactory simpleFactory =
context.getBean("defaultFactory", JmsListenerContainerTestFactory.class);
assertEquals(1, simpleFactory.getContainers().size());
MethodJmsListenerEndpoint endpoint = (MethodJmsListenerEndpoint)
simpleFactory.getContainers().get(0).getEndpoint();
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
endpoint.setupMessageContainer(container);
MessagingMessageListenerAdapter listener = (MessagingMessageListenerAdapter) container.getMessageListener();
listener.onMessage(new StubTextMessage("failValidation"), mock(Session.class));
}
@Component
static class ValidationBean {
@JmsListener(containerFactory = "defaultFactory", destination = "myQueue")
public void defaultHandle(@Validated String msg) {
}
}
static class TestValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return String.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
String value = (String) target;
if ("failValidation".equals(value)) {
errors.reject("TEST: expected invalid value");
}
}
}
}

View File

@@ -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.annotation;
import javax.jms.JMSException;
import javax.jms.MessageListener;
import org.hamcrest.core.Is;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerEndpointRegistrar;
import org.springframework.jms.config.SimpleJmsListenerEndpoint;
import org.springframework.jms.listener.adapter.ListenerExecutionFailedException;
import org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException;
/**
*
* @author Stephane Nicoll
*/
public class AnnotationDrivenNamespaceTests extends AbstractJmsAnnotationDrivenTests {
@Override
@Test
public void sampleConfiguration() {
ApplicationContext context = new ClassPathXmlApplicationContext(
"annotation-driven-sample-config.xml", getClass());
testSampleConfiguration(context);
}
@Override
@Test
public void fullConfiguration() {
ApplicationContext context = new ClassPathXmlApplicationContext(
"annotation-driven-full-config.xml", getClass());
testFullConfiguration(context);
}
@Override
@Test
public void customConfiguration() {
ApplicationContext context = new ClassPathXmlApplicationContext(
"annotation-driven-custom-registry.xml", getClass());
testCustomConfiguration(context);
}
@Override
@Test
public void defaultContainerFactoryConfiguration() {
ApplicationContext context = new ClassPathXmlApplicationContext(
"annotation-driven-custom-container-factory.xml", getClass());
testDefaultContainerFactoryConfiguration(context);
}
@Override
public void jmsHandlerMethodFactoryConfiguration() throws JMSException {
ApplicationContext context = new ClassPathXmlApplicationContext(
"annotation-driven-custom-handler-method-factory.xml", getClass());
thrown.expect(ListenerExecutionFailedException.class);
thrown.expectCause(Is.<MethodArgumentNotValidException>isA(MethodArgumentNotValidException.class));
testJmsHandlerMethodFactoryConfiguration(context);
}
static class CustomJmsListenerConfigurer implements JmsListenerConfigurer {
private MessageListener messageListener;
private JmsListenerContainerFactory containerFactory;
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("myCustomEndpointId");
endpoint.setDestination("myQueue");
endpoint.setMessageListener(messageListener);
registrar.registerEndpoint(endpoint, containerFactory);
}
public void setMessageListener(MessageListener messageListener) {
this.messageListener = messageListener;
}
public void setContainerFactory(JmsListenerContainerFactory containerFactory) {
this.containerFactory = containerFactory;
}
}
}

View File

@@ -0,0 +1,184 @@
/*
* 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 javax.jms.JMSException;
import javax.jms.MessageListener;
import org.hamcrest.core.Is;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jms.config.DefaultJmsHandlerMethodFactory;
import org.springframework.jms.config.JmsHandlerMethodFactory;
import org.springframework.jms.config.JmsListenerContainerTestFactory;
import org.springframework.jms.config.JmsListenerEndpointRegistrar;
import org.springframework.jms.config.JmsListenerEndpointRegistry;
import org.springframework.jms.config.SimpleJmsListenerEndpoint;
import org.springframework.jms.listener.adapter.ListenerExecutionFailedException;
import org.springframework.jms.listener.adapter.MessageListenerAdapter;
import org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException;
/**
*
* @author Stephane Nicoll
*/
public class EnableJmsTests extends AbstractJmsAnnotationDrivenTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Override
@Test
public void sampleConfiguration() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
EnableJmsConfig.class, SampleBean.class);
testSampleConfiguration(context);
}
@Override
@Test
public void fullConfiguration() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
EnableJmsConfig.class, FullBean.class);
testFullConfiguration(context);
}
@Override
@Test
public void customConfiguration() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
EnableJmsCustomConfig.class, CustomBean.class);
testCustomConfiguration(context);
}
@Override
@Test
public void defaultContainerFactoryConfiguration() {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
EnableJmsDefaultContainerFactoryConfig.class, DefaultBean.class);
testDefaultContainerFactoryConfiguration(context);
}
@Override
@Test
public void jmsHandlerMethodFactoryConfiguration() throws JMSException {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
EnableJmsHandlerMethodFactoryConfig.class, ValidationBean.class);
thrown.expect(ListenerExecutionFailedException.class);
thrown.expectCause(Is.<MethodArgumentNotValidException>isA(MethodArgumentNotValidException.class));
testJmsHandlerMethodFactoryConfiguration(context);
}
@Test
public void unknownFactory() {
thrown.expect(BeanCreationException.class);
thrown.expectMessage("customFactory"); // Not found
new AnnotationConfigApplicationContext(
EnableJmsConfig.class, CustomBean.class);
}
@EnableJms
@Configuration
static class EnableJmsConfig {
@Bean
public JmsListenerContainerTestFactory defaultFactory() {
return new JmsListenerContainerTestFactory();
}
@Bean
public JmsListenerContainerTestFactory simpleFactory() {
return new JmsListenerContainerTestFactory();
}
}
@Configuration
@Import(EnableJmsConfig.class)
static class EnableJmsCustomConfig implements JmsListenerConfigurer {
@Autowired
private EnableJmsConfig jmsConfig;
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setEndpointRegistry(customRegistry());
// Also register a custom endpoint
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("myCustomEndpointId");
endpoint.setDestination("myQueue");
endpoint.setMessageListener(simpleMessageListener());
registrar.registerEndpoint(endpoint, jmsConfig.defaultFactory());
}
@Bean
public JmsListenerEndpointRegistry customRegistry() {
return new JmsListenerEndpointRegistry();
}
@Bean
public JmsListenerContainerTestFactory customFactory() {
return new JmsListenerContainerTestFactory();
}
@Bean
public MessageListener simpleMessageListener() {
return new MessageListenerAdapter();
}
}
@Configuration
@Import(EnableJmsConfig.class)
static class EnableJmsDefaultContainerFactoryConfig implements JmsListenerConfigurer {
@Autowired
private EnableJmsConfig jmsConfig;
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setDefaultContainerFactory(jmsConfig.defaultFactory());
}
}
@Configuration
@Import(EnableJmsConfig.class)
static class EnableJmsHandlerMethodFactoryConfig implements JmsListenerConfigurer {
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setJmsHandlerMethodFactory(jmsHandlerMethodFactory());
}
@Bean
public JmsHandlerMethodFactory jmsHandlerMethodFactory() {
DefaultJmsHandlerMethodFactory factory = new DefaultJmsHandlerMethodFactory();
factory.setValidator(new TestValidator());
return factory;
}
}
}

View File

@@ -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.annotation;
import static org.junit.Assert.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.config.AbstractJmsListenerEndpoint;
import org.springframework.jms.config.JmsListenerContainerTestFactory;
import org.springframework.jms.config.JmsListenerEndpoint;
import org.springframework.jms.config.JmsListenerEndpointRegistry;
import org.springframework.jms.config.MessageListenerTestContainer;
import org.springframework.jms.config.MethodJmsListenerEndpoint;
import org.springframework.jms.listener.SimpleMessageListenerContainer;
import org.springframework.stereotype.Component;
/**
*
* @author Stephane Nicoll
*/
public class JmsListenerAnnotationBeanPostProcessorTests {
@Test
public void simpleMessageListener() {
final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(Config.class,
SimpleMessageListenerTestBean.class);
JmsListenerContainerTestFactory factory = context.getBean(JmsListenerContainerTestFactory.class);
assertEquals("one container should have been registered", 1, factory.getContainers().size());
MessageListenerTestContainer container = factory.getContainers().get(0);
JmsListenerEndpoint endpoint = container.getEndpoint();
assertEquals("Wrong endpoint type", MethodJmsListenerEndpoint.class, endpoint.getClass());
MethodJmsListenerEndpoint methodEndpoint = (MethodJmsListenerEndpoint) endpoint;
assertNotNull(methodEndpoint.getBean());
assertNotNull(methodEndpoint.getMethod());
assertNull(methodEndpoint.getResponseDestination());
assertTrue(methodEndpoint.isQueue());
assertTrue("Should have been started " + container, container.isStarted());
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer();
methodEndpoint.setupMessageContainer(listenerContainer);
assertNotNull(listenerContainer.getMessageListener());
context.close(); // Close and stop the listeners
assertTrue("Should have been stopped " + container, container.isStopped());
}
@Test
public void metaAnnotationIsDiscovered() {
final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(Config.class,
MetaAnnotationTestBean.class);
JmsListenerContainerTestFactory factory = context.getBean(JmsListenerContainerTestFactory.class);
assertEquals("one container should have been registered", 1, factory.getContainers().size());
JmsListenerEndpoint endpoint = factory.getContainers().get(0).getEndpoint();
assertEquals("metaTestQueue", ((AbstractJmsListenerEndpoint) endpoint).getDestination());
}
@Component
static class SimpleMessageListenerTestBean {
@JmsListener(destination = "testQueue")
public void handleIt(String body) {
}
}
@Component
static class MetaAnnotationTestBean {
@FooListener
public void handleIt(String body) {
}
}
@JmsListener(destination = "metaTestQueue")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
static @interface FooListener {
}
@Configuration
static class Config {
@Bean
public JmsListenerAnnotationBeanPostProcessor postProcessor() {
JmsListenerAnnotationBeanPostProcessor postProcessor = new JmsListenerAnnotationBeanPostProcessor();
postProcessor.setEndpointRegistry(jmsListenerEndpointRegistry());
postProcessor.setDefaultContainerFactory(testFactory());
return postProcessor;
}
@Bean
public JmsListenerEndpointRegistry jmsListenerEndpointRegistry() {
return new JmsListenerEndpointRegistry();
}
@Bean
public JmsListenerContainerTestFactory testFactory() {
return new JmsListenerContainerTestFactory();
}
}
}

View File

@@ -0,0 +1,203 @@
/*
* 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 static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TestName;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.ByteArrayMessageConverter;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.ReflectionUtils;
/**
*
* @author Stephane Nicoll
*/
public class DefaultJmsHandlerMethodFactoryTests {
@Rule
public final TestName name = new TestName();
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final SampleBean sample = new SampleBean();
@Test
public void customConversion() throws Exception {
DefaultJmsHandlerMethodFactory instance = createInstance();
GenericConversionService conversionService = new GenericConversionService();
conversionService.addConverter(SampleBean.class, String.class, new Converter<SampleBean, String>() {
@Override
public String convert(SampleBean source) {
return "foo bar";
}
});
instance.setConversionService(conversionService);
instance.afterPropertiesSet();
InvocableHandlerMethod invocableHandlerMethod =
createInvocableHandlerMethod(instance, "simpleString", String.class);
invocableHandlerMethod.invoke(MessageBuilder.withPayload(sample).build());
assertMethodInvocation(sample, "simpleString");
}
@Test
public void customConversionServiceFailure() throws Exception {
DefaultJmsHandlerMethodFactory instance = createInstance();
GenericConversionService conversionService = new GenericConversionService();
assertFalse("conversion service should fail to convert payload",
conversionService.canConvert(Integer.class, String.class));
instance.setConversionService(conversionService);
instance.afterPropertiesSet();
InvocableHandlerMethod invocableHandlerMethod =
createInvocableHandlerMethod(instance, "simpleString", String.class);
thrown.expect(MessageConversionException.class);
invocableHandlerMethod.invoke(MessageBuilder.withPayload(123).build());
}
@Test
public void customMessageConverterFailure() throws Exception {
DefaultJmsHandlerMethodFactory instance = createInstance();
MessageConverter messageConverter = new ByteArrayMessageConverter();
instance.setMessageConverter(messageConverter);
instance.afterPropertiesSet();
InvocableHandlerMethod invocableHandlerMethod =
createInvocableHandlerMethod(instance, "simpleString", String.class);
thrown.expect(MessageConversionException.class);
invocableHandlerMethod.invoke(MessageBuilder.withPayload(123).build());
}
@Test
public void customArgumentResolver() throws Exception {
DefaultJmsHandlerMethodFactory instance = createInstance();
List<HandlerMethodArgumentResolver> customResolvers = new ArrayList<HandlerMethodArgumentResolver>();
customResolvers.add(new CustomHandlerMethodArgumentResolver());
instance.setCustomArgumentResolvers(customResolvers);
instance.afterPropertiesSet();
InvocableHandlerMethod invocableHandlerMethod =
createInvocableHandlerMethod(instance, "customArgumentResolver", Locale.class);
invocableHandlerMethod.invoke(MessageBuilder.withPayload(123).build());
assertMethodInvocation(sample, "customArgumentResolver");
}
@Test
public void overrideArgumentResolvers() throws Exception {
DefaultJmsHandlerMethodFactory instance = createInstance();
List<HandlerMethodArgumentResolver> customResolvers = new ArrayList<HandlerMethodArgumentResolver>();
customResolvers.add(new CustomHandlerMethodArgumentResolver());
instance.setArgumentResolvers(customResolvers);
instance.afterPropertiesSet();
Message<String> message = MessageBuilder.withPayload("sample").build();
// This will work as the local resolver is set
InvocableHandlerMethod invocableHandlerMethod =
createInvocableHandlerMethod(instance, "customArgumentResolver", Locale.class);
invocableHandlerMethod.invoke(message);
assertMethodInvocation(sample, "customArgumentResolver");
// This won't work as no resolver is known for the payload
InvocableHandlerMethod invocableHandlerMethod2 =
createInvocableHandlerMethod(instance, "simpleString", String.class);
thrown.expect(IllegalStateException.class);
thrown.expectMessage("No suitable resolver for");
invocableHandlerMethod2.invoke(message);
}
private void assertMethodInvocation(SampleBean bean, String methodName) {
assertTrue("Method " + methodName + " should have been invoked", bean.invocations.get(methodName));
}
private InvocableHandlerMethod createInvocableHandlerMethod(
DefaultJmsHandlerMethodFactory factory, String methodName, Class<?>... parameterTypes) {
return factory.createInvocableHandlerMethod(sample, getListenerMethod(methodName, parameterTypes));
}
private DefaultJmsHandlerMethodFactory createInstance() {
DefaultJmsHandlerMethodFactory factory = new DefaultJmsHandlerMethodFactory();
factory.setApplicationContext(new StaticApplicationContext());
return factory;
}
private Method getListenerMethod(String methodName, Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(SampleBean.class,
methodName, parameterTypes);
assertNotNull("no method found with name " + methodName
+ " and parameters " + Arrays.toString(parameterTypes));
return method;
}
static class SampleBean {
private final Map<String, Boolean> invocations = new HashMap<String, Boolean>();
public void simpleString(String value) {
invocations.put("simpleString", true);
}
public void customArgumentResolver(Locale locale) {
invocations.put("customArgumentResolver", true);
assertEquals("Wrong value for locale", Locale.getDefault(), locale);
}
}
static class CustomHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(Locale.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
return Locale.getDefault();
}
}
}

View File

@@ -0,0 +1,143 @@
/*
* 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 static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.jms.StubTextMessage;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.jms.listener.SessionAwareMessageListener;
import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.util.ReflectionUtils;
/**
*
* @author Stephane Nicoll
*/
public class JmsListenerContainerFactoryIntegrationTests {
private final DefaultJmsListenerContainerFactory containerFactory = new DefaultJmsListenerContainerFactory();
private final DefaultJmsHandlerMethodFactory factory = new DefaultJmsHandlerMethodFactory();
private final JmsEndpointSampleBean sample = new JmsEndpointSampleBean();
@Before
public void setup() {
initializeFactory(factory);
}
@Test
public void messageConverterUsedIfSet() throws JMSException {
containerFactory.setMessageConverter(new UpperCaseMessageConverter());
MethodJmsListenerEndpoint endpoint = createDefaultMethodJmsEndpoint("expectFooBarUpperCase", String.class);
Message message = new StubTextMessage("foo-bar");
invokeListener(endpoint, message);
assertListenerMethodInvocation("expectFooBarUpperCase");
}
static class JmsEndpointSampleBean {
private final Map<String, Boolean> invocations = new HashMap<String, Boolean>();
public void expectFooBarUpperCase(@Payload String msg) {
invocations.put("expectFooBarUpperCase", true);
assertEquals("Unexpected payload message", "FOO-BAR", msg);
}
}
@SuppressWarnings("unchecked")
private void invokeListener(JmsListenerEndpoint endpoint, Message message) throws JMSException {
DefaultMessageListenerContainer messageListenerContainer =
containerFactory.createMessageListenerContainer(endpoint);
Object listener = messageListenerContainer.getMessageListener();
if (listener instanceof SessionAwareMessageListener) {
((SessionAwareMessageListener<Message>) listener).onMessage(message, mock(Session.class));
}
else {
((MessageListener) listener).onMessage(message);
}
}
private void assertListenerMethodInvocation(String methodName) {
assertTrue("Method " + methodName + " should have been invoked", sample.invocations.get(methodName));
}
private MethodJmsListenerEndpoint createMethodJmsEndpoint(
DefaultJmsHandlerMethodFactory factory, Method method) {
MethodJmsListenerEndpoint endpoint = new MethodJmsListenerEndpoint();
endpoint.setBean(sample);
endpoint.setMethod(method);
endpoint.setJmsHandlerMethodFactory(factory);
return endpoint;
}
private MethodJmsListenerEndpoint createDefaultMethodJmsEndpoint(String methodName, Class<?>... parameterTypes) {
return createMethodJmsEndpoint(this.factory, getListenerMethod(methodName, parameterTypes));
}
private Method getListenerMethod(String methodName, Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(JmsEndpointSampleBean.class,
methodName, parameterTypes);
assertNotNull("no method found with name " + methodName
+ " and parameters " + Arrays.toString(parameterTypes));
return method;
}
private void initializeFactory(DefaultJmsHandlerMethodFactory factory) {
factory.setApplicationContext(new StaticApplicationContext());
factory.afterPropertiesSet();
}
private static class UpperCaseMessageConverter implements MessageConverter {
@Override
public Message toMessage(Object object, Session session) throws JMSException, MessageConversionException {
return new StubTextMessage(object.toString().toUpperCase());
}
@Override
public Object fromMessage(Message message) throws JMSException, MessageConversionException {
String content = ((TextMessage) message).getText();
return content.toUpperCase();
}
}
}

View File

@@ -0,0 +1,193 @@
/*
* 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 static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import javax.jms.ConnectionFactory;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.transaction.TransactionManager;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.jms.StubConnectionFactory;
import org.springframework.jms.listener.AbstractMessageListenerContainer;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.jms.listener.SimpleMessageListenerContainer;
import org.springframework.jms.listener.adapter.MessageListenerAdapter;
import org.springframework.jms.listener.endpoint.JmsActivationSpecConfig;
import org.springframework.jms.listener.endpoint.JmsMessageEndpointManager;
import org.springframework.jms.listener.endpoint.StubJmsActivationSpecFactory;
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;
/**
*
* @author Stephane Nicoll
*/
public class JmsListenerContainerFactoryTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final ConnectionFactory connectionFactory = new StubConnectionFactory();
private final DestinationResolver destinationResolver = new DynamicDestinationResolver();
private final MessageConverter messageConverter = new SimpleMessageConverter();
private final TransactionManager transactionManager = mock(TransactionManager.class);
@Test
public void createSimpleContainer() {
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
setDefaultJmsConfig(factory);
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
MessageListener messageListener = new MessageListenerAdapter();
endpoint.setMessageListener(messageListener);
endpoint.setDestination("myQueue");
endpoint.setQueue(false); // See #setDefaultJmsConfig
SimpleMessageListenerContainer container = factory.createMessageListenerContainer(endpoint);
assertDefaultJmsConfig(container);
assertEquals(messageListener, container.getMessageListener());
assertEquals("myQueue", container.getDestinationName());
}
@Test
public void createJmsContainerFullConfig() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
setDefaultJmsConfig(factory);
factory.setCacheLevel(DefaultMessageListenerContainer.CACHE_CONSUMER);
factory.setConcurrency("3-10");
factory.setMaxMessagesPerTask(5);
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
MessageListener messageListener = new MessageListenerAdapter();
endpoint.setMessageListener(messageListener);
endpoint.setDestination("myQueue");
endpoint.setQueue(false); // See #setDefaultJmsConfig
DefaultMessageListenerContainer container = factory.createMessageListenerContainer(endpoint);
assertDefaultJmsConfig(container);
assertEquals(DefaultMessageListenerContainer.CACHE_CONSUMER, container.getCacheLevel());
assertEquals(3, container.getConcurrentConsumers());
assertEquals(10, container.getMaxConcurrentConsumers());
assertEquals(5, container.getMaxMessagesPerTask());
assertEquals(messageListener, container.getMessageListener());
assertEquals("myQueue", container.getDestinationName());
}
@Test
public void createJcaContainerFullConfig() {
DefaultJcaListenerContainerFactory factory = new DefaultJcaListenerContainerFactory();
setDefaultJcaConfig(factory);
factory.getActivationSpecConfig().setConcurrency("10");
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
MessageListener messageListener = new MessageListenerAdapter();
endpoint.setMessageListener(messageListener);
endpoint.setDestination("myQueue");
endpoint.setQueue(false); // See #setDefaultJmsConfig
JmsMessageEndpointManager container = factory.createMessageListenerContainer(endpoint);
assertDefaultJcaConfig(container);
assertEquals(10, container.getActivationSpecConfig().getMaxConcurrency());
assertEquals(messageListener, container.getMessageListener());
assertEquals("myQueue", container.getActivationSpecConfig().getDestinationName());
}
@Test
public void endpointCanOverrideConfig() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setPubSubDomain(true); // topic
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setMessageListener(new MessageListenerAdapter());
endpoint.setQueue(true); // queue
DefaultMessageListenerContainer container = factory.createMessageListenerContainer(endpoint);
assertEquals(false, container.isPubSubDomain()); // overridden by the endpoint config
}
@Test
public void jcaExclusiveProperties() {
DefaultJcaListenerContainerFactory factory = new DefaultJcaListenerContainerFactory();
factory.setDestinationResolver(destinationResolver);
factory.setActivationSpecFactory(new StubJmsActivationSpecFactory());
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setMessageListener(new MessageListenerAdapter());
thrown.expect(IllegalStateException.class);
factory.createMessageListenerContainer(endpoint);
}
private void setDefaultJmsConfig(AbstractJmsListenerContainerFactory factory) {
factory.setConnectionFactory(connectionFactory);
factory.setDestinationResolver(destinationResolver);
factory.setMessageConverter(messageConverter);
factory.setSessionTransacted(true);
factory.setSessionAcknowledgeMode(Session.DUPS_OK_ACKNOWLEDGE);
factory.setPubSubDomain(true);
factory.setSubscriptionDurable(true);
factory.setClientId("client-1234");
}
private void assertDefaultJmsConfig(AbstractMessageListenerContainer container) {
assertEquals(connectionFactory, container.getConnectionFactory());
assertEquals(destinationResolver, container.getDestinationResolver());
assertEquals(messageConverter, container.getMessageConverter());
assertEquals(true, container.isSessionTransacted());
assertEquals(Session.DUPS_OK_ACKNOWLEDGE, container.getSessionAcknowledgeMode());
assertEquals(true, container.isPubSubDomain());
assertEquals(true, container.isSubscriptionDurable());
assertEquals("client-1234", container.getClientId());
}
private void setDefaultJcaConfig(DefaultJcaListenerContainerFactory factory) {
factory.setDestinationResolver(destinationResolver);
factory.setTransactionManager(transactionManager);
JmsActivationSpecConfig config = new JmsActivationSpecConfig();
config.setMessageConverter(messageConverter);
config.setAcknowledgeMode(Session.DUPS_OK_ACKNOWLEDGE);
config.setPubSubDomain(true);
config.setSubscriptionDurable(true);
config.setClientId("client-1234");
factory.setActivationSpecConfig(config);
}
private void assertDefaultJcaConfig(JmsMessageEndpointManager container) {
assertEquals(messageConverter, container.getMessageConverter());
JmsActivationSpecConfig config = container.getActivationSpecConfig();
assertNotNull(config);
assertEquals(Session.DUPS_OK_ACKNOWLEDGE, config.getAcknowledgeMode());
assertEquals(true, config.isPubSubDomain());
assertEquals(true, config.isSubscriptionDurable());
assertEquals("client-1234", config.getClientId());
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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;
/**
*
* @author Stephane Nicoll
*/
public class JmsListenerContainerTestFactory implements JmsListenerContainerFactory<MessageListenerTestContainer> {
private final List<MessageListenerTestContainer> containers =
new ArrayList<MessageListenerTestContainer>();
public List<MessageListenerTestContainer> getContainers() {
return containers;
}
@Override
public MessageListenerTestContainer createMessageListenerContainer(JmsListenerEndpoint endpoint) {
MessageListenerTestContainer container = new MessageListenerTestContainer(endpoint);
this.containers.add(container);
return container;
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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 static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
*
* @author Stephane Nicoll
*/
public class JmsListenerEndpointRegistrarTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final JmsListenerEndpointRegistrar registrar = new JmsListenerEndpointRegistrar();
private final JmsListenerEndpointRegistry registry = new JmsListenerEndpointRegistry();
private final JmsListenerContainerTestFactory containerFactory = new JmsListenerContainerTestFactory();
@Before
public void setup() {
registrar.setEndpointRegistry(registry);
}
@Test
public void registerNullEndpoint() {
thrown.expect(IllegalArgumentException.class);
registrar.registerEndpoint(null, containerFactory);
}
@Test
public void registerNullEndpointId() {
thrown.expect(IllegalArgumentException.class);
registrar.registerEndpoint(new SimpleJmsListenerEndpoint(), containerFactory);
}
@Test
public void registerNullContainerFactoryIsAllowed() throws Exception {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("some id");
registrar.setDefaultContainerFactory(containerFactory);
registrar.registerEndpoint(endpoint, null);
registrar.afterPropertiesSet();
assertNotNull("Container not created", registry.getContainer("some id"));
assertEquals(1, registry.getContainers().size());
}
@Test
public void registerNullContainerFactoryWithNoDefault() throws Exception {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("some id");
registrar.registerEndpoint(endpoint, null);
thrown.expect(IllegalStateException.class);
thrown.expectMessage(endpoint.toString());
registrar.afterPropertiesSet();
}
@Test
public void registerContainerWithoutFactory() throws Exception {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("myEndpoint");
registrar.setDefaultContainerFactory(containerFactory);
registrar.registerEndpoint(endpoint);
registrar.afterPropertiesSet();
assertNotNull("Container not created", registry.getContainer("myEndpoint"));
assertEquals(1, registry.getContainers().size());
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
*
* @author Stephane Nicoll
*/
public class JmsListenerEndpointRegistryTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final JmsListenerEndpointRegistry registry = new JmsListenerEndpointRegistry();
private final JmsListenerContainerTestFactory containerFactory = new JmsListenerContainerTestFactory();
@Test
public void createWithNullEndpoint() {
thrown.expect(IllegalArgumentException.class);
registry.createJmsListenerContainer(null, containerFactory);
}
@Test
public void createWithNullEndpointId() {
thrown.expect(IllegalArgumentException.class);
registry.createJmsListenerContainer(new SimpleJmsListenerEndpoint(), containerFactory);
}
@Test
public void createWithNullContainerFactory() {
thrown.expect(IllegalArgumentException.class);
registry.createJmsListenerContainer(createEndpoint("foo", "myDestination"), null);
}
@Test
public void createWithDuplicateEndpointId() {
registry.createJmsListenerContainer(createEndpoint("test", "queue"), containerFactory);
thrown.expect(IllegalStateException.class);
registry.createJmsListenerContainer(createEndpoint("test", "queue"), containerFactory);
}
private SimpleJmsListenerEndpoint createEndpoint(String id, String destinationName) {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId(id);
endpoint.setDestination(destinationName);
return endpoint;
}
}

View File

@@ -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 static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import javax.jms.MessageListener;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.jms.listener.MessageListenerContainer;
import org.springframework.jms.listener.adapter.MessageListenerAdapter;
import org.springframework.jms.listener.endpoint.JmsActivationSpecConfig;
import org.springframework.jms.listener.endpoint.JmsMessageEndpointManager;
/**
*
* @author Stephane Nicoll
*/
public class JmsListenerEndpointTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void setupJmsMessageContainerFullConfig() {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
MessageListener messageListener = new MessageListenerAdapter();
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setDestination("myQueue");
endpoint.setQueue(true);
endpoint.setSelector("foo = 'bar'");
endpoint.setSubscription("mySubscription");
endpoint.setMessageListener(messageListener);
endpoint.setupMessageContainer(container);
assertEquals("myQueue", container.getDestinationName());
assertFalse(container.isPubSubDomain());
assertEquals("foo = 'bar'", container.getMessageSelector());
assertEquals("mySubscription", container.getDurableSubscriptionName());
assertEquals(messageListener, container.getMessageListener());
}
@Test
public void setupJcaMessageContainerFullConfig() {
JmsMessageEndpointManager container = new JmsMessageEndpointManager();
MessageListener messageListener = new MessageListenerAdapter();
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setDestination("myQueue");
endpoint.setQueue(true);
endpoint.setSelector("foo = 'bar'");
endpoint.setSubscription("mySubscription");
endpoint.setMessageListener(messageListener);
endpoint.setupMessageContainer(container);
JmsActivationSpecConfig config = container.getActivationSpecConfig();
assertEquals("myQueue", config.getDestinationName());
assertFalse(config.isPubSubDomain());
assertEquals("foo = 'bar'", config.getMessageSelector());
assertEquals("mySubscription", config.getDurableSubscriptionName());
assertEquals(messageListener, container.getMessageListener());
}
@Test
public void setupMessageContainerNoListener() {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
thrown.expect(IllegalStateException.class);
endpoint.setupMessageContainer(container);
}
@Test
public void setupMessageContainerUnsupportedContainer() {
MessageListenerContainer container = mock(MessageListenerContainer.class);
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setMessageListener(new MessageListenerAdapter());
thrown.expect(IllegalArgumentException.class);
endpoint.setupMessageContainer(container);
}
}

View File

@@ -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.
@@ -16,6 +16,9 @@
package org.springframework.jms.config;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
@@ -29,6 +32,7 @@ import javax.jms.TextMessage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.ComponentDefinition;
@@ -41,17 +45,16 @@ import org.springframework.context.Phased;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jca.endpoint.GenericMessageEndpointManager;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.jms.listener.adapter.MessageListenerAdapter;
import org.springframework.jms.listener.endpoint.JmsMessageEndpointManager;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.util.ErrorHandler;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
/**
* @author Mark Fisher
* @author Juergen Hoeller
* @author Christian Dupuis
* @author Stephane Nicoll
*/
public class JmsNamespaceHandlerTests {
@@ -80,6 +83,10 @@ public class JmsNamespaceHandlerTests {
containers = context.getBeansOfType(GenericMessageEndpointManager.class);
assertEquals("Context should contain 3 JCA endpoint containers", 3, containers.size());
Map<String, JmsListenerContainerFactory> containerFactories =
context.getBeansOfType(JmsListenerContainerFactory.class);
assertEquals("Context should contain 3 JmsListenerContainerFactory instances", 3, containerFactories.size());
}
@Test
@@ -99,8 +106,8 @@ public class JmsNamespaceHandlerTests {
}
else if (container.getConnectionFactory().equals(explicitConnectionFactory)) {
explicitConnectionFactoryCount++;
assertEquals(1, container.getConcurrentConsumers());
assertEquals(2, container.getMaxConcurrentConsumers());
assertEquals(3, container.getConcurrentConsumers());
assertEquals(5, container.getMaxConcurrentConsumers());
}
}
@@ -108,6 +115,72 @@ public class JmsNamespaceHandlerTests {
assertEquals("2 containers should have the explicit connectionFactory", 2, explicitConnectionFactoryCount);
}
@Test
public void testJcaContainerConfiguration() throws Exception {
Map<String, JmsMessageEndpointManager> containers = context.getBeansOfType(JmsMessageEndpointManager.class);
assertTrue("listener3 not found", containers.containsKey("listener3"));
JmsMessageEndpointManager listener3 = containers.get("listener3");
assertEquals("Wrong resource adapter",
context.getBean("testResourceAdapter"), listener3.getResourceAdapter());
assertEquals("Wrong activation spec factory", context.getBean("testActivationSpecFactory"),
new DirectFieldAccessor(listener3).getPropertyValue("activationSpecFactory"));
Object endpointFactory = new DirectFieldAccessor(listener3).getPropertyValue("endpointFactory");
Object messageListener = new DirectFieldAccessor(endpointFactory).getPropertyValue("messageListener");
assertEquals("Wrong message listener", MessageListenerAdapter.class, messageListener.getClass());
MessageListenerAdapter adapter = (MessageListenerAdapter) messageListener;
DirectFieldAccessor adapterFieldAccessor = new DirectFieldAccessor(adapter);
assertEquals("Message converter not set properly", context.getBean("testMessageConverter"),
adapterFieldAccessor.getPropertyValue("messageConverter"));
assertEquals("Wrong delegate", context.getBean("testBean1"),
adapterFieldAccessor.getPropertyValue("delegate"));
assertEquals("Wrong method name", "setName",
adapterFieldAccessor.getPropertyValue("defaultListenerMethod"));
}
@Test
public void testJmsContainerFactoryConfiguration() {
Map<String, DefaultJmsListenerContainerFactory> containers =
context.getBeansOfType(DefaultJmsListenerContainerFactory.class);
DefaultJmsListenerContainerFactory factory = containers.get("testJmsFactory");
assertNotNull("No factory registered with testJmsFactory id", factory);
DefaultMessageListenerContainer container =
factory.createMessageListenerContainer(createDummyEndpoint());
assertEquals("explicit connection factory not set",
context.getBean(EXPLICIT_CONNECTION_FACTORY), container.getConnectionFactory());
assertEquals("explicit destination resolver not set",
context.getBean("testDestinationResolver"), container.getDestinationResolver());
assertEquals("explicit message converter not set",
context.getBean("testMessageConverter"), container.getMessageConverter());
assertEquals("wrong cache", DefaultMessageListenerContainer.CACHE_CONNECTION, container.getCacheLevel());
assertEquals("wrong concurrency", 3, container.getConcurrentConsumers());
assertEquals("wrong concurrency", 5, container.getMaxConcurrentConsumers());
assertEquals("wrong prefetch", 50, container.getMaxMessagesPerTask());
assertEquals("phase cannot be customized by the factory", Integer.MAX_VALUE, container.getPhase());
}
@Test
public void testJcaContainerFactoryConfiguration() {
Map<String, DefaultJcaListenerContainerFactory> containers =
context.getBeansOfType(DefaultJcaListenerContainerFactory.class);
DefaultJcaListenerContainerFactory factory = containers.get("testJcaFactory");
assertNotNull("No factory registered with testJcaFactory id", factory);
JmsMessageEndpointManager container =
factory.createMessageListenerContainer(createDummyEndpoint());
assertEquals("explicit resource adapter not set",
context.getBean("testResourceAdapter"),container.getResourceAdapter());
assertEquals("explicit message converter not set",
context.getBean("testMessageConverter"), container.getActivationSpecConfig().getMessageConverter());
assertEquals("wrong concurrency", 5, container.getActivationSpecConfig().getMaxConcurrency());
assertEquals("Wrong prefetch", 50, container.getActivationSpecConfig().getPrefetchSize());
assertEquals("phase cannot be customized by the factory", Integer.MAX_VALUE, container.getPhase());
}
@Test
public void testListeners() throws Exception {
TestBean testBean1 = context.getBean("testBean1", TestBean.class);
@@ -177,13 +250,24 @@ public class JmsNamespaceHandlerTests {
@Test
public void testComponentRegistration() {
assertTrue("Parser should have registered a component named 'listener1'", context.containsComponentDefinition("listener1"));
assertTrue("Parser should have registered a component named 'listener2'", context.containsComponentDefinition("listener2"));
assertTrue("Parser should have registered a component named 'listener3'", context.containsComponentDefinition("listener3"));
assertTrue("Parser should have registered a component named '" + DefaultMessageListenerContainer.class.getName() + "#0'",
context.containsComponentDefinition(DefaultMessageListenerContainer.class.getName() + "#0"));
assertTrue("Parser should have registered a component named '" + JmsMessageEndpointManager.class.getName() + "#0'",
context.containsComponentDefinition(JmsMessageEndpointManager.class.getName() + "#0"));
assertTrue("Parser should have registered a component named 'listener1'",
context.containsComponentDefinition("listener1"));
assertTrue("Parser should have registered a component named 'listener2'",
context.containsComponentDefinition("listener2"));
assertTrue("Parser should have registered a component named 'listener3'",
context.containsComponentDefinition("listener3"));
assertTrue("Parser should have registered a component named '"
+ DefaultMessageListenerContainer.class.getName() + "#0'",
context.containsComponentDefinition(DefaultMessageListenerContainer.class.getName() + "#0"));
assertTrue("Parser should have registered a component named '"
+ JmsMessageEndpointManager.class.getName() + "#0'",
context.containsComponentDefinition(JmsMessageEndpointManager.class.getName() + "#0"));
assertTrue("Parser should have registered a component named 'testJmsFactory",
context.containsComponentDefinition("testJmsFactory"));
assertTrue("Parser should have registered a component named 'testJcaFactory",
context.containsComponentDefinition("testJcaFactory"));
assertTrue("Parser should have registered a component named 'testJcaFactory",
context.containsComponentDefinition("onlyJmsFactory"));
}
@Test
@@ -191,7 +275,7 @@ public class JmsNamespaceHandlerTests {
Iterator iterator = context.getRegisteredComponents();
while (iterator.hasNext()) {
ComponentDefinition compDef = (ComponentDefinition) iterator.next();
assertNotNull("CompositeComponentDefinition '" + compDef.getName()+ "' has no source attachment", compDef.getSource());
assertNotNull("CompositeComponentDefinition '" + compDef.getName() + "' has no source attachment", compDef.getSource());
validateComponentDefinition(compDef);
}
}
@@ -228,6 +312,13 @@ public class JmsNamespaceHandlerTests {
return ((Phased) container).getPhase();
}
private JmsListenerEndpoint createDummyEndpoint() {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setMessageListener(new MessageListenerAdapter());
endpoint.setDestination("testQueue");
return endpoint;
}
public static class TestMessageListener implements MessageListener {

View File

@@ -0,0 +1,118 @@
/*
* 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.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jms.JmsException;
import org.springframework.jms.listener.MessageListenerContainer;
import org.springframework.jms.support.converter.MessageConverter;
/**
*
* @author Stephane Nicoll
*/
public class MessageListenerTestContainer
implements MessageListenerContainer, InitializingBean, DisposableBean {
private final JmsListenerEndpoint endpoint;
private boolean startInvoked;
private boolean initializationInvoked;
private boolean stopInvoked;
private boolean destroyInvoked;
MessageListenerTestContainer(JmsListenerEndpoint endpoint) {
this.endpoint = endpoint;
}
public JmsListenerEndpoint getEndpoint() {
return endpoint;
}
public boolean isStarted() {
return startInvoked && initializationInvoked;
}
public boolean isStopped() {
return stopInvoked && destroyInvoked;
}
@Override
public void start() throws JmsException {
if (startInvoked) {
throw new IllegalStateException("Start already invoked on " + this);
}
startInvoked = true;
}
@Override
public boolean isRunning() {
return startInvoked && !stopInvoked;
}
@Override
public void stop() throws JmsException {
if (stopInvoked) {
throw new IllegalStateException("Stop already invoked on " + this);
}
stopInvoked = true;
}
@Override
public void setupMessageListener(Object messageListener) {
}
@Override
public MessageConverter getMessageConverter() {
return null;
}
@Override
public void afterPropertiesSet() {
if (!startInvoked) {
throw new IllegalStateException("Start should have been invoked before " +
"afterPropertiesSet on " + this);
}
initializationInvoked = true;
}
@Override
public void destroy() {
if (!stopInvoked) {
throw new IllegalStateException("Stop should have been invoked before " +
"destroy on " + this);
}
destroyInvoked = true;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("TestContainer{");
sb.append("endpoint=").append(endpoint);
sb.append(", startInvoked=").append(startInvoked);
sb.append(", initializationInvoked=").append(initializationInvoked);
sb.append(", stopInvoked=").append(stopInvoked);
sb.append(", destroyInvoked=").append(destroyInvoked);
sb.append('}');
return sb.toString();
}
}

View File

@@ -0,0 +1,418 @@
/*
* 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 static org.junit.Assert.*;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.ObjectMessage;
import javax.jms.QueueSender;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TestName;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.jms.StubTextMessage;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.jms.listener.SimpleMessageListenerContainer;
import org.springframework.jms.listener.adapter.ListenerExecutionFailedException;
import org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter;
import org.springframework.jms.support.JmsMessageHeaderAccessor;
import org.springframework.jms.support.converter.JmsHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.support.MethodArgumentTypeMismatchException;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
/**
*
* @author Stephane Nicoll
*/
public class MethodJmsListenerEndpointTests {
@Rule
public final TestName name = new TestName();
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final DefaultJmsHandlerMethodFactory factory = new DefaultJmsHandlerMethodFactory();
private final DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
private final JmsEndpointSampleBean sample = new JmsEndpointSampleBean();
@Before
public void setup() {
initializeFactory(factory);
}
@Test
public void createMessageListenerNoFactory() {
MethodJmsListenerEndpoint endpoint = new MethodJmsListenerEndpoint();
endpoint.setBean(this);
endpoint.setMethod(getTestMethod());
thrown.expect(IllegalStateException.class);
endpoint.createMessageListener(container);
}
@Test
public void createMessageListener() {
MethodJmsListenerEndpoint endpoint = new MethodJmsListenerEndpoint();
endpoint.setBean(this);
endpoint.setMethod(getTestMethod());
endpoint.setJmsHandlerMethodFactory(factory);
endpoint.setResponseDestination("myResponseQueue");
assertNotNull(endpoint.createMessageListener(container));
}
@Test
public void resolveMessageAndSession() throws JMSException {
MessagingMessageListenerAdapter listener = createDefaultInstance(javax.jms.Message.class, Session.class);
Session session = mock(Session.class);
listener.onMessage(createSimpleJmsTextMessage("test"), session);
assertDefaultListenerMethodInvocation();
}
@Test
public void resolveGenericMessage() throws JMSException {
MessagingMessageListenerAdapter listener = createDefaultInstance(Message.class);
Session session = mock(Session.class);
listener.onMessage(createSimpleJmsTextMessage("test"), session);
assertDefaultListenerMethodInvocation();
}
@Test
public void resolveHeaderAndPayload() throws JMSException {
MessagingMessageListenerAdapter listener = createDefaultInstance(String.class, int.class);
Session session = mock(Session.class);
StubTextMessage message = createSimpleJmsTextMessage("my payload");
message.setIntProperty("myCounter", 55);
listener.onMessage(message, session);
assertDefaultListenerMethodInvocation();
}
@Test
public void resolveHeaders() throws JMSException {
MessagingMessageListenerAdapter listener = createDefaultInstance(String.class, Map.class);
Session session = mock(Session.class);
StubTextMessage message = createSimpleJmsTextMessage("my payload");
message.setIntProperty("customInt", 1234);
message.setJMSMessageID("abcd-1234");
listener.onMessage(message, session);
assertDefaultListenerMethodInvocation();
}
@Test
public void resolveMessageHeaders() throws JMSException {
MessagingMessageListenerAdapter listener = createDefaultInstance(MessageHeaders.class);
Session session = mock(Session.class);
StubTextMessage message = createSimpleJmsTextMessage("my payload");
message.setLongProperty("customLong", 4567L);
message.setJMSType("myMessageType");
listener.onMessage(message, session);
assertDefaultListenerMethodInvocation();
}
@Test
public void resolveJmsMessageHeaderAccessor() throws JMSException {
MessagingMessageListenerAdapter listener = createDefaultInstance(JmsMessageHeaderAccessor.class);
Session session = mock(Session.class);
StubTextMessage message = createSimpleJmsTextMessage("my payload");
message.setBooleanProperty("customBoolean", true);
message.setJMSPriority(9);
listener.onMessage(message, session);
assertDefaultListenerMethodInvocation();
}
@Test
public void resolveObjectPayload() throws JMSException {
MessagingMessageListenerAdapter listener = createDefaultInstance(MyBean.class);
MyBean myBean = new MyBean();
myBean.name = "myBean name";
Session session = mock(Session.class);
ObjectMessage message = mock(ObjectMessage.class);
given(message.getObject()).willReturn(myBean);
listener.onMessage(message, session);
assertDefaultListenerMethodInvocation();
}
@Test
public void resolveConvertedPayload() throws JMSException {
MessagingMessageListenerAdapter listener = createDefaultInstance(Integer.class);
Session session = mock(Session.class);
listener.onMessage(createSimpleJmsTextMessage("33"), session);
assertDefaultListenerMethodInvocation();
}
@Test
public void processAndReply() throws JMSException {
MessagingMessageListenerAdapter listener = createDefaultInstance(String.class);
String body = "echo text";
String correlationId = "link-1234";
Destination replyDestination = new Destination() {};
TextMessage reply = mock(TextMessage.class);
QueueSender queueSender = mock(QueueSender.class);
Session session = mock(Session.class);
given(session.createTextMessage(body)).willReturn(reply);
given(session.createProducer(replyDestination)).willReturn(queueSender);
listener.setDefaultResponseDestination(replyDestination);
StubTextMessage inputMessage = createSimpleJmsTextMessage(body);
inputMessage.setJMSCorrelationID(correlationId);
listener.onMessage(inputMessage, session);
assertDefaultListenerMethodInvocation();
verify(reply).setJMSCorrelationID(correlationId);
verify(queueSender).send(reply);
verify(queueSender).close();
}
@Test
public void validatePayloadValid() throws JMSException {
String methodName = "validatePayload";
DefaultJmsHandlerMethodFactory customFactory = new DefaultJmsHandlerMethodFactory();
customFactory.setValidator(testValidator("invalid value"));
initializeFactory(customFactory);
Method method = getListenerMethod(methodName, String.class);
MessagingMessageListenerAdapter listener = createInstance(customFactory, method);
Session session = mock(Session.class);
listener.onMessage(createSimpleJmsTextMessage("test"), session); // test is a valid value
assertListenerMethodInvocation(sample, methodName);
}
@Test
public void validatePayloadInvalid() throws JMSException {
DefaultJmsHandlerMethodFactory customFactory = new DefaultJmsHandlerMethodFactory();
customFactory.setValidator(testValidator("invalid value"));
Method method = getListenerMethod("validatePayload", String.class);
MessagingMessageListenerAdapter listener = createInstance(customFactory, method);
Session session = mock(Session.class);
thrown.expect(ListenerExecutionFailedException.class);
listener.onMessage(createSimpleJmsTextMessage("invalid value"), session); // test is an invalid value
}
// failure scenario
@Test
public void invalidPayloadType() throws JMSException {
MessagingMessageListenerAdapter listener = createDefaultInstance(Integer.class);
Session session = mock(Session.class);
thrown.expect(ListenerExecutionFailedException.class);
thrown.expectCause(Matchers.isA(MessageConversionException.class));
thrown.expectMessage(getDefaultListenerMethod(Integer.class).toGenericString()); // ref to method
listener.onMessage(createSimpleJmsTextMessage("test"), session); // test is not a valid integer
}
@Test
public void invalidMessagePayloadType() throws JMSException {
MessagingMessageListenerAdapter listener = createDefaultInstance(Message.class);
Session session = mock(Session.class);
thrown.expect(ListenerExecutionFailedException.class);
thrown.expectCause(Matchers.isA(MethodArgumentTypeMismatchException.class));
listener.onMessage(createSimpleJmsTextMessage("test"), session); // Message<String> as Message<Integer>
}
private MessagingMessageListenerAdapter createInstance(
DefaultJmsHandlerMethodFactory factory, Method method) {
MethodJmsListenerEndpoint endpoint = new MethodJmsListenerEndpoint();
endpoint.setBean(sample);
endpoint.setMethod(method);
endpoint.setJmsHandlerMethodFactory(factory);
return endpoint.createMessageListener(new SimpleMessageListenerContainer());
}
private MessagingMessageListenerAdapter createDefaultInstance(Class<?>... parameterTypes) {
return createInstance(this.factory, getDefaultListenerMethod(parameterTypes));
}
private StubTextMessage createSimpleJmsTextMessage(String body) {
return new StubTextMessage(body);
}
private Method getListenerMethod(String methodName, Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(JmsEndpointSampleBean.class,
methodName, parameterTypes);
assertNotNull("no method found with name " + methodName
+ " and parameters " + Arrays.toString(parameterTypes));
return method;
}
private Method getDefaultListenerMethod(Class<?>... parameterTypes) {
return getListenerMethod(name.getMethodName(), parameterTypes);
}
private void assertDefaultListenerMethodInvocation() {
assertListenerMethodInvocation(sample, name.getMethodName());
}
private void assertListenerMethodInvocation(JmsEndpointSampleBean bean, String methodName) {
assertTrue("Method " + methodName + " should have been invoked", bean.invocations.get(methodName));
}
private void initializeFactory(DefaultJmsHandlerMethodFactory factory) {
factory.setApplicationContext(new StaticApplicationContext());
factory.afterPropertiesSet();
}
private Validator testValidator(final String invalidValue) {
return new Validator() {
@Override
public boolean supports(Class<?> clazz) {
return String.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
String value = (String) target;
if (invalidValue.equals(value)) {
errors.reject("not a valid value");
}
}
};
}
private Method getTestMethod() {
return ReflectionUtils.findMethod(MethodJmsListenerEndpointTests.class, name.getMethodName());
}
static class JmsEndpointSampleBean {
private final Map<String, Boolean> invocations = new HashMap<String, Boolean>();
public void resolveMessageAndSession(javax.jms.Message message, Session session) {
invocations.put("resolveMessageAndSession", true);
assertNotNull("Message not injected", message);
assertNotNull("Session not injected", session);
}
public void resolveGenericMessage(Message<String> message) {
invocations.put("resolveGenericMessage", true);
assertNotNull("Generic message not injected", message);
assertEquals("Wrong message payload", "test", message.getPayload());
}
public void resolveHeaderAndPayload(@Payload String content, @Header("myCounter") int counter) {
invocations.put("resolveHeaderAndPayload", true);
assertEquals("Wrong @Payload resolution", "my payload", content);
assertEquals("Wrong @Header resolution", 55, counter);
}
public void resolveHeaders(String content, @Headers Map<String, Object> headers) {
invocations.put("resolveHeaders", true);
assertEquals("Wrong payload resolution", "my payload", content);
assertNotNull("headers not injected", headers);
assertEquals("Missing JMS message id header", "abcd-1234", headers.get(JmsHeaders.MESSAGE_ID));
assertEquals("Missing custom header", 1234, headers.get("customInt"));
}
public void resolveMessageHeaders(MessageHeaders headers) {
invocations.put("resolveMessageHeaders", true);
assertNotNull("MessageHeaders not injected", headers);
assertEquals("Missing JMS message type header", "myMessageType", headers.get(JmsHeaders.TYPE));
assertEquals("Missing custom header", 4567L, (long) headers.get("customLong"), 0.0);
}
public void resolveJmsMessageHeaderAccessor(JmsMessageHeaderAccessor headers) {
invocations.put("resolveJmsMessageHeaderAccessor", true);
assertNotNull("MessageHeaders not injected", headers);
assertEquals("Missing JMS message priority header", Integer.valueOf(9), headers.getPriority());
assertEquals("Missing custom header", true, headers.getHeader("customBoolean"));
}
public void resolveObjectPayload(MyBean bean) {
invocations.put("resolveObjectPayload", true);
assertNotNull("Object payload not injected", bean);
assertEquals("Wrong content for payload", "myBean name", bean.name);
}
public void resolveConvertedPayload(Integer counter) {
invocations.put("resolveConvertedPayload", true);
assertNotNull("Payload not injected", counter);
assertEquals("Wrong content for payload", Integer.valueOf(33), counter);
}
public String processAndReply(@Payload String content) {
invocations.put("processAndReply", true);
return content;
}
public void validatePayload(@Validated String payload) {
invocations.put("validatePayload", true);
}
public void invalidPayloadType(@Payload Integer payload) {
throw new IllegalStateException("Should never be called.");
}
public void invalidMessagePayloadType(Message<Integer> message) {
throw new IllegalStateException("Should never be called.");
}
}
@SuppressWarnings("serial")
static class MyBean implements Serializable {
private String name;
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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 static org.junit.Assert.*;
import javax.jms.MessageListener;
import org.junit.Test;
import org.springframework.jms.listener.SimpleMessageListenerContainer;
import org.springframework.jms.listener.adapter.MessageListenerAdapter;
/**
*
* @author Stephane Nicoll
*/
public class SimpleJmsListenerEndpointTests {
private final SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
@Test
public void createListener() {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
MessageListener messageListener = new MessageListenerAdapter();
endpoint.setMessageListener(messageListener);
assertSame(messageListener, endpoint.createMessageListener(container));
}
}

View File

@@ -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.listener.adapter;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.lang.reflect.Method;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.jms.StubTextMessage;
import org.springframework.jms.config.DefaultJmsHandlerMethodFactory;
import org.springframework.jms.support.converter.JmsHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.ReflectionUtils;
/**
*
* @author Stephane Nicoll
*/
public class MessagingMessageListenerAdapterTests {
private final DefaultJmsHandlerMethodFactory factory = new DefaultJmsHandlerMethodFactory();
private final SampleBean sample = new SampleBean();
@Before
public void setup() {
initializeFactory(factory);
}
@Test
public void buildMessageWithStandardMessage() throws JMSException {
Destination replyTo = new Destination() {};
Message<String> result = MessageBuilder.withPayload("Response")
.setHeader("foo", "bar")
.setHeader(JmsHeaders.TYPE, "msg_type")
.setHeader(JmsHeaders.REPLY_TO, replyTo)
.build();
Session session = mock(Session.class);
given(session.createTextMessage("Response")).willReturn(new StubTextMessage("Response"));
javax.jms.Message replyMessage = getSimpleInstance().buildMessage(session, result);
verify(session).createTextMessage("Response");
assertNotNull("reply should never be null", replyMessage);
assertEquals("Response", ((TextMessage) replyMessage).getText());
assertEquals("custom header not copied", "bar", replyMessage.getStringProperty("foo"));
assertEquals("type header not copied", "msg_type", replyMessage.getJMSType());
assertEquals("replyTo header not copied", replyTo, replyMessage.getJMSReplyTo());
}
protected MessagingMessageListenerAdapter getSimpleInstance() {
Method m = ReflectionUtils.findMethod(SampleBean.class, "echo", Message.class);
return createInstance(m);
}
protected MessagingMessageListenerAdapter createInstance(Method m) {
MessagingMessageListenerAdapter adapter = new MessagingMessageListenerAdapter();
adapter.setHandlerMethod(factory.createInvocableHandlerMethod(sample, m));
return adapter;
}
private void initializeFactory(DefaultJmsHandlerMethodFactory factory) {
factory.setApplicationContext(new StaticApplicationContext());
factory.afterPropertiesSet();
}
private static class SampleBean {
public Message<String> echo(Message<String> input) {
return MessageBuilder.withPayload(input.getPayload())
.setHeader(JmsHeaders.TYPE, "reply")
.build();
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.support;
import static org.junit.Assert.*;
import java.util.Map;
import javax.jms.Destination;
import javax.jms.JMSException;
import org.junit.Test;
import org.springframework.jms.StubTextMessage;
import org.springframework.jms.support.converter.SimpleJmsHeaderMapper;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
/**
*
* @author Stephane Nicoll
*/
public class JmsMessageHeaderAccessorTests {
@Test
public void validateJmsHeaders() throws JMSException {
Destination destination = new Destination() {};
Destination replyTo = new Destination() {};
StubTextMessage jmsMessage = new StubTextMessage("test");
jmsMessage.setJMSCorrelationID("correlation-1234");
jmsMessage.setJMSPriority(9);
jmsMessage.setJMSDestination(destination);
jmsMessage.setJMSDeliveryMode(1);
jmsMessage.setJMSExpiration(1234L);
jmsMessage.setJMSMessageID("abcd-1234");
jmsMessage.setJMSPriority(9);
jmsMessage.setJMSReplyTo(replyTo);
jmsMessage.setJMSRedelivered(true);
jmsMessage.setJMSType("type");
jmsMessage.setJMSTimestamp(4567L);
Map<String, Object> mappedHeaders = new SimpleJmsHeaderMapper().toHeaders(jmsMessage);
Message<String> message = MessageBuilder.withPayload("test").copyHeaders(mappedHeaders).build();
JmsMessageHeaderAccessor headerAccessor = JmsMessageHeaderAccessor.wrap(message);
assertEquals("correlation-1234", headerAccessor.getCorrelationId());
assertEquals(destination, headerAccessor.getDestination());
assertEquals(Integer.valueOf(1), headerAccessor.getDeliveryMode());
assertEquals(1234L, headerAccessor.getExpiration(), 0.0);
assertEquals("abcd-1234", headerAccessor.getMessageId());
assertEquals(Integer.valueOf(9), headerAccessor.getPriority());
assertEquals(replyTo, headerAccessor.getReplyTo());
assertEquals(replyTo, headerAccessor.getReplyChannel());
assertEquals(true, headerAccessor.getRedelivered());
assertEquals("type", headerAccessor.getType());
assertEquals(4567L, headerAccessor.getTimestamp(), 0.0);
}
}

View File

@@ -0,0 +1,562 @@
/*
* 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.support.converter;
import static org.junit.Assert.*;
import java.util.Date;
import java.util.Map;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import org.junit.Test;
import org.springframework.jms.StubTextMessage;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
/**
*
* @author Mark Fisher
* @author Gary Russel
* @author Stephane Nicoll
*/
public class SimpleJmsHeaderMapperTests {
private final SimpleJmsHeaderMapper mapper = new SimpleJmsHeaderMapper();
// Outbound mapping
@Test
public void jmsReplyToMappedFromHeader() throws JMSException {
Destination replyTo = new Destination() {};
Message<String> message = initBuilder()
.setHeader(JmsHeaders.REPLY_TO, replyTo).build();
javax.jms.Message jmsMessage = new StubTextMessage();
mapper.fromHeaders(message.getHeaders(), jmsMessage);
assertNotNull(jmsMessage.getJMSReplyTo());
assertSame(replyTo, jmsMessage.getJMSReplyTo());
}
@Test
public void JmsReplyToIgnoredIfIncorrectType() throws JMSException {
Message<String> message = initBuilder()
.setHeader(JmsHeaders.REPLY_TO, "not-a-destination").build();
javax.jms.Message jmsMessage = new StubTextMessage();
mapper.fromHeaders(message.getHeaders(), jmsMessage);
assertNull(jmsMessage.getJMSReplyTo());
}
@Test
public void jmsCorrelationIdMappedFromHeader() throws JMSException {
String jmsCorrelationId = "ABC-123";
Message<String> message = initBuilder()
.setHeader(JmsHeaders.CORRELATION_ID, jmsCorrelationId).build();
javax.jms.Message jmsMessage = new StubTextMessage();
mapper.fromHeaders(message.getHeaders(), jmsMessage);
assertNotNull(jmsMessage.getJMSCorrelationID());
assertEquals(jmsCorrelationId, jmsMessage.getJMSCorrelationID());
}
@Test
public void jmsCorrelationIdNumberConvertsToString() throws JMSException {
Message<String> message = initBuilder()
.setHeader(JmsHeaders.CORRELATION_ID, 123).build();
javax.jms.Message jmsMessage = new StubTextMessage();
mapper.fromHeaders(message.getHeaders(), jmsMessage);
assertEquals("123", jmsMessage.getJMSCorrelationID());
}
@Test
public void jmsCorrelationIdIgnoredIfIncorrectType() throws JMSException {
Message<String> message = initBuilder()
.setHeader(JmsHeaders.CORRELATION_ID, new Date()).build();
javax.jms.Message jmsMessage = new StubTextMessage();
mapper.fromHeaders(message.getHeaders(), jmsMessage);
assertNull(jmsMessage.getJMSCorrelationID());
}
@Test
public void jmsTypeMappedFromHeader() throws JMSException {
String jmsType = "testing";
Message<String> message = initBuilder()
.setHeader(JmsHeaders.TYPE, jmsType).build();
javax.jms.Message jmsMessage = new StubTextMessage();
mapper.fromHeaders(message.getHeaders(), jmsMessage);
assertNotNull(jmsMessage.getJMSType());
assertEquals(jmsType, jmsMessage.getJMSType());
}
@Test
public void jmsTypeIgnoredIfIncorrectType() throws JMSException {
Message<String> message = initBuilder()
.setHeader(JmsHeaders.TYPE, 123).build();
javax.jms.Message jmsMessage = new StubTextMessage();
mapper.fromHeaders(message.getHeaders(), jmsMessage);
assertNull(jmsMessage.getJMSType());
}
@Test
public void jmsReadOnlyPropertiesNotMapped() throws JMSException {
Message<String> message = initBuilder()
.setHeader(JmsHeaders.DESTINATION, new Destination() {})
.setHeader(JmsHeaders.DELIVERY_MODE, DeliveryMode.NON_PERSISTENT)
.setHeader(JmsHeaders.EXPIRATION, 1000L)
.setHeader(JmsHeaders.MESSAGE_ID, "abc-123")
.setHeader(JmsHeaders.PRIORITY, 9)
.setHeader(JmsHeaders.REDELIVERED, true)
.setHeader(JmsHeaders.TIMESTAMP, System.currentTimeMillis())
.build();
javax.jms.Message jmsMessage = new StubTextMessage();
mapper.fromHeaders(message.getHeaders(), jmsMessage);
assertNull(jmsMessage.getJMSDestination());
assertEquals(DeliveryMode.PERSISTENT, jmsMessage.getJMSDeliveryMode());
assertEquals(0, jmsMessage.getJMSExpiration());
assertNull(jmsMessage.getJMSMessageID());
assertEquals(javax.jms.Message.DEFAULT_PRIORITY, jmsMessage.getJMSPriority());
assertFalse(jmsMessage.getJMSRedelivered());
assertEquals(0, jmsMessage.getJMSTimestamp());
}
@Test
public void contentTypePropertyMappedFromHeader() throws JMSException {
Message<String> message = initBuilder()
.setHeader(MessageHeaders.CONTENT_TYPE, "foo")
.build();
javax.jms.Message jmsMessage = new StubTextMessage();
mapper.fromHeaders(message.getHeaders(), jmsMessage);
Object value = jmsMessage.getObjectProperty(JmsHeaderMapper.CONTENT_TYPE_PROPERTY);
assertNotNull(value);
assertEquals("foo", value);
}
@Test
public void userDefinedPropertyMappedFromHeader() throws JMSException {
Message<String> message = initBuilder()
.setHeader("foo", 123)
.build();
javax.jms.Message jmsMessage = new StubTextMessage();
mapper.fromHeaders(message.getHeaders(), jmsMessage);
Object value = jmsMessage.getObjectProperty("foo");
assertNotNull(value);
assertEquals(Integer.class, value.getClass());
assertEquals(123, ((Integer) value).intValue());
}
@Test
public void userDefinedPropertyMappedFromHeaderWithCustomPrefix() throws JMSException {
Message<String> message = initBuilder()
.setHeader("foo", 123)
.build();
mapper.setOutboundPrefix("custom_");
javax.jms.Message jmsMessage = new StubTextMessage();
mapper.fromHeaders(message.getHeaders(), jmsMessage);
Object value = jmsMessage.getObjectProperty("custom_foo");
assertNotNull(value);
assertEquals(Integer.class, value.getClass());
assertEquals(123, ((Integer) value).intValue());
}
@Test
public void userDefinedPropertyWithUnsupportedType() throws JMSException {
Destination destination = new Destination() {};
Message<String> message = initBuilder()
.setHeader("destination", destination)
.build();
javax.jms.Message jmsMessage = new StubTextMessage();
mapper.fromHeaders(message.getHeaders(), jmsMessage);
Object value = jmsMessage.getObjectProperty("destination");
assertNull(value);
}
@Test
public void attemptToReadDisallowedCorrelationIdPropertyIsNotFatal() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public String getJMSCorrelationID() throws JMSException {
throw new JMSException("illegal property");
}
};
assertAttemptReadDisallowedPropertyIsNotFatal(jmsMessage, JmsHeaders.CORRELATION_ID);
}
@Test
public void attemptToReadDisallowedDestinationPropertyIsNotFatal() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public Destination getJMSDestination() throws JMSException {
throw new JMSException("illegal property");
}
};
assertAttemptReadDisallowedPropertyIsNotFatal(jmsMessage, JmsHeaders.DESTINATION);
}
@Test
public void attemptToReadDisallowedDeliveryModePropertyIsNotFatal() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public int getJMSDeliveryMode() throws JMSException {
throw new JMSException("illegal property");
}
};
assertAttemptReadDisallowedPropertyIsNotFatal(jmsMessage, JmsHeaders.DELIVERY_MODE);
}
@Test
public void attemptToReadDisallowedExpirationPropertyIsNotFatal() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public long getJMSExpiration() throws JMSException {
throw new JMSException("illegal property");
}
};
assertAttemptReadDisallowedPropertyIsNotFatal(jmsMessage, JmsHeaders.EXPIRATION);
}
@Test
public void attemptToReadDisallowedMessageIdPropertyIsNotFatal() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public String getJMSMessageID() throws JMSException {
throw new JMSException("illegal property");
}
};
assertAttemptReadDisallowedPropertyIsNotFatal(jmsMessage, JmsHeaders.MESSAGE_ID);
}
@Test
public void attemptToReadDisallowedPriorityPropertyIsNotFatal() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public int getJMSPriority() throws JMSException {
throw new JMSException("illegal property");
}
};
assertAttemptReadDisallowedPropertyIsNotFatal(jmsMessage, JmsHeaders.PRIORITY);
}
@Test
public void attemptToReadDisallowedReplyToPropertyIsNotFatal() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public Destination getJMSReplyTo() throws JMSException {
throw new JMSException("illegal property");
}
};
assertAttemptReadDisallowedPropertyIsNotFatal(jmsMessage, JmsHeaders.REPLY_TO);
}
@Test
public void attemptToReadDisallowedRedeliveredPropertyIsNotFatal() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public boolean getJMSRedelivered() throws JMSException {
throw new JMSException("illegal property");
}
};
assertAttemptReadDisallowedPropertyIsNotFatal(jmsMessage, JmsHeaders.REDELIVERED);
}
@Test
public void attemptToReadDisallowedTypePropertyIsNotFatal() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public String getJMSType() throws JMSException {
throw new JMSException("illegal property");
}
};
assertAttemptReadDisallowedPropertyIsNotFatal(jmsMessage, JmsHeaders.TYPE);
}
@Test
public void attemptToReadDisallowedTimestampPropertyIsNotFatal() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public long getJMSTimestamp() throws JMSException {
throw new JMSException("illegal property");
}
};
assertAttemptReadDisallowedPropertyIsNotFatal(jmsMessage, JmsHeaders.TIMESTAMP);
}
@Test
public void attemptToReadDisallowedUserPropertyIsNotFatal() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public Object getObjectProperty(String name) throws JMSException {
if (name.equals("fail")) {
throw new JMSException("illegal property");
}
else {
return super.getObjectProperty(name);
}
}
};
jmsMessage.setBooleanProperty("fail", true);
assertAttemptReadDisallowedPropertyIsNotFatal(jmsMessage, "fail");
}
// Inbound mapping
@Test
public void jmsCorrelationIdMappedToHeader() throws JMSException {
String correlationId = "ABC-123";
javax.jms.Message jmsMessage = new StubTextMessage();
jmsMessage.setJMSCorrelationID(correlationId);
assertInboundHeader(jmsMessage, JmsHeaders.CORRELATION_ID, correlationId);
}
@Test
public void destinationMappedToHeader() throws JMSException {
Destination destination = new Destination() {};
javax.jms.Message jmsMessage = new StubTextMessage();
jmsMessage.setJMSDestination(destination);
assertInboundHeader(jmsMessage, JmsHeaders.DESTINATION, destination);
}
@Test
public void jmsDeliveryModeMappedToHeader() throws JMSException {
int deliveryMode = 1;
javax.jms.Message jmsMessage = new StubTextMessage();
jmsMessage.setJMSDeliveryMode(deliveryMode);
assertInboundHeader(jmsMessage, JmsHeaders.DELIVERY_MODE, deliveryMode);
}
@Test
public void jmsExpirationMappedToHeader() throws JMSException {
long expiration = 1000L;
javax.jms.Message jmsMessage = new StubTextMessage();
jmsMessage.setJMSExpiration(expiration);
assertInboundHeader(jmsMessage, JmsHeaders.EXPIRATION, expiration);
}
@Test
public void jmsMessageIdMappedToHeader() throws JMSException {
String messageId = "ID:ABC-123";
javax.jms.Message jmsMessage = new StubTextMessage();
jmsMessage.setJMSMessageID(messageId);
assertInboundHeader(jmsMessage, JmsHeaders.MESSAGE_ID, messageId);
}
@Test
public void jmsPriorityMappedToHeader() throws JMSException {
int priority = 8;
javax.jms.Message jmsMessage = new StubTextMessage();
jmsMessage.setJMSPriority(priority);
assertInboundHeader(jmsMessage, JmsHeaders.PRIORITY, priority);
}
@Test
public void jmsReplyToMappedToHeader() throws JMSException {
Destination replyTo = new Destination() {};
javax.jms.Message jmsMessage = new StubTextMessage();
jmsMessage.setJMSReplyTo(replyTo);
assertInboundHeader(jmsMessage, JmsHeaders.REPLY_TO, replyTo);
}
@Test
public void jmsTypeMappedToHeader() throws JMSException {
String type = "testing";
javax.jms.Message jmsMessage = new StubTextMessage();
jmsMessage.setJMSType(type);
assertInboundHeader(jmsMessage, JmsHeaders.TYPE, type);
}
@Test
public void jmsTimestampMappedToHeader() throws JMSException {
long timestamp = 123L;
javax.jms.Message jmsMessage = new StubTextMessage();
jmsMessage.setJMSTimestamp(timestamp);
assertInboundHeader(jmsMessage, JmsHeaders.TIMESTAMP, timestamp);
}
@Test
public void contentTypePropertyMappedToHeader() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage();
jmsMessage.setStringProperty("content_type", "foo");
assertInboundHeader(jmsMessage, MessageHeaders.CONTENT_TYPE, "foo");
}
@Test
public void userDefinedPropertyMappedToHeader() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage();
jmsMessage.setIntProperty("foo", 123);
assertInboundHeader(jmsMessage, "foo", 123);
}
@Test
public void userDefinedPropertyMappedToHeaderWithCustomPrefix() throws JMSException {
javax.jms.Message jmsMessage = new StubTextMessage();
jmsMessage.setIntProperty("foo", 123);
mapper.setInboundPrefix("custom_");
assertInboundHeader(jmsMessage, "custom_foo", 123);
}
@Test
public void propertyMappingExceptionIsNotFatal() throws JMSException {
Message<String> message = initBuilder()
.setHeader("foo", 123)
.setHeader("bad", 456)
.setHeader("bar", 789)
.build();
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public void setObjectProperty(String name, Object value) throws JMSException {
if (name.equals("bad")) {
throw new JMSException("illegal property");
}
super.setObjectProperty(name, value);
}
};
mapper.fromHeaders(message.getHeaders(), jmsMessage);
Object foo = jmsMessage.getObjectProperty("foo");
assertNotNull(foo);
Object bar = jmsMessage.getObjectProperty("bar");
assertNotNull(bar);
Object bad = jmsMessage.getObjectProperty("bad");
assertNull(bad);
}
@Test
public void illegalArgumentExceptionIsNotFatal() throws JMSException {
Message<String> message = initBuilder()
.setHeader("foo", 123)
.setHeader("bad", 456)
.setHeader("bar", 789)
.build();
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public void setObjectProperty(String name, Object value) throws JMSException {
if (name.equals("bad")) {
throw new IllegalArgumentException("illegal property");
}
super.setObjectProperty(name, value);
}
};
mapper.fromHeaders(message.getHeaders(), jmsMessage);
Object foo = jmsMessage.getObjectProperty("foo");
assertNotNull(foo);
Object bar = jmsMessage.getObjectProperty("bar");
assertNotNull(bar);
Object bad = jmsMessage.getObjectProperty("bad");
assertNull(bad);
}
@Test
public void attemptToWriteDisallowedReplyToPropertyIsNotFatal() throws JMSException {
Message<String> message = initBuilder()
.setHeader(JmsHeaders.REPLY_TO, new Destination() {})
.setHeader("foo", "bar")
.build();
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public void setJMSReplyTo(Destination replyTo) throws JMSException {
throw new JMSException("illegal property");
}
};
mapper.fromHeaders(message.getHeaders(), jmsMessage);
assertNull(jmsMessage.getJMSReplyTo());
assertNotNull(jmsMessage.getStringProperty("foo"));
assertEquals("bar", jmsMessage.getStringProperty("foo"));
}
@Test
public void attemptToWriteDisallowedTypePropertyIsNotFatal() throws JMSException {
Message<String> message = initBuilder()
.setHeader(JmsHeaders.TYPE, "someType")
.setHeader("foo", "bar")
.build();
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public void setJMSType(String type) throws JMSException {
throw new JMSException("illegal property");
}
};
mapper.fromHeaders(message.getHeaders(), jmsMessage);
assertNull(jmsMessage.getJMSType());
assertNotNull(jmsMessage.getStringProperty("foo"));
assertEquals("bar", jmsMessage.getStringProperty("foo"));
}
@Test
public void attemptToWriteDisallowedCorrelationIdStringPropertyIsNotFatal() throws JMSException {
Message<String> message = initBuilder()
.setHeader(JmsHeaders.CORRELATION_ID, "abc")
.setHeader("foo", "bar")
.build();
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public void setJMSCorrelationID(String correlationId) throws JMSException {
throw new JMSException("illegal property");
}
};
mapper.fromHeaders(message.getHeaders(), jmsMessage);
assertNull(jmsMessage.getJMSCorrelationID());
assertNotNull(jmsMessage.getStringProperty("foo"));
assertEquals("bar", jmsMessage.getStringProperty("foo"));
}
@Test
public void attemptToWriteDisallowedCorrelationIdNumberPropertyIsNotFatal() throws JMSException {
Message<String> message = initBuilder()
.setHeader(JmsHeaders.CORRELATION_ID, 123)
.setHeader("foo", "bar")
.build();
javax.jms.Message jmsMessage = new StubTextMessage() {
@Override
public void setJMSCorrelationID(String correlationId) throws JMSException {
throw new JMSException("illegal property");
}
};
mapper.fromHeaders(message.getHeaders(), jmsMessage);
assertNull(jmsMessage.getJMSCorrelationID());
assertNotNull(jmsMessage.getStringProperty("foo"));
assertEquals("bar", jmsMessage.getStringProperty("foo"));
}
private void assertInboundHeader(javax.jms.Message jmsMessage, String headerId, Object value) {
Map<String, Object> headers = mapper.toHeaders(jmsMessage);
Object headerValue = headers.get(headerId);
if (value == null) {
assertNull(headerValue);
}
else {
assertNotNull(headerValue);
assertEquals(value.getClass(), headerValue.getClass());
assertEquals(value, headerValue);
}
}
private void assertAttemptReadDisallowedPropertyIsNotFatal(javax.jms.Message jmsMessage, String headerId)
throws JMSException {
jmsMessage.setStringProperty("foo", "bar");
Map<String, Object> headers = mapper.toHeaders(jmsMessage);
assertNull(headers.get(headerId));
assertNotNull(headers.get("foo"));
assertEquals("bar", headers.get("foo"));
}
private MessageBuilder<String> initBuilder() {
return MessageBuilder.withPayload("test");
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
<jms:annotation-driven default-container-factory="defaultFactory"/>
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$DefaultBean"/>
<bean id="defaultFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
</beans>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
<jms:annotation-driven handler-method-factory="jmsHandlerMethodFactory"/>
<bean id="jmsHandlerMethodFactory" class="org.springframework.jms.config.DefaultJmsHandlerMethodFactory">
<property name="validator">
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$TestValidator"/>
</property>
</bean>
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$ValidationBean"/>
<bean id="defaultFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
</beans>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
<jms:annotation-driven registry="customRegistry"/>
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$CustomBean"/>
<bean id="customRegistry" class="org.springframework.jms.config.JmsListenerEndpointRegistry"/>
<bean id="defaultFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
<bean id="customFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
<bean id="simpleMessageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter"/>
<bean class="org.springframework.jms.annotation.AnnotationDrivenNamespaceTests$CustomJmsListenerConfigurer">
<property name="messageListener" ref="simpleMessageListener"/>
<property name="containerFactory" ref="defaultFactory"/>
</bean>
</beans>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
<jms:annotation-driven/>
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$FullBean"/>
<bean id="simpleFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
</beans>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
<jms:annotation-driven/>
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$SampleBean"/>
<bean id="defaultFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
<bean id="simpleFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
</beans>

View File

@@ -3,12 +3,13 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-4.0.xsd">
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
<jms:listener-container connection-factory="testConnectionFactory" task-executor="testTaskExecutor"
<jms:listener-container factory-id="testJmsFactory"
connection-factory="testConnectionFactory" task-executor="testTaskExecutor"
destination-resolver="testDestinationResolver" message-converter="testMessageConverter"
transaction-manager="testTransactionManager" error-handler="testErrorHandler"
concurrency="1-2" prefetch="50" receive-timeout="100" recovery-interval="1000" phase="99">
cache="connection" concurrency="3-5" prefetch="50" receive-timeout="100" recovery-interval="1000" phase="99">
<jms:listener id="listener1" destination="testDestination" ref="testBean1" method="setName"/>
<jms:listener id="listener2" destination="testDestination" ref="testBean2" method="setName" response-destination="responseDestination"/>
</jms:listener-container>
@@ -26,8 +27,9 @@
</property>
</bean>
<jms:jca-listener-container resource-adapter="testResourceAdapter" activation-spec-factory="testActivationSpecFactory"
message-converter="testMessageConverter" phase="77">
<jms:jca-listener-container factory-id="testJcaFactory"
resource-adapter="testResourceAdapter" activation-spec-factory="testActivationSpecFactory"
message-converter="testMessageConverter" concurrency="5" prefetch="50" phase="77">
<jms:listener id="listener3" destination="testDestination" ref="testBean1" method="setName"/>
<jms:listener id="listener4" destination="testDestination" ref="testBean2" method="setName" response-destination="responseDestination"/>
</jms:jca-listener-container>
@@ -36,6 +38,13 @@
<jms:listener destination="testDestination" ref="testBean3"/>
</jms:jca-listener-container>
<!-- Only export the factory -->
<jms:listener-container factory-id="onlyJmsFactory"
connection-factory="testConnectionFactory" task-executor="testTaskExecutor"
destination-resolver="testDestinationResolver" message-converter="testMessageConverter"
transaction-manager="testTransactionManager" error-handler="testErrorHandler"
concurrency="3-5" prefetch="50" receive-timeout="100" recovery-interval="1000"/>
<!-- the default ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.StubConnectionFactory"/>

View File

@@ -0,0 +1,62 @@
/*
* 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.messaging.converter;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
/**
* An extension of the {@link SimpleMessageConverter} that uses a
* {@link ConversionService} to convert the payload of the message
* to the requested type.
*
* <p>Return {@code null} if the conversion service cannot convert
* from the payload type to the requested type.
*
* @author Stephane Nicoll
* @since 4.1
* @see ConversionService
*/
public class GenericMessageConverter extends SimpleMessageConverter {
private final ConversionService conversionService;
/**
* Create a new instance with the {@link ConversionService} to use.
*/
public GenericMessageConverter(ConversionService conversionService) {
Assert.notNull(conversionService, "ConversionService must not be null");
this.conversionService = conversionService;
}
@Override
public Object fromMessage(Message<?> message, Class<?> targetClass) {
Object payload = message.getPayload();
if (conversionService.canConvert(payload.getClass(), targetClass)) {
try {
return conversionService.convert(payload, targetClass);
}
catch (ConversionException e) {
throw new MessageConversionException(message, "Failed to convert message payload '"
+ payload + "' to '" + targetClass.getName() + "'", e);
}
}
return null;
}
}

View File

@@ -32,7 +32,7 @@ import org.springframework.util.ReflectionUtils;
* Invokes the handler method for a given message after resolving
* its method argument values through registered {@link HandlerMethodArgumentResolver}s.
*
* <p>Use {@link #setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite)}
* <p>Use {@link #setMessageMethodArgumentResolvers(HandlerMethodArgumentResolver)}
* to customize the list of argument resolvers.
*
* @author Rossen Stoyanchev
@@ -40,7 +40,7 @@ import org.springframework.util.ReflectionUtils;
*/
public class InvocableHandlerMethod extends HandlerMethod {
private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();
private HandlerMethodArgumentResolver argumentResolvers = new HandlerMethodArgumentResolverComposite();
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
@@ -74,7 +74,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
/**
* Set {@link HandlerMethodArgumentResolver}s to use to use for resolving method argument values.
*/
public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) {
public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolver argumentResolvers) {
this.argumentResolvers = argumentResolvers;
}

View File

@@ -0,0 +1,37 @@
/*
* 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.messaging.mapping;
import java.util.Map;
import org.springframework.messaging.MessageHeaders;
/**
* Generic strategy interface for mapping {@link MessageHeaders} to and from other
* types of objects. This would typically be used by adapters where the "other type"
* has a concept of headers or properties (HTTP, JMS, AMQP, etc).
*
* @author Mark Fisher
* @param <T> type of the instance to and from which headers will be mapped.
*/
public interface HeaderMapper<T> {
void fromHeaders(MessageHeaders headers, T target);
Map<String, Object> toHeaders(T source);
}

View File

@@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Provides classes related to mapping to/from message headers.
*/
package org.springframework.messaging.mapping;

View File

@@ -0,0 +1,65 @@
/*
* 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.messaging.converter;
import static org.hamcrest.Matchers.isA;
import static org.junit.Assert.*;
import java.util.Locale;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
/**
*
* @author Stephane Nicoll
*/
public class GenericMessageConverterTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final ConversionService conversionService = new DefaultConversionService();
private final GenericMessageConverter converter = new GenericMessageConverter(conversionService);
@Test
public void fromMessageWithConversion() {
Message<String> content = MessageBuilder.withPayload("33").build();
assertEquals(33, converter.fromMessage(content, Integer.class));
}
@Test
public void fromMessageNoConverter() {
Message<Integer> content = MessageBuilder.withPayload(1234).build();
assertNull("No converter from integer to locale", converter.fromMessage(content, Locale.class));
}
@Test
public void fromMessageWithFailedConversion() {
Message<String> content = MessageBuilder.withPayload("test not a number").build();
thrown.expect(MessageConversionException.class);
thrown.expectCause(isA(ConversionException.class));
converter.fromMessage(content, Integer.class);
}
}