Initial import of the JMS module

This commit is contained in:
Arjen Poutsma 2008-10-28 13:47:36 +00:00
parent dc20a9478f
commit e6b7d6222a
106 changed files with 15904 additions and 0 deletions

View File

@ -9,6 +9,7 @@
<pathelement location="../org.springframework.aop"/>
<pathelement location="../org.springframework.context"/>
<pathelement location="../org.springframework.transaction"/>
<pathelement location="../org.springframework.jms"/>
<pathelement location="../org.springframework.aspects"/>
<pathelement location="../org.springframework.jdbc"/>
<pathelement location="../org.springframework.web"/>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="org.springframework.jms">
<property file="${basedir}/../build.properties"/>
<import file="${basedir}/../build-spring-framework/package-bundle.xml"/>
<import file="${basedir}/../spring-build/standard/default.xml"/>
</project>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="http://ivyrep.jayasoft.org/ivy-doc.xsl"?>
<ivy-module
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://incubator.apache.org/ivy/schemas/ivy.xsd"
version="1.3">
<info organisation="org.springframework" module="org.springframework.jms"/>
<configurations>
<include file="${spring.build.dir}/common/default-ivy-configurations.xml"/>
<conf name="commons-pool" extends="runtime" description="JARs needed to run with Commons Pool"/>
<conf name="jca" extends="runtime" description="JARs needed to develop JCA beans"/>
</configurations>
<publications>
<artifact name="${ant.project.name}"/>
<artifact name="${ant.project.name}-sources" type="src" ext="jar"/>
</publications>
<dependencies>
<dependency org="javax.jms" name="com.springsource.javax.jms" rev="1.1.0" conf="provided->compile"/>
<dependency org="javax.resource" name="com.springsource.javax.resource" rev="1.5.0" conf="provided, jca->compile"/>
<dependency org="javax.transaction" name="com.springsource.javax.transaction" rev="1.1.0" conf="provided->compile"/>
<dependency org="org.aopalliance" name="com.springsource.org.aopalliance" rev="1.0.0" conf="compile->compile"/>
<dependency org="org.apache.commons" name="com.springsource.org.apache.commons.logging" rev="1.1.1" conf="compile->compile"/>
<dependency org="org.apache.commons" name="com.springsource.org.apache.commons.pool" rev="1.3.0" conf="optional, commons-pool->compile"/>
<dependency org="org.springframework" name="org.springframework.aop" rev="latest.integration" conf="compile->compile"/>
<dependency org="org.springframework" name="org.springframework.beans" rev="latest.integration" conf="compile->compile"/>
<dependency org="org.springframework" name="org.springframework.context" rev="latest.integration" conf="compile->compile"/>
<dependency org="org.springframework" name="org.springframework.core" rev="latest.integration" conf="compile->compile"/>
<dependency org="org.springframework" name="org.springframework.transaction" rev="latest.integration" conf="optional, jca->compile"/>
</dependencies>
</ivy-module>

View File

@ -0,0 +1,45 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.parent</artifactId>
<version>3.0-M1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>org.springframework.jdbc</artifactId>
<packaging>jar</packaging>
<name>Spring Framework: JDBC</name>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.transaction</artifactId>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>com.springsource.javax.transaction</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.mchange.c3p0</groupId>
<artifactId>com.springsource.com.mchange.v2.c3p0</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.experlog.xapool</groupId>
<artifactId>com.springsource.org.enhydra.jdbc</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2007 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;
/**
* Runtime exception mirroring the JMS IllegalStateException.
*
* @author Mark Pollack
* @since 1.1
* @see javax.jms.IllegalStateException
*/
public class IllegalStateException extends JmsException {
public IllegalStateException(javax.jms.IllegalStateException cause) {
super(cause);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2007 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;
/**
* Runtime exception mirroring the JMS InvalidClientIDException.
*
* @author Mark Pollack
* @since 1.1
* @see javax.jms.InvalidClientIDException
*/
public class InvalidClientIDException extends JmsException {
public InvalidClientIDException(javax.jms.InvalidClientIDException cause) {
super(cause);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2007 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;
/**
* Runtime exception mirroring the JMS InvalidDestinationException.
*
* @author Mark Pollack
* @since 1.1
* @see javax.jms.InvalidDestinationException
*/
public class InvalidDestinationException extends JmsException {
public InvalidDestinationException(javax.jms.InvalidDestinationException cause) {
super(cause);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2007 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;
/**
* Runtime exception mirroring the JMS InvalidSelectorException.
*
* @author Mark Pollack
* @since 1.1
* @see javax.jms.InvalidSelectorException
*/
public class InvalidSelectorException extends JmsException {
public InvalidSelectorException(javax.jms.InvalidSelectorException cause) {
super(cause);
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright 2002-2008 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 javax.jms.JMSException;
import org.springframework.core.NestedRuntimeException;
/**
* Base class for exception thrown by the framework whenever it
* encounters a problem related to JMS.
*
* @author Mark Pollack
* @author Juergen Hoeller
* @since 1.1
*/
public abstract class JmsException extends NestedRuntimeException {
/**
* Constructor that takes a message.
* @param msg the detail message
*/
public JmsException(String msg) {
super(msg);
}
/**
* Constructor that takes a message and a root cause.
* @param msg the detail message
* @param cause the cause of the exception. This argument is generally
* expected to be a proper subclass of {@link javax.jms.JMSException},
* but can also be a JNDI NamingException or the like.
*/
public JmsException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Constructor that takes a plain root cause, intended for
* subclasses mirroring corresponding <code>javax.jms</code> exceptions.
* @param cause the cause of the exception. This argument is generally
* expected to be a proper subclass of {@link javax.jms.JMSException}.
*/
public JmsException(Throwable cause) {
super(cause != null ? cause.getMessage() : null, cause);
}
/**
* Convenience method to get the vendor specific error code if
* the root cause was an instance of JMSException.
* @return a string specifying the vendor-specific error code if the
* root cause is an instance of JMSException, or <code>null</code>
*/
public String getErrorCode() {
Throwable cause = getCause();
if (cause instanceof JMSException) {
return ((JMSException) cause).getErrorCode();
}
return null;
}
/**
* Return the detail message, including the message from the linked exception
* if there is one.
* @see javax.jms.JMSException#getLinkedException()
*/
public String getMessage() {
String message = super.getMessage();
Throwable cause = getCause();
if (cause instanceof JMSException) {
Exception linkedEx = ((JMSException) cause).getLinkedException();
if (linkedEx != null && cause.getMessage().indexOf(linkedEx.getMessage()) == -1) {
message = message + "; nested exception is " + linkedEx;
}
}
return message;
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2007 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;
/**
* Runtime exception mirroring the JMS JMSSecurityException.
*
* @author Mark Pollack
* @since 1.1
* @see javax.jms.JMSSecurityException
*/
public class JmsSecurityException extends JmsException {
public JmsSecurityException(javax.jms.JMSSecurityException cause) {
super(cause);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2007 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;
/**
* Runtime exception mirroring the JMS MessageEOFException.
*
* @author Mark Pollack
* @since 1.1
* @see javax.jms.MessageEOFException
*/
public class MessageEOFException extends JmsException {
public MessageEOFException(javax.jms.MessageEOFException cause) {
super(cause);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2007 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;
/**
* Runtime exception mirroring the JMS MessageFormatException.
*
* @author Mark Pollack
* @since 1.1
* @see javax.jms.MessageFormatException
*/
public class MessageFormatException extends JmsException {
public MessageFormatException(javax.jms.MessageFormatException cause) {
super(cause);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2007 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;
/**
* Runtime exception mirroring the JMS MessageNotReadableException.
*
* @author Mark Pollack
* @since 1.1
* @see javax.jms.MessageNotReadableException
*/
public class MessageNotReadableException extends JmsException {
public MessageNotReadableException(javax.jms.MessageNotReadableException cause) {
super(cause);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2007 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;
/**
* Runtime exception mirroring the JMS MessageNotWriteableException.
*
* @author Mark Pollack
* @since 1.1
* @see javax.jms.MessageNotWriteableException
*/
public class MessageNotWriteableException extends JmsException {
public MessageNotWriteableException(javax.jms.MessageNotWriteableException cause) {
super(cause);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2007 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;
/**
* Runtime exception mirroring the JMS ResourceAllocationException.
*
* @author Mark Pollack
* @since 1.1
* @see javax.jms.ResourceAllocationException
*/
public class ResourceAllocationException extends JmsException {
public ResourceAllocationException(javax.jms.ResourceAllocationException cause) {
super(cause);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2007 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;
/**
* Runtime exception mirroring the JMS TransactionInProgressException.
*
* @author Mark Pollack
* @since 1.1
* @see javax.jms.TransactionInProgressException
*/
public class TransactionInProgressException extends JmsException {
public TransactionInProgressException(javax.jms.TransactionInProgressException cause) {
super(cause);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2007 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;
/**
* Runtime exception mirroring the JMS TransactionRolledBackException.
*
* @author Mark Pollack
* @since 1.1
* @see javax.jms.TransactionRolledBackException
*/
public class TransactionRolledBackException extends JmsException {
public TransactionRolledBackException(javax.jms.TransactionRolledBackException cause) {
super(cause);
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2002-2007 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;
/**
* JmsException to be thrown when no other matching subclass found.
*
* @author Juergen Hoeller
* @since 1.1
*/
public class UncategorizedJmsException extends JmsException {
/**
* Constructor that takes a message.
* @param msg the detail message
*/
public UncategorizedJmsException(String msg) {
super(msg);
}
/**
* Constructor that takes a message and a root cause.
* @param msg the detail message
* @param cause the cause of the exception. This argument is generally
* expected to be a proper subclass of {@link javax.jms.JMSException},
* but can also be a JNDI NamingException or the like.
*/
public UncategorizedJmsException(String msg, Throwable cause) {
super(msg, cause);
}
/**
* Constructor that takes a root cause only.
* @param cause the cause of the exception. This argument is generally
* expected to be a proper subclass of {@link javax.jms.JMSException},
* but can also be a JNDI NamingException or the like.
*/
public UncategorizedJmsException(Throwable cause) {
super("Uncategorized exception occured during JMS processing", cause);
}
}

View File

@ -0,0 +1,288 @@
/*
* Copyright 2002-2007 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.Session;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* Abstract parser for JMS listener container elements, providing support for
* common properties that are identical for all listener container variants.
*
* @author Juergen Hoeller
* @since 2.5
*/
abstract class AbstractListenerContainerParser implements BeanDefinitionParser {
protected static final String LISTENER_ELEMENT = "listener";
protected static final String ID_ATTRIBUTE = "id";
protected static final String DESTINATION_ATTRIBUTE = "destination";
protected static final String SUBSCRIPTION_ATTRIBUTE = "subscription";
protected static final String SELECTOR_ATTRIBUTE = "selector";
protected static final String REF_ATTRIBUTE = "ref";
protected static final String METHOD_ATTRIBUTE = "method";
protected static final String DESTINATION_RESOLVER_ATTRIBUTE = "destination-resolver";
protected static final String MESSAGE_CONVERTER_ATTRIBUTE = "message-converter";
protected static final String RESPONSE_DESTINATION_ATTRIBUTE = "response-destination";
protected static final String DESTINATION_TYPE_ATTRIBUTE = "destination-type";
protected static final String DESTINATION_TYPE_QUEUE = "queue";
protected static final String DESTINATION_TYPE_TOPIC = "topic";
protected static final String DESTINATION_TYPE_DURABLE_TOPIC = "durableTopic";
protected static final String CLIENT_ID_ATTRIBUTE = "client-id";
protected static final String ACKNOWLEDGE_ATTRIBUTE = "acknowledge";
protected static final String ACKNOWLEDGE_AUTO = "auto";
protected static final String ACKNOWLEDGE_CLIENT = "client";
protected static final String ACKNOWLEDGE_DUPS_OK = "dups-ok";
protected static final String ACKNOWLEDGE_TRANSACTED = "transacted";
protected static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";
protected static final String CONCURRENCY_ATTRIBUTE = "concurrency";
protected static final String PREFETCH_ATTRIBUTE = "prefetch";
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
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 = child.getLocalName();
if (LISTENER_ELEMENT.equals(localName)) {
parseListener((Element) child, element, parserContext);
}
}
}
parserContext.popAndRegisterContainingComponent();
return null;
}
private void parseListener(Element listenerEle, Element containerEle, ParserContext parserContext) {
RootBeanDefinition listenerDef = new RootBeanDefinition();
listenerDef.setSource(parserContext.extractSource(listenerEle));
String ref = listenerEle.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(ref)) {
parserContext.getReaderContext().error(
"Listener 'ref' attribute contains empty value.", listenerEle);
}
listenerDef.getPropertyValues().addPropertyValue("delegate", new RuntimeBeanReference(ref));
String method = null;
if (listenerEle.hasAttribute(METHOD_ATTRIBUTE)) {
method = listenerEle.getAttribute(METHOD_ATTRIBUTE);
if (!StringUtils.hasText(method)) {
parserContext.getReaderContext().error(
"Listener 'method' attribute contains empty value.", listenerEle);
}
}
listenerDef.getPropertyValues().addPropertyValue("defaultListenerMethod", method);
if (containerEle.hasAttribute(MESSAGE_CONVERTER_ATTRIBUTE)) {
String messageConverter = containerEle.getAttribute(MESSAGE_CONVERTER_ATTRIBUTE);
listenerDef.getPropertyValues().addPropertyValue("messageConverter",
new RuntimeBeanReference(messageConverter));
}
BeanDefinition containerDef = parseContainer(listenerEle, containerEle, parserContext);
if (listenerEle.hasAttribute(RESPONSE_DESTINATION_ATTRIBUTE)) {
String responseDestination = listenerEle.getAttribute(RESPONSE_DESTINATION_ATTRIBUTE);
boolean pubSubDomain = indicatesPubSub(containerDef);
listenerDef.getPropertyValues().addPropertyValue(
pubSubDomain ? "defaultResponseTopicName" : "defaultResponseQueueName", responseDestination);
if (containerDef.getPropertyValues().contains("destinationResolver")) {
listenerDef.getPropertyValues().addPropertyValue("destinationResolver",
containerDef.getPropertyValues().getPropertyValue("destinationResolver").getValue());
}
}
// Remain JMS 1.0.2 compatible for the adapter if the container class indicates this.
boolean jms102 = indicatesJms102(containerDef);
listenerDef.setBeanClassName(
"org.springframework.jms.listener.adapter.MessageListenerAdapter" + (jms102 ? "102" : ""));
containerDef.getPropertyValues().addPropertyValue("messageListener", listenerDef);
String containerBeanName = listenerEle.getAttribute(ID_ATTRIBUTE);
// If no bean id is given auto generate one using the ReaderContext's BeanNameGenerator
if (!StringUtils.hasText(containerBeanName)) {
containerBeanName = parserContext.getReaderContext().generateBeanName(containerDef);
}
// Register the listener and fire event
parserContext.registerBeanComponent(new BeanComponentDefinition(containerDef, containerBeanName));
}
protected abstract BeanDefinition parseContainer(
Element listenerEle, Element containerEle, ParserContext parserContext);
protected boolean indicatesPubSub(BeanDefinition containerDef) {
return false;
}
protected boolean indicatesJms102(BeanDefinition containerDef) {
return false;
}
protected void parseListenerConfiguration(Element ele, ParserContext parserContext, BeanDefinition configDef) {
String destination = ele.getAttribute(DESTINATION_ATTRIBUTE);
if (!StringUtils.hasText(destination)) {
parserContext.getReaderContext().error(
"Listener 'destination' attribute contains empty value.", ele);
}
configDef.getPropertyValues().addPropertyValue("destinationName", destination);
if (ele.hasAttribute(SUBSCRIPTION_ATTRIBUTE)) {
String subscription = ele.getAttribute(SUBSCRIPTION_ATTRIBUTE);
if (!StringUtils.hasText(subscription)) {
parserContext.getReaderContext().error(
"Listener 'subscription' attribute contains empty value.", ele);
}
configDef.getPropertyValues().addPropertyValue("durableSubscriptionName", subscription);
}
if (ele.hasAttribute(SELECTOR_ATTRIBUTE)) {
String selector = ele.getAttribute(SELECTOR_ATTRIBUTE);
if (!StringUtils.hasText(selector)) {
parserContext.getReaderContext().error(
"Listener 'selector' attribute contains empty value.", ele);
}
configDef.getPropertyValues().addPropertyValue("messageSelector", selector);
}
}
protected void parseContainerConfiguration(Element ele, ParserContext parserContext, BeanDefinition configDef) {
String destinationType = ele.getAttribute(DESTINATION_TYPE_ATTRIBUTE);
boolean pubSubDomain = false;
boolean subscriptionDurable = false;
if (DESTINATION_TYPE_DURABLE_TOPIC.equals(destinationType)) {
pubSubDomain = true;
subscriptionDurable = true;
}
else if (DESTINATION_TYPE_TOPIC.equals(destinationType)) {
pubSubDomain = true;
}
else if ("".equals(destinationType) || DESTINATION_TYPE_QUEUE.equals(destinationType)) {
// the default: queue
}
else {
parserContext.getReaderContext().error("Invalid listener container 'destination-type': " +
"only \"queue\", \"topic\" and \"durableTopic\" supported.", ele);
}
configDef.getPropertyValues().addPropertyValue("pubSubDomain", Boolean.valueOf(pubSubDomain));
configDef.getPropertyValues().addPropertyValue("subscriptionDurable", Boolean.valueOf(subscriptionDurable));
if (ele.hasAttribute(CLIENT_ID_ATTRIBUTE)) {
String clientId = ele.getAttribute(CLIENT_ID_ATTRIBUTE);
if (!StringUtils.hasText(clientId)) {
parserContext.getReaderContext().error(
"Listener 'client-id' attribute contains empty value.", ele);
}
configDef.getPropertyValues().addPropertyValue("clientId", clientId);
}
}
protected Integer parseAcknowledgeMode(Element ele, ParserContext parserContext) {
String acknowledge = ele.getAttribute(ACKNOWLEDGE_ATTRIBUTE);
if (StringUtils.hasText(acknowledge)) {
int acknowledgeMode = Session.AUTO_ACKNOWLEDGE;
if (ACKNOWLEDGE_TRANSACTED.equals(acknowledge)) {
acknowledgeMode = Session.SESSION_TRANSACTED;
}
else if (ACKNOWLEDGE_DUPS_OK.equals(acknowledge)) {
acknowledgeMode = Session.DUPS_OK_ACKNOWLEDGE;
}
else if (ACKNOWLEDGE_CLIENT.equals(acknowledge)) {
acknowledgeMode = Session.CLIENT_ACKNOWLEDGE;
}
else if (!ACKNOWLEDGE_AUTO.equals(acknowledge)) {
parserContext.getReaderContext().error("Invalid listener container 'acknowledge' setting [" +
acknowledge + "]: only \"auto\", \"client\", \"dups-ok\" and \"transacted\" supported.", ele);
}
return new Integer(acknowledgeMode);
}
else {
return null;
}
}
protected boolean indicatesPubSubConfig(BeanDefinition configDef) {
return ((Boolean) configDef.getPropertyValues().getPropertyValue("pubSubDomain").getValue()).booleanValue();
}
protected int[] parseConcurrency(Element ele, ParserContext parserContext) {
String concurrency = ele.getAttribute(CONCURRENCY_ATTRIBUTE);
if (!StringUtils.hasText(concurrency)) {
return null;
}
try {
int separatorIndex = concurrency.indexOf('-');
if (separatorIndex != -1) {
int[] result = new int[2];
result[0] = Integer.parseInt(concurrency.substring(0, separatorIndex));
result[1] = Integer.parseInt(concurrency.substring(separatorIndex + 1, concurrency.length()));
return result;
}
else {
return new int[] {1, Integer.parseInt(concurrency)};
}
}
catch (NumberFormatException ex) {
parserContext.getReaderContext().error("Invalid concurrency value [" + concurrency + "]: only " +
"single maximum integer (e.g. \"5\") and minimum-maximum combo (e.g. \"3-5\") supported.", ele, ex);
return null;
}
}
}

View File

@ -0,0 +1,111 @@
/*
* Copyright 2002-2007 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.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* Parser for the JMS <code>&lt;jca-listener-container&gt;</code> element.
*
* @author Juergen Hoeller
* @since 2.5
*/
class JcaListenerContainerParser extends AbstractListenerContainerParser {
private static final String RESOURCE_ADAPTER_ATTRIBUTE = "resource-adapter";
private static final String ACTIVATION_SPEC_FACTORY_ATTRIBUTE = "activation-spec-factory";
protected BeanDefinition parseContainer(Element listenerEle, Element containerEle, ParserContext parserContext) {
RootBeanDefinition containerDef = new RootBeanDefinition();
containerDef.setSource(parserContext.extractSource(containerEle));
containerDef.setBeanClassName("org.springframework.jms.listener.endpoint.JmsMessageEndpointManager");
String resourceAdapterBeanName = "resourceAdapter";
if (containerEle.hasAttribute(RESOURCE_ADAPTER_ATTRIBUTE)) {
resourceAdapterBeanName = containerEle.getAttribute(RESOURCE_ADAPTER_ATTRIBUTE);
if (!StringUtils.hasText(resourceAdapterBeanName)) {
parserContext.getReaderContext().error(
"Listener container 'resource-adapter' attribute contains empty value.", containerEle);
}
}
containerDef.getPropertyValues().addPropertyValue("resourceAdapter",
new RuntimeBeanReference(resourceAdapterBeanName));
String activationSpecFactoryBeanName = containerEle.getAttribute(ACTIVATION_SPEC_FACTORY_ATTRIBUTE);
String destinationResolverBeanName = containerEle.getAttribute(DESTINATION_RESOLVER_ATTRIBUTE);
if (StringUtils.hasText(activationSpecFactoryBeanName)) {
if (StringUtils.hasText(destinationResolverBeanName)) {
parserContext.getReaderContext().error("Specify either 'activation-spec-factory' or " +
"'destination-resolver', not both. If you define a dedicated JmsActivationSpecFactory bean, " +
"specify the custom DestinationResolver there (if possible).", containerEle);
}
containerDef.getPropertyValues().addPropertyValue("activationSpecFactory",
new RuntimeBeanReference(activationSpecFactoryBeanName));
}
if (StringUtils.hasText(destinationResolverBeanName)) {
containerDef.getPropertyValues().addPropertyValue("destinationResolver",
new RuntimeBeanReference(destinationResolverBeanName));
}
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().addPropertyValue("acknowledgeMode", acknowledgeMode);
}
String transactionManagerBeanName = containerEle.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE);
if (StringUtils.hasText(transactionManagerBeanName)) {
containerDef.getPropertyValues().addPropertyValue("transactionManager",
new RuntimeBeanReference(transactionManagerBeanName));
}
int[] concurrency = parseConcurrency(containerEle, parserContext);
if (concurrency != null) {
configDef.getPropertyValues().addPropertyValue("maxConcurrency", new Integer(concurrency[1]));
}
String prefetch = containerEle.getAttribute(PREFETCH_ATTRIBUTE);
if (StringUtils.hasText(prefetch)) {
configDef.getPropertyValues().addPropertyValue("prefetchSize", new Integer(prefetch));
}
containerDef.getPropertyValues().addPropertyValue("activationSpecConfig", configDef);
return containerDef;
}
protected boolean indicatesPubSub(BeanDefinition containerDef) {
BeanDefinition configDef =
(BeanDefinition) containerDef.getPropertyValues().getPropertyValue("activationSpecConfig").getValue();
return indicatesPubSubConfig(configDef);
}
}

View File

@ -0,0 +1,166 @@
/*
* Copyright 2002-2008 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.Session;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* Parser for the JMS <code>&lt;listener-container&gt;</code> element.
*
* @author Mark Fisher
* @author Juergen Hoeller
* @since 2.5
*/
class JmsListenerContainerParser extends AbstractListenerContainerParser {
private static final String CONTAINER_TYPE_ATTRIBUTE = "container-type";
private static final String CONTAINER_CLASS_ATTRIBUTE = "container-class";
private static final String CONNECTION_FACTORY_ATTRIBUTE = "connection-factory";
private static final String TASK_EXECUTOR_ATTRIBUTE = "task-executor";
private static final String CACHE_ATTRIBUTE = "cache";
protected BeanDefinition parseContainer(Element listenerEle, Element containerEle, ParserContext parserContext) {
RootBeanDefinition containerDef = new RootBeanDefinition();
containerDef.setSource(parserContext.extractSource(containerEle));
parseListenerConfiguration(listenerEle, parserContext, containerDef);
parseContainerConfiguration(containerEle, parserContext, containerDef);
String containerType = containerEle.getAttribute(CONTAINER_TYPE_ATTRIBUTE);
String containerClass = containerEle.getAttribute(CONTAINER_CLASS_ATTRIBUTE);
if (!"".equals(containerClass)) {
containerDef.setBeanClassName(containerClass);
}
else if ("".equals(containerType) || "default".equals(containerType)) {
containerDef.setBeanClassName("org.springframework.jms.listener.DefaultMessageListenerContainer");
}
else if ("default102".equals(containerType)) {
containerDef.setBeanClassName("org.springframework.jms.listener.DefaultMessageListenerContainer102");
}
else if ("simple".equals(containerType)) {
containerDef.setBeanClassName("org.springframework.jms.listener.SimpleMessageListenerContainer");
}
else if ("simple102".equals(containerType)) {
containerDef.setBeanClassName("org.springframework.jms.listener.SimpleMessageListenerContainer102");
}
else {
parserContext.getReaderContext().error(
"Invalid 'container-type' attribute: only \"default(102)\" and \"simple(102)\" 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);
}
}
containerDef.getPropertyValues().addPropertyValue("connectionFactory",
new RuntimeBeanReference(connectionFactoryBeanName));
String taskExecutorBeanName = containerEle.getAttribute(TASK_EXECUTOR_ATTRIBUTE);
if (StringUtils.hasText(taskExecutorBeanName)) {
containerDef.getPropertyValues().addPropertyValue("taskExecutor",
new RuntimeBeanReference(taskExecutorBeanName));
}
String destinationResolverBeanName = containerEle.getAttribute(DESTINATION_RESOLVER_ATTRIBUTE);
if (StringUtils.hasText(destinationResolverBeanName)) {
containerDef.getPropertyValues().addPropertyValue("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().addPropertyValue("cacheLevelName", "CACHE_" + cache.toUpperCase());
}
}
Integer acknowledgeMode = parseAcknowledgeMode(containerEle, parserContext);
if (acknowledgeMode != null) {
if (acknowledgeMode.intValue() == Session.SESSION_TRANSACTED) {
containerDef.getPropertyValues().addPropertyValue("sessionTransacted", Boolean.TRUE);
}
else {
containerDef.getPropertyValues().addPropertyValue("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().addPropertyValue("transactionManager",
new RuntimeBeanReference(transactionManagerBeanName));
}
}
int[] concurrency = parseConcurrency(containerEle, parserContext);
if (concurrency != null) {
if (containerType.startsWith("default")) {
containerDef.getPropertyValues().addPropertyValue("concurrentConsumers", new Integer(concurrency[0]));
containerDef.getPropertyValues().addPropertyValue("maxConcurrentConsumers", new Integer(concurrency[1]));
}
else {
containerDef.getPropertyValues().addPropertyValue("concurrentConsumers", new Integer(concurrency[1]));
}
}
String prefetch = containerEle.getAttribute(PREFETCH_ATTRIBUTE);
if (StringUtils.hasText(prefetch)) {
if (containerType.startsWith("default")) {
containerDef.getPropertyValues().addPropertyValue("maxMessagesPerTask", new Integer(prefetch));
}
}
return containerDef;
}
protected boolean indicatesPubSub(BeanDefinition containerDef) {
return indicatesPubSubConfig(containerDef);
}
protected boolean indicatesJms102(BeanDefinition containerDef) {
return containerDef.getBeanClassName().endsWith("102");
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2002-2007 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.xml.NamespaceHandlerSupport;
/**
* A {@link org.springframework.beans.factory.xml.NamespaceHandler}
* for the JMS namespace.
*
* @author Mark Fisher
* @author Juergen Hoeller
* @since 2.5
*/
public class JmsNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("listener-container", new JmsListenerContainerParser());
registerBeanDefinitionParser("jca-listener-container", new JcaListenerContainerParser());
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Support package for declarative messaging configuration,
with XML schema being the primary configuration format.
</body>
</html>

View File

@ -0,0 +1,439 @@
<?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:xsi="http://www.w3.org/2001/XMLSchema-instance"
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"/>
<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.
]]></xsd:documentation>
</xsd:annotation>
<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.
]]></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="container-type" default="default">
<xsd:annotation>
<xsd:documentation><![CDATA[
The type of this listener container: "default" or "simple", choosing
between DefaultMessageListenerContainer and SimpleMessageListenerContainer.
The "102" suffix adapts to a JMS provider that implements JMS 1.0.2 only.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="default"/>
<xsd:enumeration value="default102"/>
<xsd:enumeration value="simple"/>
<xsd:enumeration value="simple102"/>
</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 the Spring TaskExecutor for the JMS listener invokers.
Default is a SimpleAsyncTaskExecutor, using internally managed threads.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.core.task.TaskExecutor"/>
</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="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:NMTOKEN">
<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:NMTOKEN">
<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:NMTOKEN">
<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:int">
<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: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).
]]></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="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:NMTOKEN">
<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:NMTOKEN">
<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:int">
<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:complexType>
</xsd:element>
<xsd:complexType name="listenerType">
<xsd:attribute name="id" type="xsd:ID">
<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,90 @@
/*
* Copyright 2002-2008 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.connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueReceiver;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;
/**
* JMS MessageConsumer decorator that adapts all calls
* to a shared MessageConsumer instance underneath.
*
* @author Juergen Hoeller
* @since 2.5.6
*/
class CachedMessageConsumer implements MessageConsumer, QueueReceiver, TopicSubscriber {
protected final MessageConsumer target;
public CachedMessageConsumer(MessageConsumer target) {
this.target = target;
}
public String getMessageSelector() throws JMSException {
return this.target.getMessageSelector();
}
public Queue getQueue() throws JMSException {
return (this.target instanceof QueueReceiver ? ((QueueReceiver) this.target).getQueue() : null);
}
public Topic getTopic() throws JMSException {
return (this.target instanceof TopicSubscriber ? ((TopicSubscriber) this.target).getTopic() : null);
}
public boolean getNoLocal() throws JMSException {
return (this.target instanceof TopicSubscriber && ((TopicSubscriber) this.target).getNoLocal());
}
public MessageListener getMessageListener() throws JMSException {
return this.target.getMessageListener();
}
public void setMessageListener(MessageListener messageListener) throws JMSException {
this.target.setMessageListener(messageListener);
}
public Message receive() throws JMSException {
return this.target.receive();
}
public Message receive(long timeout) throws JMSException {
return this.target.receive(timeout);
}
public Message receiveNoWait() throws JMSException {
return this.target.receiveNoWait();
}
public void close() throws JMSException {
// It's a cached MessageConsumer...
}
public String toString() {
return "Cached JMS MessageConsumer: " + this.target;
}
}

View File

@ -0,0 +1,173 @@
/*
* Copyright 2002-2008 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.connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueSender;
import javax.jms.Topic;
import javax.jms.TopicPublisher;
/**
* JMS MessageProducer decorator that adapts calls to a shared MessageProducer
* instance underneath, managing QoS settings locally within the decorator.
*
* @author Juergen Hoeller
* @since 2.5.3
*/
class CachedMessageProducer implements MessageProducer, QueueSender, TopicPublisher {
private final MessageProducer target;
private Boolean originalDisableMessageID;
private Boolean originalDisableMessageTimestamp;
private int deliveryMode;
private int priority;
private long timeToLive;
public CachedMessageProducer(MessageProducer target) throws JMSException {
this.target = target;
this.deliveryMode = target.getDeliveryMode();
this.priority = target.getPriority();
this.timeToLive = target.getTimeToLive();
}
public void setDisableMessageID(boolean disableMessageID) throws JMSException {
if (this.originalDisableMessageID == null) {
this.originalDisableMessageID = Boolean.valueOf(this.target.getDisableMessageID());
}
this.target.setDisableMessageID(disableMessageID);
}
public boolean getDisableMessageID() throws JMSException {
return this.target.getDisableMessageID();
}
public void setDisableMessageTimestamp(boolean disableMessageTimestamp) throws JMSException {
if (this.originalDisableMessageTimestamp == null) {
this.originalDisableMessageTimestamp = Boolean.valueOf(this.target.getDisableMessageTimestamp());
}
this.target.setDisableMessageTimestamp(disableMessageTimestamp);
}
public boolean getDisableMessageTimestamp() throws JMSException {
return this.target.getDisableMessageTimestamp();
}
public void setDeliveryMode(int deliveryMode) {
this.deliveryMode = deliveryMode;
}
public int getDeliveryMode() {
return this.deliveryMode;
}
public void setPriority(int priority) {
this.priority = priority;
}
public int getPriority() {
return this.priority;
}
public void setTimeToLive(long timeToLive) {
this.timeToLive = timeToLive;
}
public long getTimeToLive() {
return this.timeToLive;
}
public Destination getDestination() throws JMSException {
return this.target.getDestination();
}
public Queue getQueue() throws JMSException {
return (Queue) this.target.getDestination();
}
public Topic getTopic() throws JMSException {
return (Topic) this.target.getDestination();
}
public void send(Message message) throws JMSException {
this.target.send(message, this.deliveryMode, this.priority, this.timeToLive);
}
public void send(Message message, int deliveryMode, int priority, long timeToLive) throws JMSException {
this.target.send(message, deliveryMode, priority, timeToLive);
}
public void send(Destination destination, Message message) throws JMSException {
this.target.send(destination, message, this.deliveryMode, this.priority, this.timeToLive);
}
public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive) throws JMSException {
this.target.send(destination, message, deliveryMode, priority, timeToLive);
}
public void send(Queue queue, Message message) throws JMSException {
this.target.send(queue, message, this.deliveryMode, this.priority, this.timeToLive);
}
public void send(Queue queue, Message message, int deliveryMode, int priority, long timeToLive) throws JMSException {
this.target.send(queue, message, deliveryMode, priority, timeToLive);
}
public void publish(Message message) throws JMSException {
this.target.send(message, this.deliveryMode, this.priority, this.timeToLive);
}
public void publish(Message message, int deliveryMode, int priority, long timeToLive) throws JMSException {
this.target.send(message, deliveryMode, priority, timeToLive);
}
public void publish(Topic topic, Message message) throws JMSException {
this.target.send(topic, message, this.deliveryMode, this.priority, this.timeToLive);
}
public void publish(Topic topic, Message message, int deliveryMode, int priority, long timeToLive) throws JMSException {
this.target.send(topic, message, deliveryMode, priority, timeToLive);
}
public void close() throws JMSException {
// It's a cached MessageProducer... reset properties only.
if (this.originalDisableMessageID != null) {
this.target.setDisableMessageID(this.originalDisableMessageID.booleanValue());
this.originalDisableMessageID = null;
}
if (this.originalDisableMessageTimestamp != null) {
this.target.setDisableMessageTimestamp(this.originalDisableMessageTimestamp.booleanValue());
this.originalDisableMessageTimestamp = null;
}
}
public String toString() {
return "Cached JMS MessageProducer: " + this.target;
}
}

View File

@ -0,0 +1,468 @@
/*
* Copyright 2002-2008 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.connection;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicSession;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@link SingleConnectionFactory} subclass that adds {@link javax.jms.Session}
* caching as well {@link javax.jms.MessageProducer} caching. This ConnectionFactory
* also switches the {@link #setReconnectOnException "reconnectOnException" property}
* to "true" by default, allowing for automatic recovery of the underlying Connection.
*
* <p>By default, only one single Session will be cached, with further requested
* Sessions being created and disposed on demand. Consider raising the
* {@link #setSessionCacheSize "sessionCacheSize" value} in case of a
* high-concurrency environment.
*
* <p><b>NOTE: This ConnectionFactory decorator requires JMS 1.1 or higher.</b>
* You may use it through the JMS 1.0.2 API; however, the target JMS driver
* needs to be compliant with JMS 1.1.
*
* <p>When using the JMS 1.0.2 API, this ConnectionFactory will switch
* into queue/topic mode according to the JMS API methods used at runtime:
* <code>createQueueConnection</code> and <code>createTopicConnection</code> will
* lead to queue/topic mode, respectively; generic <code>createConnection</code>
* calls will lead to a JMS 1.1 connection which is able to serve both modes.
*
* <p><b>NOTE: This ConnectionFactory requires explicit closing of all Sessions
* obtained from its shared Connection.</b> This is the usual recommendation for
* native JMS access code anyway. However, with this ConnectionFactory, its use
* is mandatory in order to actually allow for Session reuse.
*
* <p>Note also that MessageConsumers obtained from a cached Session won't get
* closed until the Session will eventually be removed from the pool. This may
* lead to semantic side effects in some cases. For a durable subscriber, the
* logical <code>Session.close()</code> call will also close the subscription.
* Re-registering a durable consumer for the same subscription on the same
* Session handle is not supported; close and reobtain a cached Session first.
*
* @author Juergen Hoeller
* @since 2.5.3
*/
public class CachingConnectionFactory extends SingleConnectionFactory {
private int sessionCacheSize = 1;
private boolean cacheProducers = true;
private boolean cacheConsumers = true;
private volatile boolean active = true;
private final Map cachedSessions = new HashMap();
/**
* Create a new CachingConnectionFactory for bean-style usage.
* @see #setTargetConnectionFactory
*/
public CachingConnectionFactory() {
super();
setReconnectOnException(true);
}
/**
* Create a new CachingConnectionFactory for the given target
* ConnectionFactory.
* @param targetConnectionFactory the target ConnectionFactory
*/
public CachingConnectionFactory(ConnectionFactory targetConnectionFactory) {
super(targetConnectionFactory);
setReconnectOnException(true);
}
/**
* Specify the desired size for the JMS Session cache (per JMS Session type).
* <p>This cache size is the maximum limit for the number of cached Sessions
* per session acknowledgement type (auto, client, dups_ok, transacted).
* As a consequence, the actual number of cached Sessions may be up to
* four times as high as the specified value - in the unlikely case
* of mixing and matching different acknowledgement types.
* <p>Default is 1: caching a single Session, (re-)creating further ones on
* demand. Specify a number like 10 if you'd like to raise the number of cached
* Sessions; that said, 1 may be sufficient for low-concurrency scenarios.
* @see #setCacheProducers
*/
public void setSessionCacheSize(int sessionCacheSize) {
Assert.isTrue(sessionCacheSize >= 1, "Session cache size must be 1 or higher");
this.sessionCacheSize = sessionCacheSize;
}
/**
* Return the desired size for the JMS Session cache (per JMS Session type).
*/
public int getSessionCacheSize() {
return this.sessionCacheSize;
}
/**
* Specify whether to cache JMS MessageProducers per JMS Session instance
* (more specifically: one MessageProducer per Destination and Session).
* <p>Default is "true". Switch this to "false" in order to always
* recreate MessageProducers on demand.
*/
public void setCacheProducers(boolean cacheProducers) {
this.cacheProducers = cacheProducers;
}
/**
* Return whether to cache JMS MessageProducers per JMS Session instance.
*/
public boolean isCacheProducers() {
return this.cacheProducers;
}
/**
* Specify whether to cache JMS MessageConsumers per JMS Session instance
* (more specifically: one MessageConsumer per Destination, selector String
* and Session). Note that durable subscribers will only be cached until
* logical closing of the Session handle.
* <p>Default is "true". Switch this to "false" in order to always
* recreate MessageConsumers on demand.
*/
public void setCacheConsumers(boolean cacheConsumers) {
this.cacheConsumers = cacheConsumers;
}
/**
* Return whether to cache JMS MessageConsumers per JMS Session instance.
*/
public boolean isCacheConsumers() {
return this.cacheConsumers;
}
/**
* Resets the Session cache as well.
*/
public void resetConnection() {
this.active = false;
synchronized (this.cachedSessions) {
for (Iterator it = this.cachedSessions.values().iterator(); it.hasNext();) {
LinkedList sessionList = (LinkedList) it.next();
synchronized (sessionList) {
for (Iterator it2 = sessionList.iterator(); it2.hasNext();) {
Session session = (Session) it2.next();
try {
session.close();
}
catch (Throwable ex) {
logger.trace("Could not close cached JMS Session", ex);
}
}
}
}
this.cachedSessions.clear();
}
this.active = true;
// Now proceed with actual closing of the shared Connection...
super.resetConnection();
}
/**
* Checks for a cached Session for the given mode.
*/
protected Session getSession(Connection con, Integer mode) throws JMSException {
LinkedList sessionList = null;
synchronized (this.cachedSessions) {
sessionList = (LinkedList) this.cachedSessions.get(mode);
if (sessionList == null) {
sessionList = new LinkedList();
this.cachedSessions.put(mode, sessionList);
}
}
Session session = null;
synchronized (sessionList) {
if (!sessionList.isEmpty()) {
session = (Session) sessionList.removeFirst();
}
}
if (session != null) {
if (logger.isTraceEnabled()) {
logger.trace("Found cached JMS Session for mode " + mode + ": " +
(session instanceof SessionProxy ? ((SessionProxy) session).getTargetSession() : session));
}
}
else {
Session targetSession = createSession(con, mode);
if (logger.isDebugEnabled()) {
logger.debug("Creating cached JMS Session for mode " + mode + ": " + targetSession);
}
session = getCachedSessionProxy(targetSession, sessionList);
}
return session;
}
/**
* Wrap the given Session with a proxy that delegates every method call to it
* but adapts close calls. This is useful for allowing application code to
* handle a special framework Session just like an ordinary Session.
* @param target the original Session to wrap
* @param sessionList the List of cached Sessions that the given Session belongs to
* @return the wrapped Session
*/
protected Session getCachedSessionProxy(Session target, LinkedList sessionList) {
List classes = new ArrayList(3);
classes.add(SessionProxy.class);
if (target instanceof QueueSession) {
classes.add(QueueSession.class);
}
if (target instanceof TopicSession) {
classes.add(TopicSession.class);
}
return (Session) Proxy.newProxyInstance(
SessionProxy.class.getClassLoader(),
(Class[]) classes.toArray(new Class[classes.size()]),
new CachedSessionInvocationHandler(target, sessionList));
}
/**
* Invocation handler for a cached JMS Session proxy.
*/
private class CachedSessionInvocationHandler implements InvocationHandler {
private final Session target;
private final LinkedList sessionList;
private final Map cachedProducers = new HashMap();
private final Map cachedConsumers = new HashMap();
private boolean transactionOpen = false;
public CachedSessionInvocationHandler(Session target, LinkedList sessionList) {
this.target = target;
this.sessionList = sessionList;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("equals")) {
// Only consider equal when proxies are identical.
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
}
else if (methodName.equals("hashCode")) {
// Use hashCode of Session proxy.
return new Integer(System.identityHashCode(proxy));
}
else if (methodName.equals("toString")) {
return "Cached JMS Session: " + this.target;
}
else if (methodName.equals("close")) {
// Handle close method: don't pass the call on.
if (active) {
synchronized (this.sessionList) {
if (this.sessionList.size() < getSessionCacheSize()) {
logicalClose(proxy);
// Remain open in the session list.
return null;
}
}
}
// If we get here, we're supposed to shut down.
physicalClose();
return null;
}
else if (methodName.equals("getTargetSession")) {
// Handle getTargetSession method: return underlying Session.
return this.target;
}
else if (methodName.equals("commit") || methodName.equals("rollback")) {
this.transactionOpen = false;
}
else {
this.transactionOpen = true;
if ((methodName.equals("createProducer") || methodName.equals("createSender") ||
methodName.equals("createPublisher")) && isCacheProducers()) {
return getCachedProducer((Destination) args[0]);
}
else if ((methodName.equals("createConsumer") || methodName.equals("createReceiver") ||
methodName.equals("createSubscriber")) && isCacheConsumers()) {
return getCachedConsumer((Destination) args[0], (args.length > 1 ? (String) args[1] : null),
(args.length > 2 && ((Boolean) args[2]).booleanValue()), null);
}
else if (methodName.equals("createDurableSubscriber") && isCacheConsumers()) {
return getCachedConsumer((Destination) args[0], (args.length > 2 ? (String) args[2] : null),
(args.length > 3 && ((Boolean) args[3]).booleanValue()), (String) args[1]);
}
}
try {
return method.invoke(this.target, args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
private MessageProducer getCachedProducer(Destination dest) throws JMSException {
MessageProducer producer = (MessageProducer) this.cachedProducers.get(dest);
if (producer != null) {
if (logger.isTraceEnabled()) {
logger.trace("Found cached JMS MessageProducer for destination [" + dest + "]: " + producer);
}
}
else {
producer = this.target.createProducer(dest);
if (logger.isDebugEnabled()) {
logger.debug("Creating cached JMS MessageProducer for destination [" + dest + "]: " + producer);
}
this.cachedProducers.put(dest, producer);
}
return new CachedMessageProducer(producer);
}
private MessageConsumer getCachedConsumer(
Destination dest, String selector, boolean noLocal, String subscription) throws JMSException {
Object cacheKey = new ConsumerCacheKey(dest, selector, noLocal, subscription);
MessageConsumer consumer = (MessageConsumer) this.cachedConsumers.get(cacheKey);
if (consumer != null) {
if (logger.isTraceEnabled()) {
logger.trace("Found cached JMS MessageConsumer for destination [" + dest + "]: " + consumer);
}
}
else {
if (dest instanceof Topic) {
consumer = (subscription != null ?
this.target.createDurableSubscriber((Topic) dest, subscription, selector, noLocal) :
this.target.createConsumer(dest, selector, noLocal));
}
else {
consumer = this.target.createConsumer(dest, selector);
}
if (logger.isDebugEnabled()) {
logger.debug("Creating cached JMS MessageConsumer for destination [" + dest + "]: " + consumer);
}
this.cachedConsumers.put(cacheKey, consumer);
}
return new CachedMessageConsumer(consumer);
}
private void logicalClose(Object proxy) throws JMSException {
// Preserve rollback-on-close semantics.
if (this.transactionOpen && this.target.getTransacted()) {
this.transactionOpen = false;
this.target.rollback();
}
// Physically close durable subscribers at time of Session close call.
for (Iterator it = this.cachedConsumers.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
ConsumerCacheKey key = (ConsumerCacheKey) entry.getKey();
if (key.subscription != null) {
((MessageConsumer) entry.getValue()).close();
it.remove();
}
}
// Allow for multiple close calls...
if (!this.sessionList.contains(proxy)) {
if (logger.isTraceEnabled()) {
logger.trace("Returning cached Session: " + this.target);
}
this.sessionList.addLast(proxy);
}
}
private void physicalClose() throws JMSException {
if (logger.isDebugEnabled()) {
logger.debug("Closing cached Session: " + this.target);
}
// Explicitly close all MessageProducers and MessageConsumers that
// this Session happens to cache...
try {
for (Iterator it = this.cachedProducers.values().iterator(); it.hasNext();) {
((MessageProducer) it.next()).close();
}
for (Iterator it = this.cachedConsumers.values().iterator(); it.hasNext();) {
((MessageConsumer) it.next()).close();
}
}
finally {
this.cachedProducers.clear();
this.cachedConsumers.clear();
// Now actually close the Session.
this.target.close();
}
}
}
/**
* Simple wrapper class around a Destination and other consumer attributes.
* Used as the key when caching consumers.
*/
private static class ConsumerCacheKey {
private final Destination destination;
private final String selector;
private final boolean noLocal;
private final String subscription;
private ConsumerCacheKey(Destination destination, String selector, boolean noLocal, String subscription) {
this.destination = destination;
this.selector = selector;
this.noLocal = noLocal;
this.subscription = subscription;
}
public boolean equals(Object other) {
if (other == this) {
return true;
}
ConsumerCacheKey otherKey = (ConsumerCacheKey) other;
return (this.destination.equals(otherKey.destination) &&
ObjectUtils.nullSafeEquals(this.selector, otherKey.selector) &&
this.noLocal == otherKey.noLocal &&
ObjectUtils.nullSafeEquals(this.subscription, otherKey.subscription));
}
public int hashCode() {
return this.destination.hashCode();
}
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2002-2006 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.connection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import org.springframework.util.Assert;
/**
* Implementation of the JMS ExceptionListener interface that supports chaining,
* allowing the addition of multiple ExceptionListener instances in order.
*
* @author Juergen Hoeller
* @since 2.0
*/
public class ChainedExceptionListener implements ExceptionListener {
/** List of ExceptionListeners */
private final List delegates = new ArrayList(2);
/**
* Add an ExceptionListener to the chained delegate list.
*/
public final void addDelegate(ExceptionListener listener) {
Assert.notNull(listener, "ExceptionListener must not be null");
this.delegates.add(listener);
}
/**
* Return all registered ExceptionListener delegates (as array).
*/
public final ExceptionListener[] getDelegates() {
return (ExceptionListener[]) this.delegates.toArray(new ExceptionListener[this.delegates.size()]);
}
public void onException(JMSException ex) {
for (Iterator it = this.delegates.iterator(); it.hasNext();) {
ExceptionListener listener = (ExceptionListener) it.next();
listener.onException(ex);
}
}
}

View File

@ -0,0 +1,417 @@
/*
* Copyright 2002-2008 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.connection;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.support.ResourceHolder;
import org.springframework.transaction.support.ResourceHolderSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* Helper class for managing a JMS {@link javax.jms.ConnectionFactory}, in particular
* for obtaining transactional JMS resources for a given ConnectionFactory.
*
* <p>Mainly for internal use within the framework. Used by
* {@link org.springframework.jms.core.JmsTemplate} as well as
* {@link org.springframework.jms.listener.DefaultMessageListenerContainer}.
*
* @author Juergen Hoeller
* @since 2.0
* @see SmartConnectionFactory
*/
public abstract class ConnectionFactoryUtils {
private static final Log logger = LogFactory.getLog(ConnectionFactoryUtils.class);
/**
* Release the given Connection, stopping it (if necessary) and eventually closing it.
* <p>Checks {@link SmartConnectionFactory#shouldStop}, if available.
* This is essentially a more sophisticated version of
* {@link org.springframework.jms.support.JmsUtils#closeConnection}.
* @param con the Connection to release
* (if this is <code>null</code>, the call will be ignored)
* @param cf the ConnectionFactory that the Connection was obtained from
* (may be <code>null</code>)
* @param started whether the Connection might have been started by the application
* @see SmartConnectionFactory#shouldStop
* @see org.springframework.jms.support.JmsUtils#closeConnection
*/
public static void releaseConnection(Connection con, ConnectionFactory cf, boolean started) {
if (con == null) {
return;
}
if (started && cf instanceof SmartConnectionFactory && ((SmartConnectionFactory) cf).shouldStop(con)) {
try {
con.stop();
}
catch (Throwable ex) {
logger.debug("Could not stop JMS Connection before closing it", ex);
}
}
try {
con.close();
}
catch (Throwable ex) {
logger.debug("Could not close JMS Connection", ex);
}
}
/**
* Return the innermost target Session of the given Session. If the given
* Session is a proxy, it will be unwrapped until a non-proxy Session is
* found. Otherwise, the passed-in Session will be returned as-is.
* @param session the Session proxy to unwrap
* @return the innermost target Session, or the passed-in one if no proxy
* @see SessionProxy#getTargetSession()
*/
public static Session getTargetSession(Session session) {
Session sessionToUse = session;
while (sessionToUse instanceof SessionProxy) {
sessionToUse = ((SessionProxy) sessionToUse).getTargetSession();
}
return sessionToUse;
}
/**
* Determine whether the given JMS Session is transactional, that is,
* bound to the current thread by Spring's transaction facilities.
* @param session the JMS Session to check
* @param cf the JMS ConnectionFactory that the Session originated from
* @return whether the Session is transactional
*/
public static boolean isSessionTransactional(Session session, ConnectionFactory cf) {
if (session == null || cf == null) {
return false;
}
JmsResourceHolder resourceHolder = (JmsResourceHolder) TransactionSynchronizationManager.getResource(cf);
return (resourceHolder != null && resourceHolder.containsSession(session));
}
/**
* Obtain a JMS Session that is synchronized with the current transaction, if any.
* @param cf the ConnectionFactory to obtain a Session for
* @param existingCon the existing JMS Connection to obtain a Session for
* (may be <code>null</code>)
* @param synchedLocalTransactionAllowed whether to allow for a local JMS transaction
* that is synchronized with a Spring-managed transaction (where the main transaction
* might be a JDBC-based one for a specific DataSource, for example), with the JMS
* transaction committing right after the main transaction. If not allowed, the given
* ConnectionFactory needs to handle transaction enlistment underneath the covers.
* @return the transactional Session, or <code>null</code> if none found
* @throws JMSException in case of JMS failure
*/
public static Session getTransactionalSession(
final ConnectionFactory cf, final Connection existingCon, final boolean synchedLocalTransactionAllowed)
throws JMSException {
return doGetTransactionalSession(cf, new ResourceFactory() {
public Session getSession(JmsResourceHolder holder) {
return holder.getSession(Session.class, existingCon);
}
public Connection getConnection(JmsResourceHolder holder) {
return (existingCon != null ? existingCon : holder.getConnection());
}
public Connection createConnection() throws JMSException {
return cf.createConnection();
}
public Session createSession(Connection con) throws JMSException {
return con.createSession(synchedLocalTransactionAllowed, Session.AUTO_ACKNOWLEDGE);
}
public boolean isSynchedLocalTransactionAllowed() {
return synchedLocalTransactionAllowed;
}
}, true);
}
/**
* Obtain a JMS QueueSession that is synchronized with the current transaction, if any.
* <p>Mainly intended for use with the JMS 1.0.2 API.
* @param cf the ConnectionFactory to obtain a Session for
* @param existingCon the existing JMS Connection to obtain a Session for
* (may be <code>null</code>)
* @param synchedLocalTransactionAllowed whether to allow for a local JMS transaction
* that is synchronized with a Spring-managed transaction (where the main transaction
* might be a JDBC-based one for a specific DataSource, for example), with the JMS
* transaction committing right after the main transaction. If not allowed, the given
* ConnectionFactory needs to handle transaction enlistment underneath the covers.
* @return the transactional Session, or <code>null</code> if none found
* @throws JMSException in case of JMS failure
*/
public static QueueSession getTransactionalQueueSession(
final QueueConnectionFactory cf, final QueueConnection existingCon, final boolean synchedLocalTransactionAllowed)
throws JMSException {
return (QueueSession) doGetTransactionalSession(cf, new ResourceFactory() {
public Session getSession(JmsResourceHolder holder) {
return holder.getSession(QueueSession.class, existingCon);
}
public Connection getConnection(JmsResourceHolder holder) {
return (existingCon != null ? existingCon : holder.getConnection(QueueConnection.class));
}
public Connection createConnection() throws JMSException {
return cf.createQueueConnection();
}
public Session createSession(Connection con) throws JMSException {
return ((QueueConnection) con).createQueueSession(synchedLocalTransactionAllowed, Session.AUTO_ACKNOWLEDGE);
}
public boolean isSynchedLocalTransactionAllowed() {
return synchedLocalTransactionAllowed;
}
}, true);
}
/**
* Obtain a JMS TopicSession that is synchronized with the current transaction, if any.
* <p>Mainly intended for use with the JMS 1.0.2 API.
* @param cf the ConnectionFactory to obtain a Session for
* @param existingCon the existing JMS Connection to obtain a Session for
* (may be <code>null</code>)
* @param synchedLocalTransactionAllowed whether to allow for a local JMS transaction
* that is synchronized with a Spring-managed transaction (where the main transaction
* might be a JDBC-based one for a specific DataSource, for example), with the JMS
* transaction committing right after the main transaction. If not allowed, the given
* ConnectionFactory needs to handle transaction enlistment underneath the covers.
* @return the transactional Session, or <code>null</code> if none found
* @throws JMSException in case of JMS failure
*/
public static TopicSession getTransactionalTopicSession(
final TopicConnectionFactory cf, final TopicConnection existingCon, final boolean synchedLocalTransactionAllowed)
throws JMSException {
return (TopicSession) doGetTransactionalSession(cf, new ResourceFactory() {
public Session getSession(JmsResourceHolder holder) {
return holder.getSession(TopicSession.class, existingCon);
}
public Connection getConnection(JmsResourceHolder holder) {
return (existingCon != null ? existingCon : holder.getConnection(TopicConnection.class));
}
public Connection createConnection() throws JMSException {
return cf.createTopicConnection();
}
public Session createSession(Connection con) throws JMSException {
return ((TopicConnection) con).createTopicSession(synchedLocalTransactionAllowed, Session.AUTO_ACKNOWLEDGE);
}
public boolean isSynchedLocalTransactionAllowed() {
return synchedLocalTransactionAllowed;
}
}, true);
}
/**
* Obtain a JMS Session that is synchronized with the current transaction, if any.
* <p>This <code>doGetTransactionalSession</code> variant always starts the underlying
* JMS Connection, assuming that the Session will be used for receiving messages.
* @param connectionFactory the JMS ConnectionFactory to bind for
* (used as TransactionSynchronizationManager key)
* @param resourceFactory the ResourceFactory to use for extracting or creating
* JMS resources
* @return the transactional Session, or <code>null</code> if none found
* @throws JMSException in case of JMS failure
* @see #doGetTransactionalSession(javax.jms.ConnectionFactory, ResourceFactory, boolean)
*/
public static Session doGetTransactionalSession(
ConnectionFactory connectionFactory, ResourceFactory resourceFactory) throws JMSException {
return doGetTransactionalSession(connectionFactory, resourceFactory, true);
}
/**
* Obtain a JMS Session that is synchronized with the current transaction, if any.
* @param connectionFactory the JMS ConnectionFactory to bind for
* (used as TransactionSynchronizationManager key)
* @param resourceFactory the ResourceFactory to use for extracting or creating
* JMS resources
* @param startConnection whether the underlying JMS Connection approach should be
* started in order to allow for receiving messages. Note that a reused Connection
* may already have been started before, even if this flag is <code>false</code>.
* @return the transactional Session, or <code>null</code> if none found
* @throws JMSException in case of JMS failure
*/
public static Session doGetTransactionalSession(
ConnectionFactory connectionFactory, ResourceFactory resourceFactory, boolean startConnection)
throws JMSException {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
Assert.notNull(resourceFactory, "ResourceFactory must not be null");
JmsResourceHolder resourceHolder =
(JmsResourceHolder) TransactionSynchronizationManager.getResource(connectionFactory);
if (resourceHolder != null) {
Session session = resourceFactory.getSession(resourceHolder);
if (session != null) {
if (startConnection) {
Connection con = resourceFactory.getConnection(resourceHolder);
if (con != null) {
con.start();
}
}
return session;
}
if (resourceHolder.isFrozen()) {
return null;
}
}
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
return null;
}
JmsResourceHolder resourceHolderToUse = resourceHolder;
if (resourceHolderToUse == null) {
resourceHolderToUse = new JmsResourceHolder(connectionFactory);
}
Connection con = resourceFactory.getConnection(resourceHolderToUse);
Session session = null;
try {
boolean isExistingCon = (con != null);
if (!isExistingCon) {
con = resourceFactory.createConnection();
resourceHolderToUse.addConnection(con);
}
session = resourceFactory.createSession(con);
resourceHolderToUse.addSession(session, con);
if (startConnection) {
con.start();
}
}
catch (JMSException ex) {
if (session != null) {
try {
session.close();
}
catch (Throwable ex2) {
// ignore
}
}
if (con != null) {
try {
con.close();
}
catch (Throwable ex2) {
// ignore
}
}
throw ex;
}
if (resourceHolderToUse != resourceHolder) {
TransactionSynchronizationManager.registerSynchronization(
new JmsResourceSynchronization(
resourceHolderToUse, connectionFactory, resourceFactory.isSynchedLocalTransactionAllowed()));
resourceHolderToUse.setSynchronizedWithTransaction(true);
TransactionSynchronizationManager.bindResource(connectionFactory, resourceHolderToUse);
}
return session;
}
/**
* Callback interface for resource creation.
* Serving as argument for the <code>doGetTransactionalSession</code> method.
*/
public interface ResourceFactory {
/**
* Fetch an appropriate Session from the given JmsResourceHolder.
* @param holder the JmsResourceHolder
* @return an appropriate Session fetched from the holder,
* or <code>null</code> if none found
*/
Session getSession(JmsResourceHolder holder);
/**
* Fetch an appropriate Connection from the given JmsResourceHolder.
* @param holder the JmsResourceHolder
* @return an appropriate Connection fetched from the holder,
* or <code>null</code> if none found
*/
Connection getConnection(JmsResourceHolder holder);
/**
* Create a new JMS Connection for registration with a JmsResourceHolder.
* @return the new JMS Connection
* @throws JMSException if thrown by JMS API methods
*/
Connection createConnection() throws JMSException;
/**
* Create a new JMS Session for registration with a JmsResourceHolder.
* @param con the JMS Connection to create a Session for
* @return the new JMS Session
* @throws JMSException if thrown by JMS API methods
*/
Session createSession(Connection con) throws JMSException;
/**
* Return whether to allow for a local JMS transaction that is synchronized with
* a Spring-managed transaction (where the main transaction might be a JDBC-based
* one for a specific DataSource, for example), with the JMS transaction
* committing right after the main transaction.
* @return whether to allow for synchronizing a local JMS transaction
*/
boolean isSynchedLocalTransactionAllowed();
}
/**
* Callback for resource cleanup at the end of a non-native JMS transaction
* (e.g. when participating in a JtaTransactionManager transaction).
* @see org.springframework.transaction.jta.JtaTransactionManager
*/
private static class JmsResourceSynchronization extends ResourceHolderSynchronization {
private final boolean transacted;
public JmsResourceSynchronization(JmsResourceHolder resourceHolder, Object resourceKey, boolean transacted) {
super(resourceHolder, resourceKey);
this.transacted = transacted;
}
protected boolean shouldReleaseBeforeCompletion() {
return !this.transacted;
}
protected void processResourceAfterCommit(ResourceHolder resourceHolder) {
try {
((JmsResourceHolder) resourceHolder).commitAll();
}
catch (JMSException ex) {
throw new SynchedLocalTransactionFailedException("Local JMS transaction failed to commit", ex);
}
}
protected void releaseResource(ResourceHolder resourceHolder, Object resourceKey) {
((JmsResourceHolder) resourceHolder).closeAll();
}
}
}

View File

@ -0,0 +1,163 @@
/*
* Copyright 2002-2007 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.connection;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* {@link javax.jms.ConnectionFactory} implementation that delegates all calls
* to a given target {@link javax.jms.ConnectionFactory}, adapting specific
* <code>create(Queue/Topic)Connection</code> calls to the target ConnectionFactory
* if necessary (e.g. when running JMS 1.0.2 API based code against a generic
* JMS 1.1 ConnectionFactory, such as ActiveMQ's PooledConnectionFactory).
*
* <p>This class allows for being subclassed, with subclasses overriding only
* those methods (such as {@link #createConnection()}) that should not simply
* delegate to the target ConnectionFactory.
*
* <p>Can also be defined as-is, wrapping a specific target ConnectionFactory,
* using the "shouldStopConnections" flag to indicate whether Connections
* obtained from the target factory are supposed to be stopped before closed.
* The latter may be necessary for some connection pools that simply return
* released connections to the pool, not stopping them while they sit in the pool.
*
* @author Juergen Hoeller
* @since 2.0.2
* @see #createConnection()
* @see #setShouldStopConnections
* @see ConnectionFactoryUtils#releaseConnection
*/
public class DelegatingConnectionFactory
implements SmartConnectionFactory, QueueConnectionFactory, TopicConnectionFactory, InitializingBean {
private ConnectionFactory targetConnectionFactory;
private boolean shouldStopConnections = false;
/**
* Set the target ConnectionFactory that this ConnectionFactory should delegate to.
*/
public void setTargetConnectionFactory(ConnectionFactory targetConnectionFactory) {
Assert.notNull(targetConnectionFactory, "'targetConnectionFactory' must not be null");
this.targetConnectionFactory = targetConnectionFactory;
}
/**
* Return the target ConnectionFactory that this ConnectionFactory delegates to.
*/
public ConnectionFactory getTargetConnectionFactory() {
return this.targetConnectionFactory;
}
/**
* Indicate whether Connections obtained from the target factory are supposed
* to be stopped before closed ("true") or simply closed ("false").
* The latter may be necessary for some connection pools that simply return
* released connections to the pool, not stopping them while they sit in the pool.
* <p>Default is "false", simply closing Connections.
* @see ConnectionFactoryUtils#releaseConnection
*/
public void setShouldStopConnections(boolean shouldStopConnections) {
this.shouldStopConnections = shouldStopConnections;
}
public void afterPropertiesSet() {
if (getTargetConnectionFactory() == null) {
throw new IllegalArgumentException("'targetConnectionFactory' is required");
}
}
public Connection createConnection() throws JMSException {
return getTargetConnectionFactory().createConnection();
}
public Connection createConnection(String username, String password) throws JMSException {
return getTargetConnectionFactory().createConnection(username, password);
}
public QueueConnection createQueueConnection() throws JMSException {
ConnectionFactory cf = getTargetConnectionFactory();
if (cf instanceof QueueConnectionFactory) {
return ((QueueConnectionFactory) cf).createQueueConnection();
}
else {
Connection con = cf.createConnection();
if (!(con instanceof QueueConnection)) {
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is not a QueueConnectionFactory");
}
return (QueueConnection) con;
}
}
public QueueConnection createQueueConnection(String username, String password) throws JMSException {
ConnectionFactory cf = getTargetConnectionFactory();
if (cf instanceof QueueConnectionFactory) {
return ((QueueConnectionFactory) cf).createQueueConnection(username, password);
}
else {
Connection con = cf.createConnection(username, password);
if (!(con instanceof QueueConnection)) {
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is not a QueueConnectionFactory");
}
return (QueueConnection) con;
}
}
public TopicConnection createTopicConnection() throws JMSException {
ConnectionFactory cf = getTargetConnectionFactory();
if (cf instanceof TopicConnectionFactory) {
return ((TopicConnectionFactory) cf).createTopicConnection();
}
else {
Connection con = cf.createConnection();
if (!(con instanceof TopicConnection)) {
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is not a TopicConnectionFactory");
}
return (TopicConnection) con;
}
}
public TopicConnection createTopicConnection(String username, String password) throws JMSException {
ConnectionFactory cf = getTargetConnectionFactory();
if (cf instanceof TopicConnectionFactory) {
return ((TopicConnectionFactory) cf).createTopicConnection(username, password);
}
else {
Connection con = cf.createConnection(username, password);
if (!(con instanceof TopicConnection)) {
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is not a TopicConnectionFactory");
}
return (TopicConnection) con;
}
}
public boolean shouldStop(Connection con) {
return this.shouldStopConnections;
}
}

View File

@ -0,0 +1,211 @@
/*
* Copyright 2002-2008 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.connection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.TransactionInProgressException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.support.ResourceHolderSupport;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* JMS resource holder, wrapping a JMS Connection and a JMS Session.
* JmsTransactionManager binds instances of this class to the thread,
* for a given JMS ConnectionFactory.
*
* <p>Note: This is an SPI class, not intended to be used by applications.
*
* @author Juergen Hoeller
* @since 1.1
* @see JmsTransactionManager
* @see org.springframework.jms.core.JmsTemplate
*/
public class JmsResourceHolder extends ResourceHolderSupport {
private static final Log logger = LogFactory.getLog(JmsResourceHolder.class);
private ConnectionFactory connectionFactory;
private boolean frozen = false;
private final List connections = new LinkedList();
private final List sessions = new LinkedList();
private final Map sessionsPerConnection = new HashMap();
/**
* Create a new JmsResourceHolder that is open for resources to be added.
* @see #addConnection
* @see #addSession
*/
public JmsResourceHolder() {
}
/**
* Create a new JmsResourceHolder that is open for resources to be added.
* @param connectionFactory the JMS ConnectionFactory that this
* resource holder is associated with (may be <code>null</code>)
*/
public JmsResourceHolder(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
/**
* Create a new JmsResourceHolder for the given JMS Session.
* @param session the JMS Session
*/
public JmsResourceHolder(Session session) {
addSession(session);
this.frozen = true;
}
/**
* Create a new JmsResourceHolder for the given JMS resources.
* @param connection the JMS Connection
* @param session the JMS Session
*/
public JmsResourceHolder(Connection connection, Session session) {
addConnection(connection);
addSession(session, connection);
this.frozen = true;
}
/**
* Create a new JmsResourceHolder for the given JMS resources.
* @param connectionFactory the JMS ConnectionFactory that this
* resource holder is associated with (may be <code>null</code>)
* @param connection the JMS Connection
* @param session the JMS Session
*/
public JmsResourceHolder(ConnectionFactory connectionFactory, Connection connection, Session session) {
this.connectionFactory = connectionFactory;
addConnection(connection);
addSession(session, connection);
this.frozen = true;
}
public final boolean isFrozen() {
return this.frozen;
}
public final void addConnection(Connection connection) {
Assert.isTrue(!this.frozen, "Cannot add Connection because JmsResourceHolder is frozen");
Assert.notNull(connection, "Connection must not be null");
if (!this.connections.contains(connection)) {
this.connections.add(connection);
}
}
public final void addSession(Session session) {
addSession(session, null);
}
public final void addSession(Session session, Connection connection) {
Assert.isTrue(!this.frozen, "Cannot add Session because JmsResourceHolder is frozen");
Assert.notNull(session, "Session must not be null");
if (!this.sessions.contains(session)) {
this.sessions.add(session);
if (connection != null) {
List sessions = (List) this.sessionsPerConnection.get(connection);
if (sessions == null) {
sessions = new LinkedList();
this.sessionsPerConnection.put(connection, sessions);
}
sessions.add(session);
}
}
}
public boolean containsSession(Session session) {
return this.sessions.contains(session);
}
public Connection getConnection() {
return (!this.connections.isEmpty() ? (Connection) this.connections.get(0) : null);
}
public Connection getConnection(Class connectionType) {
return (Connection) CollectionUtils.findValueOfType(this.connections, connectionType);
}
public Session getSession() {
return (!this.sessions.isEmpty() ? (Session) this.sessions.get(0) : null);
}
public Session getSession(Class sessionType) {
return getSession(sessionType, null);
}
public Session getSession(Class sessionType, Connection connection) {
List sessions = this.sessions;
if (connection != null) {
sessions = (List) this.sessionsPerConnection.get(connection);
}
return (Session) CollectionUtils.findValueOfType(sessions, sessionType);
}
public void commitAll() throws JMSException {
for (Iterator it = this.sessions.iterator(); it.hasNext();) {
try {
((Session) it.next()).commit();
}
catch (TransactionInProgressException ex) {
// Ignore -> can only happen in case of a JTA transaction.
}
catch (javax.jms.IllegalStateException ex) {
// Ignore -> can only happen in case of a JTA transaction.
}
}
}
public void closeAll() {
for (Iterator it = this.sessions.iterator(); it.hasNext();) {
try {
((Session) it.next()).close();
}
catch (Throwable ex) {
logger.debug("Could not close synchronized JMS Session after transaction", ex);
}
}
for (Iterator it = this.connections.iterator(); it.hasNext();) {
Connection con = (Connection) it.next();
ConnectionFactoryUtils.releaseConnection(con, this.connectionFactory, true);
}
this.connections.clear();
this.sessions.clear();
this.sessionsPerConnection.clear();
}
}

View File

@ -0,0 +1,321 @@
/*
* Copyright 2002-2008 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.connection;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.TransactionRolledBackException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.InvalidIsolationLevelException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.UnexpectedRollbackException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.ResourceTransactionManager;
import org.springframework.transaction.support.SmartTransactionObject;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* {@link org.springframework.transaction.PlatformTransactionManager} implementation
* for a single JMS {@link javax.jms.ConnectionFactory}. Binds a JMS
* Connection/Session pair from the specified ConnectionFactory to the thread,
* potentially allowing for one thread-bound Session per ConnectionFactory.
*
* <p><b>NOTE:</b> This class requires a JMS 1.1+ provider because it builds on
* the domain-independent API. <b>Use the {@link JmsTransactionManager102} subclass
* for a JMS 1.0.2 provider, e.g. when running on a J2EE 1.3 server.</b>
*
* <p>This local strategy is an alternative to executing JMS operations within
* JTA transactions. Its advantage is that it is able to work in any environment,
* for example a standalone application or a test suite, with any message broker
* as target. However, this strategy is <i>not</i> able to provide XA transactions,
* for example in order to share transactions between messaging and database access.
* A full JTA/XA setup is required for XA transactions, typically using Spring's
* {@link org.springframework.transaction.jta.JtaTransactionManager} as strategy.
*
* <p>Application code is required to retrieve the transactional JMS Session via
* {@link ConnectionFactoryUtils#getTransactionalSession} instead of a standard
* J2EE-style {@link ConnectionFactory#createConnection()} call with subsequent
* Session creation. Spring's {@link org.springframework.jms.core.JmsTemplate}
* will autodetect a thread-bound Session and automatically participate in it.
*
* <p>Alternatively, you can allow application code to work with the standard
* J2EE-style lookup pattern on a ConnectionFactory, for example for legacy code
* that is not aware of Spring at all. In that case, define a
* {@link TransactionAwareConnectionFactoryProxy} for your target ConnectionFactory,
* which will automatically participate in Spring-managed transactions.
*
* <p><b>The use of {@link CachingConnectionFactory} as a target for this
* transaction manager is strongly recommended.</b> CachingConnectionFactory
* uses a single JMS Connection for all JMS access in order to avoid the overhead
* of repeated Connection creation, as well as maintaining a cache of Sessions.
* Each transaction will then share the same JMS Connection, while still using
* its own individual JMS Session.
*
* <p>The use of a <i>raw</i> target ConnectionFactory would not only be inefficient
* because of the lack of resource reuse. It might also lead to strange effects
* when your JMS driver doesn't accept <code>MessageProducer.close()</code> calls
* and/or <code>MessageConsumer.close()</code> calls before <code>Session.commit()</code>,
* with the latter supposed to commit all the messages that have been sent through the
* producer handle and received through the consumer handle. As a safe general solution,
* always pass in a {@link CachingConnectionFactory} into this transaction manager's
* {@link #setConnectionFactory "connectionFactory"} property.
*
* <p>Transaction synchronization is turned off by default, as this manager might
* be used alongside a datastore-based Spring transaction manager such as the
* JDBC {@link org.springframework.jdbc.datasource.DataSourceTransactionManager},
* which has stronger needs for synchronization.
*
* @author Juergen Hoeller
* @since 1.1
* @see ConnectionFactoryUtils#getTransactionalSession
* @see TransactionAwareConnectionFactoryProxy
* @see org.springframework.jms.core.JmsTemplate
*/
public class JmsTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
private ConnectionFactory connectionFactory;
/**
* Create a new JmsTransactionManager for bean-style usage.
* <p>Note: The ConnectionFactory has to be set before using the instance.
* This constructor can be used to prepare a JmsTemplate via a BeanFactory,
* typically setting the ConnectionFactory via setConnectionFactory.
* <p>Turns off transaction synchronization by default, as this manager might
* be used alongside a datastore-based Spring transaction manager like
* DataSourceTransactionManager, which has stronger needs for synchronization.
* Only one manager is allowed to drive synchronization at any point of time.
* @see #setConnectionFactory
* @see #setTransactionSynchronization
*/
public JmsTransactionManager() {
setTransactionSynchronization(SYNCHRONIZATION_NEVER);
}
/**
* Create a new JmsTransactionManager, given a ConnectionFactory.
* @param connectionFactory the ConnectionFactory to obtain connections from
*/
public JmsTransactionManager(ConnectionFactory connectionFactory) {
this();
setConnectionFactory(connectionFactory);
afterPropertiesSet();
}
/**
* Set the JMS ConnectionFactory that this instance should manage transactions for.
*/
public void setConnectionFactory(ConnectionFactory cf) {
if (cf instanceof TransactionAwareConnectionFactoryProxy) {
// If we got a TransactionAwareConnectionFactoryProxy, we need to perform transactions
// for its underlying target ConnectionFactory, else JMS access code won't see
// properly exposed transactions (i.e. transactions for the target ConnectionFactory).
this.connectionFactory = ((TransactionAwareConnectionFactoryProxy) cf).getTargetConnectionFactory();
}
else {
this.connectionFactory = cf;
}
}
/**
* Return the JMS ConnectionFactory that this instance should manage transactions for.
*/
public ConnectionFactory getConnectionFactory() {
return this.connectionFactory;
}
/**
* Make sure the ConnectionFactory has been set.
*/
public void afterPropertiesSet() {
if (getConnectionFactory() == null) {
throw new IllegalArgumentException("Property 'connectionFactory' is required");
}
}
public Object getResourceFactory() {
return getConnectionFactory();
}
protected Object doGetTransaction() {
JmsTransactionObject txObject = new JmsTransactionObject();
txObject.setResourceHolder(
(JmsResourceHolder) TransactionSynchronizationManager.getResource(getConnectionFactory()));
return txObject;
}
protected boolean isExistingTransaction(Object transaction) {
JmsTransactionObject txObject = (JmsTransactionObject) transaction;
return (txObject.getResourceHolder() != null);
}
protected void doBegin(Object transaction, TransactionDefinition definition) {
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
throw new InvalidIsolationLevelException("JMS does not support an isolation level concept");
}
JmsTransactionObject txObject = (JmsTransactionObject) transaction;
Connection con = null;
Session session = null;
try {
con = createConnection();
session = createSession(con);
if (logger.isDebugEnabled()) {
logger.debug("Created JMS transaction on Session [" + session + "] from Connection [" + con + "]");
}
txObject.setResourceHolder(new JmsResourceHolder(getConnectionFactory(), con, session));
txObject.getResourceHolder().setSynchronizedWithTransaction(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getResourceHolder().setTimeoutInSeconds(timeout);
}
TransactionSynchronizationManager.bindResource(
getConnectionFactory(), txObject.getResourceHolder());
}
catch (JMSException ex) {
if (session != null) {
try {
session.close();
}
catch (Throwable ex2) {
// ignore
}
}
if (con != null) {
try {
con.close();
}
catch (Throwable ex2) {
// ignore
}
}
throw new CannotCreateTransactionException("Could not create JMS transaction", ex);
}
}
protected Object doSuspend(Object transaction) {
JmsTransactionObject txObject = (JmsTransactionObject) transaction;
txObject.setResourceHolder(null);
return TransactionSynchronizationManager.unbindResource(getConnectionFactory());
}
protected void doResume(Object transaction, Object suspendedResources) {
JmsResourceHolder conHolder = (JmsResourceHolder) suspendedResources;
TransactionSynchronizationManager.bindResource(getConnectionFactory(), conHolder);
}
protected void doCommit(DefaultTransactionStatus status) {
JmsTransactionObject txObject = (JmsTransactionObject) status.getTransaction();
Session session = txObject.getResourceHolder().getSession();
try {
if (status.isDebug()) {
logger.debug("Committing JMS transaction on Session [" + session + "]");
}
session.commit();
}
catch (TransactionRolledBackException ex) {
throw new UnexpectedRollbackException("JMS transaction rolled back", ex);
}
catch (JMSException ex) {
throw new TransactionSystemException("Could not commit JMS transaction", ex);
}
}
protected void doRollback(DefaultTransactionStatus status) {
JmsTransactionObject txObject = (JmsTransactionObject) status.getTransaction();
Session session = txObject.getResourceHolder().getSession();
try {
if (status.isDebug()) {
logger.debug("Rolling back JMS transaction on Session [" + session + "]");
}
session.rollback();
}
catch (JMSException ex) {
throw new TransactionSystemException("Could not roll back JMS transaction", ex);
}
}
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
JmsTransactionObject txObject = (JmsTransactionObject) status.getTransaction();
txObject.getResourceHolder().setRollbackOnly();
}
protected void doCleanupAfterCompletion(Object transaction) {
JmsTransactionObject txObject = (JmsTransactionObject) transaction;
TransactionSynchronizationManager.unbindResource(getConnectionFactory());
txObject.getResourceHolder().closeAll();
txObject.getResourceHolder().clear();
}
//-------------------------------------------------------------------------
// JMS 1.1 factory methods, potentially overridden for JMS 1.0.2
//-------------------------------------------------------------------------
/**
* Create a JMS Connection via this template's ConnectionFactory.
* <p>This implementation uses JMS 1.1 API.
* @return the new JMS Connection
* @throws javax.jms.JMSException if thrown by JMS API methods
*/
protected Connection createConnection() throws JMSException {
return getConnectionFactory().createConnection();
}
/**
* Create a JMS Session for the given Connection.
* <p>This implementation uses JMS 1.1 API.
* @param con the JMS Connection to create a Session for
* @return the new JMS Session
* @throws javax.jms.JMSException if thrown by JMS API methods
*/
protected Session createSession(Connection con) throws JMSException {
return con.createSession(true, Session.AUTO_ACKNOWLEDGE);
}
/**
* JMS transaction object, representing a JmsResourceHolder.
* Used as transaction object by JmsTransactionManager.
* @see JmsResourceHolder
*/
private static class JmsTransactionObject implements SmartTransactionObject {
private JmsResourceHolder resourceHolder;
public void setResourceHolder(JmsResourceHolder resourceHolder) {
this.resourceHolder = resourceHolder;
}
public JmsResourceHolder getResourceHolder() {
return this.resourceHolder;
}
public boolean isRollbackOnly() {
return this.resourceHolder.isRollbackOnly();
}
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright 2002-2007 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.connection;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.Session;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
/**
* A subclass of {@link JmsTransactionManager} for the JMS 1.0.2 specification,
* not relying on JMS 1.1 methods like JmsTransactionManager itself.
* This class can be used for JMS 1.0.2 providers, offering the same API as
* JmsTransactionManager does for JMS 1.1 providers.
*
* <p>You need to set the {@link #setPubSubDomain "pubSubDomain" property},
* since this class will always explicitly differentiate between a
* {@link javax.jms.QueueConnection} and a {@link javax.jms.TopicConnection}.
*
* @author Juergen Hoeller
* @since 1.1
* @see #setConnectionFactory
* @see #setPubSubDomain
*/
public class JmsTransactionManager102 extends JmsTransactionManager {
private boolean pubSubDomain = false;
/**
* Create a new JmsTransactionManager102 for bean-style usage.
* <p>Note: The ConnectionFactory has to be set before using the instance.
* This constructor can be used to prepare a JmsTemplate via a BeanFactory,
* typically setting the ConnectionFactory via setConnectionFactory.
* @see #setConnectionFactory
*/
public JmsTransactionManager102() {
super();
}
/**
* Create a new JmsTransactionManager102, given a ConnectionFactory.
* @param connectionFactory the ConnectionFactory to manage transactions for
* @param pubSubDomain whether the Publish/Subscribe domain (Topics) or
* Point-to-Point domain (Queues) should be used
* @see #setPubSubDomain
*/
public JmsTransactionManager102(ConnectionFactory connectionFactory, boolean pubSubDomain) {
setConnectionFactory(connectionFactory);
this.pubSubDomain = pubSubDomain;
afterPropertiesSet();
}
/**
* Configure the transaction manager with knowledge of the JMS domain used.
* This tells the JMS 1.0.2 provider which class hierarchy to use for creating
* Connections and Sessions.
* <p>Default is Point-to-Point (Queues).
* @param pubSubDomain <code>true</code> for Publish/Subscribe domain (Topics),
* <code>false</code> for Point-to-Point domain (Queues)
*/
public void setPubSubDomain(boolean pubSubDomain) {
this.pubSubDomain = pubSubDomain;
}
/**
* Return whether the Publish/Subscribe domain (Topics) is used.
* Otherwise, the Point-to-Point domain (Queues) is used.
*/
public boolean isPubSubDomain() {
return this.pubSubDomain;
}
/**
* In addition to checking if the connection factory is set, make sure
* that the supplied connection factory is of the appropriate type for
* the specified destination type: QueueConnectionFactory for queues,
* and TopicConnectionFactory for topics.
*/
public void afterPropertiesSet() {
super.afterPropertiesSet();
// Make sure that the ConnectionFactory passed is consistent.
// Some provider implementations of the ConnectionFactory interface
// implement both domain interfaces under the cover, so just check if
// the selected domain is consistent with the type of connection factory.
if (isPubSubDomain()) {
if (!(getConnectionFactory() instanceof TopicConnectionFactory)) {
throw new IllegalArgumentException(
"Specified a Spring JMS 1.0.2 transaction manager for topics " +
"but did not supply an instance of TopicConnectionFactory");
}
}
else {
if (!(getConnectionFactory() instanceof QueueConnectionFactory)) {
throw new IllegalArgumentException(
"Specified a Spring JMS 1.0.2 transaction manager for queues " +
"but did not supply an instance of QueueConnectionFactory");
}
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected Connection createConnection() throws JMSException {
if (isPubSubDomain()) {
return ((TopicConnectionFactory) getConnectionFactory()).createTopicConnection();
}
else {
return ((QueueConnectionFactory) getConnectionFactory()).createQueueConnection();
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected Session createSession(Connection con) throws JMSException {
if (isPubSubDomain()) {
return ((TopicConnection) con).createTopicSession(true, Session.AUTO_ACKNOWLEDGE);
}
else {
return ((QueueConnection) con).createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
}
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2002-2008 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.connection;
import javax.jms.Session;
/**
* Subinterface of {@link javax.jms.Session} to be implemented by
* Session proxies. Allows access to the the underlying target Session.
*
* @author Juergen Hoeller
* @since 2.0.4
* @see TransactionAwareConnectionFactoryProxy
* @see CachingConnectionFactory
* @see ConnectionFactoryUtils#getTargetSession(javax.jms.Session)
*/
public interface SessionProxy extends Session {
/**
* Return the target Session of this proxy.
* <p>This will typically be the native provider Session
* or a wrapper from a session pool.
* @return the underlying Session (never <code>null</code>)
*/
Session getTargetSession();
}

View File

@ -0,0 +1,587 @@
/*
* Copyright 2002-2008 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.connection;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.Session;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* A JMS ConnectionFactory adapter that returns the same Connection
* from all {@link #createConnection()} calls, and ignores calls to
* {@link javax.jms.Connection#close()}. According to the JMS Connection
* model, this is perfectly thread-safe (in contrast to e.g. JDBC). The
* shared Connection can be automatically recovered in case of an Exception.
*
* <p>You can either pass in a specific JMS Connection directly or let this
* factory lazily create a Connection via a given target ConnectionFactory.
* This factory generally works with JMS 1.1 as well as JMS 1.0.2; use
* {@link SingleConnectionFactory102} for strict JMS 1.0.2 only usage.
*
* <p>Note that when using the JMS 1.0.2 API, this ConnectionFactory will switch
* into queue/topic mode according to the JMS API methods used at runtime:
* <code>createQueueConnection</code> and <code>createTopicConnection</code> will
* lead to queue/topic mode, respectively; generic <code>createConnection</code>
* calls will lead to a JMS 1.1 connection which is able to serve both modes.
*
* <p>Useful for testing and standalone environments in order to keep using the
* same Connection for multiple {@link org.springframework.jms.core.JmsTemplate}
* calls, without having a pooling ConnectionFactory underneath. This may span
* any number of transactions, even concurrently executing transactions.
*
* <p>Note that Spring's message listener containers support the use of
* a shared Connection within each listener container instance. Using
* SingleConnectionFactory in combination only really makes sense for
* sharing a single JMS Connection <i>across multiple listener containers</i>.
*
* @author Juergen Hoeller
* @author Mark Pollack
* @since 1.1
* @see org.springframework.jms.core.JmsTemplate
* @see org.springframework.jms.listener.SimpleMessageListenerContainer
* @see org.springframework.jms.listener.DefaultMessageListenerContainer#setCacheLevel
*/
public class SingleConnectionFactory
implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory, ExceptionListener,
InitializingBean, DisposableBean {
protected final Log logger = LogFactory.getLog(getClass());
private ConnectionFactory targetConnectionFactory;
private String clientId;
private ExceptionListener exceptionListener;
private boolean reconnectOnException = false;
/** Wrapped Connection */
private Connection target;
/** Proxy Connection */
private Connection connection;
/** A hint whether to create a queue or topic connection */
private Boolean pubSubMode;
/** Whether the shared Connection has been started */
private boolean started = false;
/** Synchronization monitor for the shared Connection */
private final Object connectionMonitor = new Object();
/**
* Create a new SingleConnectionFactory for bean-style usage.
* @see #setTargetConnectionFactory
*/
public SingleConnectionFactory() {
}
/**
* Create a new SingleConnectionFactory that always returns the given Connection.
* @param target the single Connection
*/
public SingleConnectionFactory(Connection target) {
Assert.notNull(target, "Target Connection must not be null");
this.target = target;
this.connection = getSharedConnectionProxy(target);
}
/**
* Create a new SingleConnectionFactory that always returns a single
* Connection that it will lazily create via the given target
* ConnectionFactory.
* @param targetConnectionFactory the target ConnectionFactory
*/
public SingleConnectionFactory(ConnectionFactory targetConnectionFactory) {
Assert.notNull(targetConnectionFactory, "Target ConnectionFactory must not be null");
this.targetConnectionFactory = targetConnectionFactory;
}
/**
* Set the target ConnectionFactory which will be used to lazily
* create a single Connection.
*/
public void setTargetConnectionFactory(ConnectionFactory targetConnectionFactory) {
this.targetConnectionFactory = targetConnectionFactory;
}
/**
* Return the target ConnectionFactory which will be used to lazily
* create a single Connection, if any.
*/
public ConnectionFactory getTargetConnectionFactory() {
return this.targetConnectionFactory;
}
/**
* Specify a JMS client ID for the single Connection created and exposed
* by this ConnectionFactory.
* <p>Note that client IDs need to be unique among all active Connections
* of the underlying JMS provider. Furthermore, a client ID can only be
* assigned if the original ConnectionFactory hasn't already assigned one.
* @see javax.jms.Connection#setClientID
* @see #setTargetConnectionFactory
*/
public void setClientId(String clientId) {
this.clientId = clientId;
}
/**
* Return a JMS client ID for the single Connection created and exposed
* by this ConnectionFactory, if any.
*/
protected String getClientId() {
return this.clientId;
}
/**
* Specify an JMS ExceptionListener implementation that should be
* registered with with the single Connection created by this factory.
* @see #setReconnectOnException
*/
public void setExceptionListener(ExceptionListener exceptionListener) {
this.exceptionListener = exceptionListener;
}
/**
* Return the JMS ExceptionListener implementation that should be registered
* with with the single Connection created by this factory, if any.
*/
protected ExceptionListener getExceptionListener() {
return this.exceptionListener;
}
/**
* Specify whether the single Connection should be reset (to be subsequently renewed)
* when a JMSException is reported by the underlying Connection.
* <p>Default is "false". Switch this to "true" to automatically trigger
* recovery based on your JMS provider's exception notifications.
* <p>Internally, this will lead to a special JMS ExceptionListener
* (this SingleConnectionFactory itself) being registered with the
* underlying Connection. This can also be combined with a
* user-specified ExceptionListener, if desired.
* @see #setExceptionListener
*/
public void setReconnectOnException(boolean reconnectOnException) {
this.reconnectOnException = reconnectOnException;
}
/**
* Return whether the single Connection should be renewed when
* a JMSException is reported by the underlying Connection.
*/
protected boolean isReconnectOnException() {
return this.reconnectOnException;
}
/**
* Make sure a Connection or ConnectionFactory has been set.
*/
public void afterPropertiesSet() {
if (this.connection == null && getTargetConnectionFactory() == null) {
throw new IllegalArgumentException("Connection or 'targetConnectionFactory' is required");
}
}
public Connection createConnection() throws JMSException {
synchronized (this.connectionMonitor) {
if (this.connection == null) {
initConnection();
}
return this.connection;
}
}
public Connection createConnection(String username, String password) throws JMSException {
throw new javax.jms.IllegalStateException(
"SingleConnectionFactory does not support custom username and password");
}
public QueueConnection createQueueConnection() throws JMSException {
Connection con = null;
synchronized (this.connectionMonitor) {
this.pubSubMode = Boolean.FALSE;
con = createConnection();
}
if (!(con instanceof QueueConnection)) {
throw new javax.jms.IllegalStateException(
"This SingleConnectionFactory does not hold a QueueConnection but rather: " + con);
}
return ((QueueConnection) con);
}
public QueueConnection createQueueConnection(String username, String password) throws JMSException {
throw new javax.jms.IllegalStateException(
"SingleConnectionFactory does not support custom username and password");
}
public TopicConnection createTopicConnection() throws JMSException {
Connection con = null;
synchronized (this.connectionMonitor) {
this.pubSubMode = Boolean.TRUE;
con = createConnection();
}
if (!(con instanceof TopicConnection)) {
throw new javax.jms.IllegalStateException(
"This SingleConnectionFactory does not hold a TopicConnection but rather: " + con);
}
return ((TopicConnection) con);
}
public TopicConnection createTopicConnection(String username, String password) throws JMSException {
throw new javax.jms.IllegalStateException(
"SingleConnectionFactory does not support custom username and password");
}
/**
* Initialize the underlying shared Connection.
* <p>Closes and reinitializes the Connection if an underlying
* Connection is present already.
* @throws javax.jms.JMSException if thrown by JMS API methods
*/
public void initConnection() throws JMSException {
if (getTargetConnectionFactory() == null) {
throw new IllegalStateException(
"'targetConnectionFactory' is required for lazily initializing a Connection");
}
synchronized (this.connectionMonitor) {
if (this.target != null) {
closeConnection(this.target);
}
this.target = doCreateConnection();
prepareConnection(this.target);
if (logger.isInfoEnabled()) {
logger.info("Established shared JMS Connection: " + this.target);
}
this.connection = getSharedConnectionProxy(this.target);
}
}
/**
* Exception listener callback that renews the underlying single Connection.
*/
public void onException(JMSException ex) {
resetConnection();
}
/**
* Close the underlying shared connection.
* The provider of this ConnectionFactory needs to care for proper shutdown.
* <p>As this bean implements DisposableBean, a bean factory will
* automatically invoke this on destruction of its cached singletons.
*/
public void destroy() {
resetConnection();
}
/**
* Reset the underlying shared Connection, to be reinitialized on next access.
*/
public void resetConnection() {
synchronized (this.connectionMonitor) {
if (this.target != null) {
closeConnection(this.target);
}
this.target = null;
this.connection = null;
}
}
/**
* Create a JMS Connection via this template's ConnectionFactory.
* @return the new JMS Connection
* @throws javax.jms.JMSException if thrown by JMS API methods
*/
protected Connection doCreateConnection() throws JMSException {
ConnectionFactory cf = getTargetConnectionFactory();
if (Boolean.FALSE.equals(this.pubSubMode) && cf instanceof QueueConnectionFactory) {
return ((QueueConnectionFactory) cf).createQueueConnection();
}
else if (Boolean.TRUE.equals(this.pubSubMode) && cf instanceof TopicConnectionFactory) {
return ((TopicConnectionFactory) cf).createTopicConnection();
}
else {
return getTargetConnectionFactory().createConnection();
}
}
/**
* Prepare the given Connection before it is exposed.
* <p>The default implementation applies ExceptionListener and client id.
* Can be overridden in subclasses.
* @param con the Connection to prepare
* @throws JMSException if thrown by JMS API methods
* @see #setExceptionListener
* @see #setReconnectOnException
*/
protected void prepareConnection(Connection con) throws JMSException {
if (getClientId() != null) {
con.setClientID(getClientId());
}
if (getExceptionListener() != null || isReconnectOnException()) {
ExceptionListener listenerToUse = getExceptionListener();
if (isReconnectOnException()) {
listenerToUse = new InternalChainedExceptionListener(this, listenerToUse);
}
con.setExceptionListener(listenerToUse);
}
}
/**
* Template method for obtaining a (potentially cached) Session.
* <p>The default implementation always returns <code>null</code>.
* Subclasses may override this for exposing specific Session handles,
* possibly delegating to {@link #createSession} for the creation of raw
* Session objects that will then get wrapped and returned from here.
* @param con the JMS Connection to operate on
* @param mode the Session acknowledgement mode
* (<code>Session.TRANSACTED</code> or one of the common modes)
* @return the Session to use, or <code>null</code> to indicate
* creation of a raw standard Session
* @throws JMSException if thrown by the JMS API
*/
protected Session getSession(Connection con, Integer mode) throws JMSException {
return null;
}
/**
* Create a default Session for this ConnectionFactory,
* adaptign to JMS 1.0.2 style queue/topic mode if necessary.
* @param con the JMS Connection to operate on
* @param mode the Session acknowledgement mode
* (<code>Session.TRANSACTED</code> or one of the common modes)
* @return the newly created Session
* @throws JMSException if thrown by the JMS API
*/
protected Session createSession(Connection con, Integer mode) throws JMSException {
// Determine JMS API arguments...
boolean transacted = (mode.intValue() == Session.SESSION_TRANSACTED);
int ackMode = (transacted ? Session.AUTO_ACKNOWLEDGE : mode.intValue());
// Now actually call the appropriate JMS factory method...
if (Boolean.FALSE.equals(this.pubSubMode) && con instanceof QueueConnection) {
return ((QueueConnection) con).createQueueSession(transacted, ackMode);
}
else if (Boolean.TRUE.equals(this.pubSubMode) && con instanceof TopicConnection) {
return ((TopicConnection) con).createTopicSession(transacted, ackMode);
}
else {
return con.createSession(transacted, ackMode);
}
}
/**
* Close the given Connection.
* @param con the Connection to close
*/
protected void closeConnection(Connection con) {
if (logger.isDebugEnabled()) {
logger.debug("Closing shared JMS Connection: " + this.target);
}
try {
try {
if (this.started) {
this.started = false;
con.stop();
}
}
finally {
con.close();
}
}
catch (javax.jms.IllegalStateException ex) {
logger.debug("Ignoring Connection state exception - assuming already closed: " + ex);
}
catch (Throwable ex) {
logger.debug("Could not close shared JMS Connection", ex);
}
}
/**
* Wrap the given Connection with a proxy that delegates every method call to it
* but suppresses close calls. This is useful for allowing application code to
* handle a special framework Connection just like an ordinary Connection from a
* JMS ConnectionFactory.
* @param target the original Connection to wrap
* @return the wrapped Connection
*/
protected Connection getSharedConnectionProxy(Connection target) {
List classes = new ArrayList(3);
classes.add(Connection.class);
if (target instanceof QueueConnection) {
classes.add(QueueConnection.class);
}
if (target instanceof TopicConnection) {
classes.add(TopicConnection.class);
}
return (Connection) Proxy.newProxyInstance(
Connection.class.getClassLoader(),
(Class[]) classes.toArray(new Class[classes.size()]),
new SharedConnectionInvocationHandler(target));
}
/**
* Invocation handler for a cached JMS Connection proxy.
*/
private class SharedConnectionInvocationHandler implements InvocationHandler {
private final Connection target;
public SharedConnectionInvocationHandler(Connection target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("equals")) {
// Only consider equal when proxies are identical.
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
}
else if (method.getName().equals("hashCode")) {
// Use hashCode of Connection proxy.
return new Integer(System.identityHashCode(proxy));
}
else if (method.getName().equals("toString")) {
return "Shared JMS Connection: " + this.target;
}
else if (method.getName().equals("setClientID")) {
// Handle setClientID method: throw exception if not compatible.
String currentClientId = this.target.getClientID();
if (currentClientId != null && currentClientId.equals(args[0])) {
return null;
}
else {
throw new javax.jms.IllegalStateException(
"setClientID call not supported on proxy for shared Connection. " +
"Set the 'clientId' property on the SingleConnectionFactory instead.");
}
}
else if (method.getName().equals("setExceptionListener")) {
// Handle setExceptionListener method: add to the chain.
ExceptionListener currentExceptionListener = this.target.getExceptionListener();
if (currentExceptionListener instanceof InternalChainedExceptionListener && args[0] != null) {
((InternalChainedExceptionListener) currentExceptionListener).addDelegate((ExceptionListener) args[0]);
return null;
}
else {
throw new javax.jms.IllegalStateException(
"setExceptionListener call not supported on proxy for shared Connection. " +
"Set the 'exceptionListener' property on the SingleConnectionFactory instead. " +
"Alternatively, activate SingleConnectionFactory's 'reconnectOnException' feature, " +
"which will allow for registering further ExceptionListeners to the recovery chain.");
}
}
else if (method.getName().equals("start")) {
// Handle start method: track started state.
this.target.start();
synchronized (connectionMonitor) {
started = true;
}
return null;
}
else if (method.getName().equals("stop")) {
// Handle stop method: don't pass the call on.
return null;
}
else if (method.getName().equals("close")) {
// Handle close method: don't pass the call on.
return null;
}
else if (method.getName().equals("createSession") || method.getName().equals("createQueueSession") ||
method.getName().equals("createTopicSession")) {
boolean transacted = ((Boolean) args[0]).booleanValue();
Integer ackMode = (Integer) args[1];
Integer mode = (transacted ? new Integer(Session.SESSION_TRANSACTED) : ackMode);
Session session = getSession(this.target, mode);
if (session != null) {
if (!method.getReturnType().isInstance(session)) {
String msg = "JMS Session does not implement specific domain: " + session;
try {
session.close();
}
catch (Throwable ex) {
logger.trace("Failed to close newly obtained JMS Session", ex);
}
throw new javax.jms.IllegalStateException(msg);
}
return session;
}
}
try {
Object retVal = method.invoke(this.target, args);
if (method.getName().equals("getExceptionListener") && retVal instanceof InternalChainedExceptionListener) {
// Handle getExceptionListener method: hide internal chain.
InternalChainedExceptionListener listener = (InternalChainedExceptionListener) retVal;
return listener.getUserListener();
}
else {
return retVal;
}
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
/**
* Internal chained ExceptionListener for handling the internal recovery listener
* in combination with a user-specified listener.
*/
private static class InternalChainedExceptionListener extends ChainedExceptionListener {
private ExceptionListener userListener;
public InternalChainedExceptionListener(ExceptionListener internalListener, ExceptionListener userListener) {
addDelegate(internalListener);
if (userListener != null) {
addDelegate(userListener);
this.userListener = userListener;
}
}
public ExceptionListener getUserListener() {
return this.userListener;
}
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright 2002-2007 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.connection;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.QueueConnectionFactory;
import javax.jms.TopicConnectionFactory;
/**
* A subclass of {@link SingleConnectionFactory} for the JMS 1.0.2 specification,
* not relying on JMS 1.1 methods like SingleConnectionFactory itself.
* This class can be used for JMS 1.0.2 providers, offering the same API as
* SingleConnectionFactory does for JMS 1.1 providers.
*
* <p>You need to set the {@link #setPubSubDomain "pubSubDomain" property},
* since this class will always explicitly differentiate between a
* {@link javax.jms.QueueConnection} and a {@link javax.jms.TopicConnection}.
*
* @author Juergen Hoeller
* @since 1.1
* @see #setTargetConnectionFactory
* @see #setPubSubDomain
*/
public class SingleConnectionFactory102 extends SingleConnectionFactory {
private boolean pubSubDomain = false;
/**
* Create a new SingleConnectionFactory102 for bean-style usage.
*/
public SingleConnectionFactory102() {
super();
}
/**
* Create a new SingleConnectionFactory102 that always returns a single
* Connection that it will lazily create via the given target
* ConnectionFactory.
* @param connectionFactory the target ConnectionFactory
* @param pubSubDomain whether the Publish/Subscribe domain (Topics) or
* Point-to-Point domain (Queues) should be used
*/
public SingleConnectionFactory102(ConnectionFactory connectionFactory, boolean pubSubDomain) {
setTargetConnectionFactory(connectionFactory);
this.pubSubDomain = pubSubDomain;
afterPropertiesSet();
}
/**
* Configure the factory with knowledge of the JMS domain used.
* This tells the JMS 1.0.2 provider which class hierarchy to use for creating
* Connections and Sessions.
* <p>Default is Point-to-Point (Queues).
* @param pubSubDomain <code>true</code> for Publish/Subscribe domain (Topics),
* <code>false</code> for Point-to-Point domain (Queues)
*/
public void setPubSubDomain(boolean pubSubDomain) {
this.pubSubDomain = pubSubDomain;
}
/**
* Return whether the Publish/Subscribe domain (Topics) is used.
* Otherwise, the Point-to-Point domain (Queues) is used.
*/
public boolean isPubSubDomain() {
return this.pubSubDomain;
}
/**
* In addition to checking whether the target ConnectionFactory is set,
* make sure that the supplied factory is of the appropriate type for
* the specified destination type: QueueConnectionFactory for queues,
* TopicConnectionFactory for topics.
*/
public void afterPropertiesSet() {
super.afterPropertiesSet();
// Make sure that the ConnectionFactory passed is consistent.
// Some provider implementations of the ConnectionFactory interface
// implement both domain interfaces under the cover, so just check if
// the selected domain is consistent with the type of connection factory.
if (isPubSubDomain()) {
if (!(getTargetConnectionFactory() instanceof TopicConnectionFactory)) {
throw new IllegalArgumentException(
"Specified a Spring JMS 1.0.2 SingleConnectionFactory for topics " +
"but did not supply an instance of TopicConnectionFactory");
}
}
else {
if (!(getTargetConnectionFactory() instanceof QueueConnectionFactory)) {
throw new IllegalArgumentException(
"Specified a Spring JMS 1.0.2 SingleConnectionFactory for queues " +
"but did not supply an instance of QueueConnectionFactory");
}
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected Connection doCreateConnection() throws JMSException {
if (isPubSubDomain()) {
return ((TopicConnectionFactory) getTargetConnectionFactory()).createTopicConnection();
}
else {
return ((QueueConnectionFactory) getTargetConnectionFactory()).createQueueConnection();
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2002-2007 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.connection;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
/**
* Extension of the <code>javax.jms.ConnectionFactory</code> interface,
* indicating how to release Connections obtained from it.
*
* @author Juergen Hoeller
* @since 2.0.2
*/
public interface SmartConnectionFactory extends ConnectionFactory {
/**
* Should we stop the Connection, obtained from this ConnectionFactory?
* @param con the Connection to check
* @return whether a stop call is necessary
* @see javax.jms.Connection#stop()
*/
boolean shouldStop(Connection con);
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2002-2006 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.connection;
import javax.jms.JMSException;
import org.springframework.jms.JmsException;
/**
* Exception thrown when a synchronized local transaction failed to complete
* (after the main transaction has already completed).
*
* @author Juergen Hoeller
* @since 2.0
* @see ConnectionFactoryUtils
*/
public class SynchedLocalTransactionFailedException extends JmsException {
/**
* Create a new SynchedLocalTransactionFailedException.
* @param msg the detail message
* @param cause the root cause
*/
public SynchedLocalTransactionFailedException(String msg, JMSException cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,329 @@
/*
* Copyright 2002-2008 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.connection;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicSession;
import javax.jms.TransactionInProgressException;
import org.springframework.util.Assert;
/**
* Proxy for a target JMS {@link javax.jms.ConnectionFactory}, adding awareness of
* Spring-managed transactions. Similar to a transactional JNDI ConnectionFactory
* as provided by a J2EE server.
*
* <p>Messaging code that should remain unaware of Spring's JMS support can work
* with this proxy to seamlessly participate in Spring-managed transactions.
* Note that the transaction manager, for example {@link JmsTransactionManager},
* still needs to work with the underlying ConnectionFactory, <i>not</i> with
* this proxy.
*
* <p><b>Make sure that TransactionAwareConnectionFactoryProxy is the outermost
* ConnectionFactory of a chain of ConnectionFactory proxies/adapters.</b>
* TransactionAwareConnectionFactoryProxy can delegate either directly to the
* target factory or to some intermediary adapter like
* {@link UserCredentialsConnectionFactoryAdapter}.
*
* <p>Delegates to {@link ConnectionFactoryUtils} for automatically participating
* in thread-bound transactions, for example managed by {@link JmsTransactionManager}.
* <code>createSession</code> calls and <code>close</code> calls on returned Sessions
* will behave properly within a transaction, that is, always work on the transactional
* Session. If not within a transaction, normal ConnectionFactory behavior applies.
*
* <p>Note that transactional JMS Sessions will be registered on a per-Connection
* basis. To share the same JMS Session across a transaction, make sure that you
* operate on the same JMS Connection handle - either through reusing the handle
* or through configuring a {@link SingleConnectionFactory} underneath.
*
* <p>Returned transactional Session proxies will implement the {@link SessionProxy}
* interface to allow for access to the underlying target Session. This is only
* intended for accessing vendor-specific Session API or for testing purposes
* (e.g. to perform manual transaction control). For typical application purposes,
* simply use the standard JMS Session interface.
*
* @author Juergen Hoeller
* @since 2.0
* @see UserCredentialsConnectionFactoryAdapter
* @see SingleConnectionFactory
*/
public class TransactionAwareConnectionFactoryProxy
implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory {
private boolean synchedLocalTransactionAllowed = false;
private ConnectionFactory targetConnectionFactory;
/**
* Create a new TransactionAwareConnectionFactoryProxy.
*/
public TransactionAwareConnectionFactoryProxy() {
}
/**
* Create a new TransactionAwareConnectionFactoryProxy.
* @param targetConnectionFactory the target ConnectionFactory
*/
public TransactionAwareConnectionFactoryProxy(ConnectionFactory targetConnectionFactory) {
setTargetConnectionFactory(targetConnectionFactory);
}
/**
* Set the target ConnectionFactory that this ConnectionFactory should delegate to.
*/
public final void setTargetConnectionFactory(ConnectionFactory targetConnectionFactory) {
Assert.notNull(targetConnectionFactory, "targetConnectionFactory must not be nul");
this.targetConnectionFactory = targetConnectionFactory;
}
/**
* Return the target ConnectionFactory that this ConnectionFactory should delegate to.
*/
protected ConnectionFactory getTargetConnectionFactory() {
return this.targetConnectionFactory;
}
/**
* Set whether to allow for a local JMS transaction that is synchronized with a
* Spring-managed transaction (where the main transaction might be a JDBC-based
* one for a specific DataSource, for example), with the JMS transaction committing
* right after the main transaction. If not allowed, the given ConnectionFactory
* needs to handle transaction enlistment underneath the covers.
* <p>Default is "false": If not within a managed transaction that encompasses
* the underlying JMS ConnectionFactory, standard Sessions will be returned.
* Turn this flag on to allow participation in any Spring-managed transaction,
* with a local JMS transaction synchronized with the main transaction.
*/
public void setSynchedLocalTransactionAllowed(boolean synchedLocalTransactionAllowed) {
this.synchedLocalTransactionAllowed = synchedLocalTransactionAllowed;
}
/**
* Return whether to allow for a local JMS transaction that is synchronized
* with a Spring-managed transaction.
*/
protected boolean isSynchedLocalTransactionAllowed() {
return this.synchedLocalTransactionAllowed;
}
public Connection createConnection() throws JMSException {
Connection targetConnection = this.targetConnectionFactory.createConnection();
return getTransactionAwareConnectionProxy(targetConnection);
}
public Connection createConnection(String username, String password) throws JMSException {
Connection targetConnection = this.targetConnectionFactory.createConnection(username, password);
return getTransactionAwareConnectionProxy(targetConnection);
}
public QueueConnection createQueueConnection() throws JMSException {
if (!(this.targetConnectionFactory instanceof QueueConnectionFactory)) {
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is no QueueConnectionFactory");
}
QueueConnection targetConnection =
((QueueConnectionFactory) this.targetConnectionFactory).createQueueConnection();
return (QueueConnection) getTransactionAwareConnectionProxy(targetConnection);
}
public QueueConnection createQueueConnection(String username, String password) throws JMSException {
if (!(this.targetConnectionFactory instanceof QueueConnectionFactory)) {
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is no QueueConnectionFactory");
}
QueueConnection targetConnection =
((QueueConnectionFactory) this.targetConnectionFactory).createQueueConnection(username, password);
return (QueueConnection) getTransactionAwareConnectionProxy(targetConnection);
}
public TopicConnection createTopicConnection() throws JMSException {
if (!(this.targetConnectionFactory instanceof TopicConnectionFactory)) {
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is no TopicConnectionFactory");
}
TopicConnection targetConnection =
((TopicConnectionFactory) this.targetConnectionFactory).createTopicConnection();
return (TopicConnection) getTransactionAwareConnectionProxy(targetConnection);
}
public TopicConnection createTopicConnection(String username, String password) throws JMSException {
if (!(this.targetConnectionFactory instanceof TopicConnectionFactory)) {
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is no TopicConnectionFactory");
}
TopicConnection targetConnection =
((TopicConnectionFactory) this.targetConnectionFactory).createTopicConnection(username, password);
return (TopicConnection) getTransactionAwareConnectionProxy(targetConnection);
}
/**
* Wrap the given Connection with a proxy that delegates every method call to it
* but handles Session lookup in a transaction-aware fashion.
* @param target the original Connection to wrap
* @return the wrapped Connection
*/
private Connection getTransactionAwareConnectionProxy(Connection target) {
List classes = new ArrayList(3);
classes.add(Connection.class);
if (target instanceof QueueConnection) {
classes.add(QueueConnection.class);
}
if (target instanceof TopicConnection) {
classes.add(TopicConnection.class);
}
return (Connection) Proxy.newProxyInstance(
Connection.class.getClassLoader(),
(Class[]) classes.toArray(new Class[classes.size()]),
new TransactionAwareConnectionInvocationHandler(target));
}
/**
* Invocation handler that exposes transactional Sessions for the underlying Connection.
*/
private class TransactionAwareConnectionInvocationHandler implements InvocationHandler {
private final Connection target;
public TransactionAwareConnectionInvocationHandler(Connection target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Invocation on ConnectionProxy interface coming in...
if (method.getName().equals("equals")) {
// Only consider equal when proxies are identical.
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
}
else if (method.getName().equals("hashCode")) {
// Use hashCode of Connection proxy.
return new Integer(System.identityHashCode(proxy));
}
else if (Session.class.equals(method.getReturnType())) {
Session session = ConnectionFactoryUtils.getTransactionalSession(
getTargetConnectionFactory(), this.target, isSynchedLocalTransactionAllowed());
if (session != null) {
return getCloseSuppressingSessionProxy(session);
}
}
else if (QueueSession.class.equals(method.getReturnType())) {
QueueSession session = ConnectionFactoryUtils.getTransactionalQueueSession(
(QueueConnectionFactory) getTargetConnectionFactory(), (QueueConnection) this.target,
isSynchedLocalTransactionAllowed());
if (session != null) {
return getCloseSuppressingSessionProxy(session);
}
}
else if (TopicSession.class.equals(method.getReturnType())) {
TopicSession session = ConnectionFactoryUtils.getTransactionalTopicSession(
(TopicConnectionFactory) getTargetConnectionFactory(), (TopicConnection) this.target,
isSynchedLocalTransactionAllowed());
if (session != null) {
return getCloseSuppressingSessionProxy(session);
}
}
// Invoke method on target Connection.
try {
return method.invoke(this.target, args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
private Session getCloseSuppressingSessionProxy(Session target) {
List classes = new ArrayList(3);
classes.add(SessionProxy.class);
if (target instanceof QueueSession) {
classes.add(QueueSession.class);
}
if (target instanceof TopicSession) {
classes.add(TopicSession.class);
}
return (Session) Proxy.newProxyInstance(
SessionProxy.class.getClassLoader(),
(Class[]) classes.toArray(new Class[classes.size()]),
new CloseSuppressingSessionInvocationHandler(target));
}
}
/**
* Invocation handler that suppresses close calls for a transactional JMS Session.
*/
private static class CloseSuppressingSessionInvocationHandler implements InvocationHandler {
private final Session target;
public CloseSuppressingSessionInvocationHandler(Session target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Invocation on SessionProxy interface coming in...
if (method.getName().equals("equals")) {
// Only consider equal when proxies are identical.
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
}
else if (method.getName().equals("hashCode")) {
// Use hashCode of Connection proxy.
return new Integer(System.identityHashCode(proxy));
}
else if (method.getName().equals("commit")) {
throw new TransactionInProgressException("Commit call not allowed within a managed transaction");
}
else if (method.getName().equals("rollback")) {
throw new TransactionInProgressException("Rollback call not allowed within a managed transaction");
}
else if (method.getName().equals("close")) {
// Handle close method: not to be closed within a transaction.
return null;
}
else if (method.getName().equals("getTargetSession")) {
// Handle getTargetSession method: return underlying Session.
return this.target;
}
// Invoke method on target Session.
try {
return method.invoke(this.target, args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
}

View File

@ -0,0 +1,299 @@
/*
* Copyright 2002-2008 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.connection;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.NamedThreadLocal;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* An adapter for a target JMS {@link javax.jms.ConnectionFactory}, applying the
* given user credentials to every standard <code>createConnection()</code> call,
* that is, implicitly invoking <code>createConnection(username, password)</code>
* on the target. All other methods simply delegate to the corresponding methods
* of the target ConnectionFactory.
*
* <p>Can be used to proxy a target JNDI ConnectionFactory that does not have user
* credentials configured. Client code can work with the ConnectionFactory without
* passing in username and password on every <code>createConnection()</code> call.
*
* <p>In the following example, client code can simply transparently work
* with the preconfigured "myConnectionFactory", implicitly accessing
* "myTargetConnectionFactory" with the specified user credentials.
*
* <pre class="code">
* &lt;bean id="myTargetConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean"&gt;
* &lt;property name="jndiName" value="java:comp/env/jms/mycf"/&gt;
* &lt;/bean&gt;
*
* &lt;bean id="myConnectionFactory" class="org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter"&gt;
* &lt;property name="targetConnectionFactory" ref="myTargetConnectionFactory"/&gt;
* &lt;property name="username" value="myusername"/&gt;
* &lt;property name="password" value="mypassword"/&gt;
* &lt;/bean></pre>
*
* <p>If the "username" is empty, this proxy will simply delegate to the standard
* <code>createConnection()</code> method of the target ConnectionFactory.
* This can be used to keep a UserCredentialsConnectionFactoryAdapter bean
* definition just for the <i>option</i> of implicitly passing in user credentials
* if the particular target ConnectionFactory requires it.
*
* @author Juergen Hoeller
* @since 1.2
* @see #createConnection
* @see #createQueueConnection
* @see #createTopicConnection
*/
public class UserCredentialsConnectionFactoryAdapter
implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory, InitializingBean {
private ConnectionFactory targetConnectionFactory;
private String username;
private String password;
private final ThreadLocal threadBoundCredentials = new NamedThreadLocal("Current JMS user credentials");
/**
* Set the target ConnectionFactory that this ConnectionFactory should delegate to.
*/
public void setTargetConnectionFactory(ConnectionFactory targetConnectionFactory) {
Assert.notNull(targetConnectionFactory, "'targetConnectionFactory' must not be null");
this.targetConnectionFactory = targetConnectionFactory;
}
/**
* Set the username that this adapter should use for retrieving Connections.
* Default is no specific user.
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Set the password that this adapter should use for retrieving Connections.
* Default is no specific password.
*/
public void setPassword(String password) {
this.password = password;
}
public void afterPropertiesSet() {
if (this.targetConnectionFactory == null) {
throw new IllegalArgumentException("Property 'targetConnectionFactory' is required");
}
}
/**
* Set user credententials for this proxy and the current thread.
* The given username and password will be applied to all subsequent
* <code>createConnection()</code> calls on this ConnectionFactory proxy.
* <p>This will override any statically specified user credentials,
* that is, values of the "username" and "password" bean properties.
* @param username the username to apply
* @param password the password to apply
* @see #removeCredentialsFromCurrentThread
*/
public void setCredentialsForCurrentThread(String username, String password) {
this.threadBoundCredentials.set(new JmsUserCredentials(username, password));
}
/**
* Remove any user credentials for this proxy from the current thread.
* Statically specified user credentials apply again afterwards.
* @see #setCredentialsForCurrentThread
*/
public void removeCredentialsFromCurrentThread() {
this.threadBoundCredentials.set(null);
}
/**
* Determine whether there are currently thread-bound credentials,
* using them if available, falling back to the statically specified
* username and password (i.e. values of the bean properties) else.
* @see #doCreateConnection
*/
public final Connection createConnection() throws JMSException {
JmsUserCredentials threadCredentials = (JmsUserCredentials) this.threadBoundCredentials.get();
if (threadCredentials != null) {
return doCreateConnection(threadCredentials.username, threadCredentials.password);
}
else {
return doCreateConnection(this.username, this.password);
}
}
/**
* Delegate the call straight to the target ConnectionFactory.
*/
public Connection createConnection(String username, String password) throws JMSException {
return doCreateConnection(username, password);
}
/**
* This implementation delegates to the <code>createConnection(username, password)</code>
* method of the target ConnectionFactory, passing in the specified user credentials.
* If the specified username is empty, it will simply delegate to the standard
* <code>createConnection()</code> method of the target ConnectionFactory.
* @param username the username to use
* @param password the password to use
* @return the Connection
* @see javax.jms.ConnectionFactory#createConnection(String, String)
* @see javax.jms.ConnectionFactory#createConnection()
*/
protected Connection doCreateConnection(String username, String password) throws JMSException {
Assert.state(this.targetConnectionFactory != null, "'targetConnectionFactory' is required");
if (StringUtils.hasLength(username)) {
return this.targetConnectionFactory.createConnection(username, password);
}
else {
return this.targetConnectionFactory.createConnection();
}
}
/**
* Determine whether there are currently thread-bound credentials,
* using them if available, falling back to the statically specified
* username and password (i.e. values of the bean properties) else.
* @see #doCreateQueueConnection
*/
public final QueueConnection createQueueConnection() throws JMSException {
JmsUserCredentials threadCredentials = (JmsUserCredentials) this.threadBoundCredentials.get();
if (threadCredentials != null) {
return doCreateQueueConnection(threadCredentials.username, threadCredentials.password);
}
else {
return doCreateQueueConnection(this.username, this.password);
}
}
/**
* Delegate the call straight to the target QueueConnectionFactory.
*/
public QueueConnection createQueueConnection(String username, String password) throws JMSException {
return doCreateQueueConnection(username, password);
}
/**
* This implementation delegates to the <code>createQueueConnection(username, password)</code>
* method of the target QueueConnectionFactory, passing in the specified user credentials.
* If the specified username is empty, it will simply delegate to the standard
* <code>createQueueConnection()</code> method of the target ConnectionFactory.
* @param username the username to use
* @param password the password to use
* @return the Connection
* @see javax.jms.QueueConnectionFactory#createQueueConnection(String, String)
* @see javax.jms.QueueConnectionFactory#createQueueConnection()
*/
protected QueueConnection doCreateQueueConnection(String username, String password) throws JMSException {
Assert.state(this.targetConnectionFactory != null, "'targetConnectionFactory' is required");
if (!(this.targetConnectionFactory instanceof QueueConnectionFactory)) {
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is not a QueueConnectionFactory");
}
QueueConnectionFactory queueFactory = (QueueConnectionFactory) this.targetConnectionFactory;
if (StringUtils.hasLength(username)) {
return queueFactory.createQueueConnection(username, password);
}
else {
return queueFactory.createQueueConnection();
}
}
/**
* Determine whether there are currently thread-bound credentials,
* using them if available, falling back to the statically specified
* username and password (i.e. values of the bean properties) else.
* @see #doCreateTopicConnection
*/
public final TopicConnection createTopicConnection() throws JMSException {
JmsUserCredentials threadCredentials = (JmsUserCredentials) this.threadBoundCredentials.get();
if (threadCredentials != null) {
return doCreateTopicConnection(threadCredentials.username, threadCredentials.password);
}
else {
return doCreateTopicConnection(this.username, this.password);
}
}
/**
* Delegate the call straight to the target TopicConnectionFactory.
*/
public TopicConnection createTopicConnection(String username, String password) throws JMSException {
return doCreateTopicConnection(username, password);
}
/**
* This implementation delegates to the <code>createTopicConnection(username, password)</code>
* method of the target TopicConnectionFactory, passing in the specified user credentials.
* If the specified username is empty, it will simply delegate to the standard
* <code>createTopicConnection()</code> method of the target ConnectionFactory.
* @param username the username to use
* @param password the password to use
* @return the Connection
* @see javax.jms.TopicConnectionFactory#createTopicConnection(String, String)
* @see javax.jms.TopicConnectionFactory#createTopicConnection()
*/
protected TopicConnection doCreateTopicConnection(String username, String password) throws JMSException {
Assert.state(this.targetConnectionFactory != null, "'targetConnectionFactory' is required");
if (!(this.targetConnectionFactory instanceof TopicConnectionFactory)) {
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is not a TopicConnectionFactory");
}
TopicConnectionFactory queueFactory = (TopicConnectionFactory) this.targetConnectionFactory;
if (StringUtils.hasLength(username)) {
return queueFactory.createTopicConnection(username, password);
}
else {
return queueFactory.createTopicConnection();
}
}
/**
* Inner class used as ThreadLocal value.
*/
private static class JmsUserCredentials {
public final String username;
public final String password;
private JmsUserCredentials(String username, String password) {
this.username = username;
this.password = password;
}
public String toString() {
return "JmsUserCredentials[username='" + this.username + "',password='" + this.password + "']";
}
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Provides a PlatformTransactionManager implementation for a single
JMS ConnectionFactory, and a SingleConnectionFactory adapter.
</body>
</html>

View File

@ -0,0 +1,46 @@
/*
* Copyright 2002-2008 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.core;
import javax.jms.JMSException;
import javax.jms.QueueBrowser;
import javax.jms.Session;
/**
* Callback for browsing the messages in a JMS queue.
*
* <p>To be used with JmsTemplate's callback methods that take a BrowserCallback
* argument, often implemented as an anonymous inner class.
*
* @author Juergen Hoeller
* @since 2.5.1
* @see JmsTemplate#browse(BrowserCallback)
* @see JmsTemplate#browseSelected(String, BrowserCallback)
*/
public interface BrowserCallback {
/**
* Perform operations on the given {@link javax.jms.Session} and {@link javax.jms.QueueBrowser}.
* <p>The message producer is not associated with any destination.
* @param session the JMS <code>Session</code> object to use
* @param browser the JMS <code>QueueBrowser</code> object to use
* @return a result object from working with the <code>Session</code>, if any (can be <code>null</code>)
* @throws javax.jms.JMSException if thrown by JMS API methods
*/
Object doInJms(Session session, QueueBrowser browser) throws JMSException;
}

View File

@ -0,0 +1,425 @@
/*
* Copyright 2002-2008 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.core;
import javax.jms.Destination;
import javax.jms.Message;
import javax.jms.Queue;
import org.springframework.jms.JmsException;
/**
* Specifies a basic set of JMS operations.
*
* <p>Implemented by {@link JmsTemplate}. Not often used but a useful option
* to enhance testability, as it can easily be mocked or stubbed.
*
* <p>Provides <code>JmsTemplate's</code> <code>send(..)</code> and
* <code>receive(..)</code> methods that mirror various JMS API methods.
* See the JMS specification and javadocs for details on those methods.
*
* @author Mark Pollack
* @author Juergen Hoeller
* @since 1.1
* @see JmsTemplate
* @see javax.jms.Destination
* @see javax.jms.Session
* @see javax.jms.MessageProducer
* @see javax.jms.MessageConsumer
*/
public interface JmsOperations {
/**
* Execute the action specified by the given action object within a JMS Session.
* <p>When used with a 1.0.2 provider, you may need to downcast
* to the appropriate domain implementation, either QueueSession or
* TopicSession in the action objects doInJms callback method.
* @param action callback object that exposes the session
* @return the result object from working with the session
* @throws JmsException if there is any problem
*/
Object execute(SessionCallback action) throws JmsException;
/**
* Send messages to the default JMS destination (or one specified
* for each send operation). The callback gives access to the JMS Session
* and MessageProducer in order to perform complex send operations.
* @param action callback object that exposes the session/producer pair
* @return the result object from working with the session
* @throws JmsException checked JMSException converted to unchecked
*/
Object execute(ProducerCallback action) throws JmsException;
/**
* Send messages to a JMS destination. The callback gives access to the JMS Session
* and MessageProducer in order to perform complex send operations.
* @param destination the destination to send messages to
* @param action callback object that exposes the session/producer pair
* @return the result object from working with the session
* @throws JmsException checked JMSException converted to unchecked
*/
Object execute(Destination destination, ProducerCallback action) throws JmsException;
/**
* Send messages to a JMS destination. The callback gives access to the JMS Session
* and MessageProducer in order to perform complex send operations.
* @param destinationName the name of the destination to send messages to
* (to be resolved to an actual destination by a DestinationResolver)
* @param action callback object that exposes the session/producer pair
* @return the result object from working with the session
* @throws JmsException checked JMSException converted to unchecked
*/
Object execute(String destinationName, ProducerCallback action) throws JmsException;
//-------------------------------------------------------------------------
// Convenience methods for sending messages
//-------------------------------------------------------------------------
/**
* Send a message to the default destination.
* <p>This will only work with a default destination specified!
* @param messageCreator callback to create a message
* @throws JmsException checked JMSException converted to unchecked
*/
void send(MessageCreator messageCreator) throws JmsException;
/**
* Send a message to the specified destination.
* The MessageCreator callback creates the message given a Session.
* @param destination the destination to send this message to
* @param messageCreator callback to create a message
* @throws JmsException checked JMSException converted to unchecked
*/
void send(Destination destination, MessageCreator messageCreator) throws JmsException;
/**
* Send a message to the specified destination.
* The MessageCreator callback creates the message given a Session.
* @param destinationName the name of the destination to send this message to
* (to be resolved to an actual destination by a DestinationResolver)
* @param messageCreator callback to create a message
* @throws JmsException checked JMSException converted to unchecked
*/
void send(String destinationName, MessageCreator messageCreator) throws JmsException;
//-------------------------------------------------------------------------
// Convenience methods for sending auto-converted messages
//-------------------------------------------------------------------------
/**
* Send the given object to the default destination, converting the object
* to a JMS message with a configured MessageConverter.
* <p>This will only work with a default destination specified!
* @param message the object to convert to a message
* @throws JmsException converted checked JMSException to unchecked
*/
void convertAndSend(Object message) throws JmsException;
/**
* Send the given object to the specified destination, converting the object
* to a JMS message with a configured MessageConverter.
* @param destination the destination to send this message to
* @param message the object to convert to a message
* @throws JmsException converted checked JMSException to unchecked
*/
void convertAndSend(Destination destination, Object message) throws JmsException;
/**
* Send the given object to the specified destination, converting the object
* to a JMS message with a configured MessageConverter.
* @param destinationName the name of the destination to send this message to
* (to be resolved to an actual destination by a DestinationResolver)
* @param message the object to convert to a message
* @throws JmsException checked JMSException converted to unchecked
*/
void convertAndSend(String destinationName, Object message) throws JmsException;
/**
* Send the given object to the default destination, converting the object
* to a JMS message with a configured MessageConverter. The MessagePostProcessor
* callback allows for modification of the message after conversion.
* <p>This will only work with a default destination specified!
* @param message the object to convert to a message
* @param postProcessor the callback to modify the message
* @throws JmsException checked JMSException converted to unchecked
*/
void convertAndSend(Object message, MessagePostProcessor postProcessor)
throws JmsException;
/**
* Send the given object to the specified destination, converting the object
* to a JMS message with a configured MessageConverter. The MessagePostProcessor
* callback allows for modification of the message after conversion.
* @param destination the destination to send this message to
* @param message the object to convert to a message
* @param postProcessor the callback to modify the message
* @throws JmsException checked JMSException converted to unchecked
*/
void convertAndSend(Destination destination, Object message, MessagePostProcessor postProcessor)
throws JmsException;
/**
* Send the given object to the specified destination, converting the object
* to a JMS message with a configured MessageConverter. The MessagePostProcessor
* callback allows for modification of the message after conversion.
* @param destinationName the name of the destination to send this message to
* (to be resolved to an actual destination by a DestinationResolver)
* @param message the object to convert to a message.
* @param postProcessor the callback to modify the message
* @throws JmsException checked JMSException converted to unchecked
*/
void convertAndSend(String destinationName, Object message, MessagePostProcessor postProcessor)
throws JmsException;
//-------------------------------------------------------------------------
// Convenience methods for receiving messages
//-------------------------------------------------------------------------
/**
* Receive a message synchronously from the default destination, but only
* wait up to a specified time for delivery.
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* <p>This will only work with a default destination specified!
* @return the message received by the consumer, or <code>null</code> if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
Message receive() throws JmsException;
/**
* Receive a message synchronously from the specified destination, but only
* wait up to a specified time for delivery.
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* @param destination the destination to receive a message from
* @return the message received by the consumer, or <code>null</code> if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
Message receive(Destination destination) throws JmsException;
/**
* Receive a message synchronously from the specified destination, but only
* wait up to a specified time for delivery.
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* @param destinationName the name of the destination to send this message to
* (to be resolved to an actual destination by a DestinationResolver)
* @return the message received by the consumer, or <code>null</code> if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
Message receive(String destinationName) throws JmsException;
/**
* Receive a message synchronously from the default destination, but only
* wait up to a specified time for delivery.
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* <p>This will only work with a default destination specified!
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
* See the JMS specification for a detailed definition of selector expressions.
* @return the message received by the consumer, or <code>null</code> if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
Message receiveSelected(String messageSelector) throws JmsException;
/**
* Receive a message synchronously from the specified destination, but only
* wait up to a specified time for delivery.
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* @param destination the destination to receive a message from
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
* See the JMS specification for a detailed definition of selector expressions.
* @return the message received by the consumer, or <code>null</code> if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
Message receiveSelected(Destination destination, String messageSelector) throws JmsException;
/**
* Receive a message synchronously from the specified destination, but only
* wait up to a specified time for delivery.
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* @param destinationName the name of the destination to send this message to
* (to be resolved to an actual destination by a DestinationResolver)
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
* See the JMS specification for a detailed definition of selector expressions.
* @return the message received by the consumer, or <code>null</code> if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
Message receiveSelected(String destinationName, String messageSelector) throws JmsException;
//-------------------------------------------------------------------------
// Convenience methods for receiving auto-converted messages
//-------------------------------------------------------------------------
/**
* Receive a message synchronously from the default destination, but only
* wait up to a specified time for delivery. Convert the message into an
* object with a configured MessageConverter.
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* <p>This will only work with a default destination specified!
* @return the message produced for the consumer or <code>null</code> if the timeout expires.
* @throws JmsException checked JMSException converted to unchecked
*/
Object receiveAndConvert() throws JmsException;
/**
* Receive a message synchronously from the specified destination, but only
* wait up to a specified time for delivery. Convert the message into an
* object with a configured MessageConverter.
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* @param destination the destination to receive a message from
* @return the message produced for the consumer or <code>null</code> if the timeout expires.
* @throws JmsException checked JMSException converted to unchecked
*/
Object receiveAndConvert(Destination destination) throws JmsException;
/**
* Receive a message synchronously from the specified destination, but only
* wait up to a specified time for delivery. Convert the message into an
* object with a configured MessageConverter.
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* @param destinationName the name of the destination to send this message to
* (to be resolved to an actual destination by a DestinationResolver)
* @return the message produced for the consumer or <code>null</code> if the timeout expires.
* @throws JmsException checked JMSException converted to unchecked
*/
Object receiveAndConvert(String destinationName) throws JmsException;
/**
* Receive a message synchronously from the default destination, but only
* wait up to a specified time for delivery. Convert the message into an
* object with a configured MessageConverter.
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* <p>This will only work with a default destination specified!
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
* See the JMS specification for a detailed definition of selector expressions.
* @return the message produced for the consumer or <code>null</code> if the timeout expires.
* @throws JmsException checked JMSException converted to unchecked
*/
Object receiveSelectedAndConvert(String messageSelector) throws JmsException;
/**
* Receive a message synchronously from the specified destination, but only
* wait up to a specified time for delivery. Convert the message into an
* object with a configured MessageConverter.
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* @param destination the destination to receive a message from
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
* See the JMS specification for a detailed definition of selector expressions.
* @return the message produced for the consumer or <code>null</code> if the timeout expires.
* @throws JmsException checked JMSException converted to unchecked
*/
Object receiveSelectedAndConvert(Destination destination, String messageSelector) throws JmsException;
/**
* Receive a message synchronously from the specified destination, but only
* wait up to a specified time for delivery. Convert the message into an
* object with a configured MessageConverter.
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* @param destinationName the name of the destination to send this message to
* (to be resolved to an actual destination by a DestinationResolver)
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
* See the JMS specification for a detailed definition of selector expressions.
* @return the message produced for the consumer or <code>null</code> if the timeout expires.
* @throws JmsException checked JMSException converted to unchecked
*/
Object receiveSelectedAndConvert(String destinationName, String messageSelector) throws JmsException;
//-------------------------------------------------------------------------
// Convenience methods for browsing messages
//-------------------------------------------------------------------------
/**
* Browse messages in the default JMS queue. The callback gives access to the JMS
* Session and QueueBrowser in order to browse the queue and react to the contents.
* @param action callback object that exposes the session/browser pair
* @return the result object from working with the session
* @throws JmsException checked JMSException converted to unchecked
*/
Object browse(BrowserCallback action) throws JmsException;
/**
* Browse messages in a JMS queue. The callback gives access to the JMS Session
* and QueueBrowser in order to browse the queue and react to the contents.
* @param queue the queue to browse
* @param action callback object that exposes the session/browser pair
* @return the result object from working with the session
* @throws JmsException checked JMSException converted to unchecked
*/
Object browse(Queue queue, BrowserCallback action) throws JmsException;
/**
* Browse messages in a JMS queue. The callback gives access to the JMS Session
* and QueueBrowser in order to browse the queue and react to the contents.
* @param queueName the name of the queue to browse
* (to be resolved to an actual destination by a DestinationResolver)
* @param action callback object that exposes the session/browser pair
* @return the result object from working with the session
* @throws JmsException checked JMSException converted to unchecked
*/
Object browse(String queueName, BrowserCallback action) throws JmsException;
/**
* Browse selected messages in a JMS queue. The callback gives access to the JMS
* Session and QueueBrowser in order to browse the queue and react to the contents.
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
* See the JMS specification for a detailed definition of selector expressions.
* @param action callback object that exposes the session/browser pair
* @return the result object from working with the session
* @throws JmsException checked JMSException converted to unchecked
*/
Object browseSelected(String messageSelector, BrowserCallback action) throws JmsException;
/**
* Browse selected messages in a JMS queue. The callback gives access to the JMS
* Session and QueueBrowser in order to browse the queue and react to the contents.
* @param queue the queue to browse
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
* See the JMS specification for a detailed definition of selector expressions.
* @param action callback object that exposes the session/browser pair
* @return the result object from working with the session
* @throws JmsException checked JMSException converted to unchecked
*/
Object browseSelected(Queue queue, String messageSelector, BrowserCallback action) throws JmsException;
/**
* Browse selected messages in a JMS queue. The callback gives access to the JMS
* Session and QueueBrowser in order to browse the queue and react to the contents.
* @param queueName the name of the queue to browse
* (to be resolved to an actual destination by a DestinationResolver)
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
* See the JMS specification for a detailed definition of selector expressions.
* @param action callback object that exposes the session/browser pair
* @return the result object from working with the session
* @throws JmsException checked JMSException converted to unchecked
*/
Object browseSelected(String queueName, String messageSelector, BrowserCallback action) throws JmsException;
}

View File

@ -0,0 +1,255 @@
/*
* Copyright 2002-2008 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.core;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueBrowser;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import org.springframework.jms.connection.JmsResourceHolder;
import org.springframework.jms.support.converter.SimpleMessageConverter102;
/**
* A subclass of {@link JmsTemplate} for the JMS 1.0.2 specification, not relying
* on JMS 1.1 methods like JmsTemplate itself. This class can be used for JMS
* 1.0.2 providers, offering the same API as JmsTemplate does for JMS 1.1 providers.
*
* <p>You must specify the domain (or style) of messaging to be either
* Point-to-Point (Queues) or Publish/Subscribe (Topics), using the
* {@link #setPubSubDomain "pubSubDomain" property}.
* Point-to-Point (Queues) is the default domain.
*
* <p>The "pubSubDomain" property is an important setting due to the use of similar
* but separate class hierarchies in the JMS 1.0.2 API. JMS 1.1 provides a new
* domain-independent API that allows for easy mix-and-match use of Point-to-Point
* and Publish/Subscribe styles.
*
* <p>This template uses a
* {@link org.springframework.jms.support.destination.DynamicDestinationResolver}
* and a {@link org.springframework.jms.support.converter.SimpleMessageConverter102}
* as default strategies for resolving a destination name and converting a message,
* respectively.
*
* @author Mark Pollack
* @author Juergen Hoeller
* @since 1.1
* @see #setConnectionFactory
* @see #setPubSubDomain
* @see javax.jms.Queue
* @see javax.jms.Topic
* @see javax.jms.QueueSession
* @see javax.jms.TopicSession
* @see javax.jms.QueueSender
* @see javax.jms.TopicPublisher
* @see javax.jms.QueueReceiver
* @see javax.jms.TopicSubscriber
*/
public class JmsTemplate102 extends JmsTemplate {
/**
* Create a new JmsTemplate102 for bean-style usage.
* <p>Note: The ConnectionFactory has to be set before using the instance.
* This constructor can be used to prepare a JmsTemplate via a BeanFactory,
* typically setting the ConnectionFactory via setConnectionFactory.
* @see #setConnectionFactory
*/
public JmsTemplate102() {
super();
}
/**
* Create a new JmsTemplate102, given a ConnectionFactory.
* @param connectionFactory the ConnectionFactory to obtain Connections from
* @param pubSubDomain whether the Publish/Subscribe domain (Topics) or
* Point-to-Point domain (Queues) should be used
* @see #setPubSubDomain
*/
public JmsTemplate102(ConnectionFactory connectionFactory, boolean pubSubDomain) {
this();
setConnectionFactory(connectionFactory);
setPubSubDomain(pubSubDomain);
afterPropertiesSet();
}
/**
* Initialize the default implementations for the template's strategies:
* DynamicDestinationResolver and SimpleMessageConverter102.
* @see #setDestinationResolver
* @see #setMessageConverter
* @see org.springframework.jms.support.destination.DynamicDestinationResolver
* @see org.springframework.jms.support.converter.SimpleMessageConverter102
*/
protected void initDefaultStrategies() {
setMessageConverter(new SimpleMessageConverter102());
}
/**
* In addition to checking if the connection factory is set, make sure
* that the supplied connection factory is of the appropriate type for
* the specified destination type: QueueConnectionFactory for queues,
* and TopicConnectionFactory for topics.
*/
public void afterPropertiesSet() {
super.afterPropertiesSet();
// Make sure that the ConnectionFactory passed is consistent.
// Some provider implementations of the ConnectionFactory interface
// implement both domain interfaces under the cover, so just check if
// the selected domain is consistent with the type of connection factory.
if (isPubSubDomain()) {
if (!(getConnectionFactory() instanceof TopicConnectionFactory)) {
throw new IllegalArgumentException(
"Specified a Spring JMS 1.0.2 template for topics " +
"but did not supply an instance of TopicConnectionFactory");
}
}
else {
if (!(getConnectionFactory() instanceof QueueConnectionFactory)) {
throw new IllegalArgumentException(
"Specified a Spring JMS 1.0.2 template for queues " +
"but did not supply an instance of QueueConnectionFactory");
}
}
}
/**
* This implementation overrides the superclass method to accept either
* a QueueConnection or a TopicConnection, depending on the domain.
*/
protected Connection getConnection(JmsResourceHolder holder) {
return holder.getConnection(isPubSubDomain() ? (Class) TopicConnection.class : QueueConnection.class);
}
/**
* This implementation overrides the superclass method to accept either
* a QueueSession or a TopicSession, depending on the domain.
*/
protected Session getSession(JmsResourceHolder holder) {
return holder.getSession(isPubSubDomain() ? (Class) TopicSession.class : QueueSession.class);
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected Connection createConnection() throws JMSException {
if (isPubSubDomain()) {
return ((TopicConnectionFactory) getConnectionFactory()).createTopicConnection();
}
else {
return ((QueueConnectionFactory) getConnectionFactory()).createQueueConnection();
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected Session createSession(Connection con) throws JMSException {
if (isPubSubDomain()) {
return ((TopicConnection) con).createTopicSession(isSessionTransacted(), getSessionAcknowledgeMode());
}
else {
return ((QueueConnection) con).createQueueSession(isSessionTransacted(), getSessionAcknowledgeMode());
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected MessageProducer doCreateProducer(Session session, Destination destination) throws JMSException {
if (isPubSubDomain()) {
return ((TopicSession) session).createPublisher((Topic) destination);
}
else {
return ((QueueSession) session).createSender((Queue) destination);
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected MessageConsumer createConsumer(Session session, Destination destination, String messageSelector)
throws JMSException {
if (isPubSubDomain()) {
return ((TopicSession) session).createSubscriber((Topic) destination, messageSelector, isPubSubNoLocal());
}
else {
return ((QueueSession) session).createReceiver((Queue) destination, messageSelector);
}
}
protected QueueBrowser createBrowser(Session session, Queue queue, String messageSelector)
throws JMSException {
if (isPubSubDomain()) {
throw new javax.jms.IllegalStateException("Cannot create QueueBrowser for a TopicSession");
}
else {
return ((QueueSession) session).createBrowser(queue, messageSelector);
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected void doSend(MessageProducer producer, Message message) throws JMSException {
if (isPubSubDomain()) {
if (isExplicitQosEnabled()) {
((TopicPublisher) producer).publish(message, getDeliveryMode(), getPriority(), getTimeToLive());
}
else {
((TopicPublisher) producer).publish(message);
}
}
else {
if (isExplicitQosEnabled()) {
((QueueSender) producer).send(message, getDeliveryMode(), getPriority(), getTimeToLive());
}
else {
((QueueSender) producer).send(message);
}
}
}
/**
* This implementation overrides the superclass method to avoid using
* JMS 1.1's Session <code>getAcknowledgeMode()</code> method.
* The best we can do here is to check the setting on the template.
* @see #getSessionAcknowledgeMode()
*/
protected boolean isClientAcknowledge(Session session) throws JMSException {
return (getSessionAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2002-2005 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.core;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
/**
* Creates a JMS message given a {@link Session}.
*
* <p>The <code>Session</code> typically is provided by an instance
* of the {@link JmsTemplate} class.
*
* <p>Implementations <i>do not</i> need to concern themselves with
* checked <code>JMSExceptions</code> (from the '<code>javax.jms</code>'
* package) that may be thrown from operations they attempt. The
* <code>JmsTemplate</code> will catch and handle these
* <code>JMSExceptions</code> appropriately.
*
* @author Mark Pollack
* @since 1.1
*/
public interface MessageCreator {
/**
* Create a {@link Message} to be sent.
* @param session the JMS {@link Session} to be used to create the
* <code>Message</code> (never <code>null</code>)
* @return the <code>Message</code> to be sent
* @throws javax.jms.JMSException if thrown by JMS API methods
*/
Message createMessage(Session session) throws JMSException;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2002-2005 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.core;
import javax.jms.JMSException;
import javax.jms.Message;
/**
* To be used with JmsTemplate's send method that convert an object to a message.
* It allows for further modification of the message after it has been processed
* by the converter. This is useful for setting of JMS Header and Properties.
*
* <p>This often as an anonymous class within a method implementation.
*
* @author Mark Pollack
* @since 1.1
* @see JmsTemplate#convertAndSend(String, Object, MessagePostProcessor)
* @see JmsTemplate#convertAndSend(javax.jms.Destination, Object, MessagePostProcessor)
* @see org.springframework.jms.support.converter.MessageConverter
*/
public interface MessagePostProcessor {
/**
* Apply a MessagePostProcessor to the message. The returned message is
* typically a modified version of the original.
* @param message the JMS message from the MessageConverter
* @return the modified version of the Message
* @throws javax.jms.JMSException if thrown by JMS API methods
*/
Message postProcessMessage(Message message) throws JMSException;
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2002-2008 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.core;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
/**
* Callback for sending a message to a JMS destination.
*
* <p>To be used with JmsTemplate's callback methods that take a ProducerCallback
* argument, often implemented as an anonymous inner class.
*
* <p>The typical implementation will perform multiple operations on the
* supplied JMS {@link Session} and {@link MessageProducer}. When used with
* a 1.0.2 provider, you need to downcast to the appropriate domain
* implementation, either {@link javax.jms.QueueSender} or
* {@link javax.jms.TopicPublisher}, to actually send a message.
*
* @author Mark Pollack
* @since 1.1
* @see JmsTemplate#execute(ProducerCallback)
* @see JmsTemplate#execute(javax.jms.Destination, ProducerCallback)
* @see JmsTemplate#execute(String, ProducerCallback)
*/
public interface ProducerCallback {
/**
* Perform operations on the given {@link Session} and {@link MessageProducer}.
* <p>The message producer is not associated with any destination unless
* when specified in the JmsTemplate call.
* @param session the JMS <code>Session</code> object to use
* @param producer the JMS <code>MessageProducer</code> object to use
* @return a result object from working with the <code>Session</code>, if any (can be <code>null</code>)
* @throws javax.jms.JMSException if thrown by JMS API methods
*/
Object doInJms(Session session, MessageProducer producer) throws JMSException;
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2002-2005 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.core;
import javax.jms.JMSException;
import javax.jms.Session;
/**
* Callback for executing any number of operations on a provided
* {@link Session}.
*
* <p>To be used with the {@link JmsTemplate#execute(SessionCallback)}
* method, often implemented as an anonymous inner class.
*
* @author Mark Pollack
* @since 1.1
* @see JmsTemplate#execute(SessionCallback)
*/
public interface SessionCallback {
/**
* Execute any number of operations against the supplied JMS
* {@link Session}, possibly returning a result.
* @param session the JMS <code>Session</code>
* @return a result object from working with the <code>Session</code>, if any (so can be <code>null</code>)
* @throws javax.jms.JMSException if thrown by JMS API methods
*/
Object doInJms(Session session) throws JMSException;
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Core package of the JMS support.
Provides a JmsTemplate class and various callback interfaces.
</body>
</html>

View File

@ -0,0 +1,118 @@
/*
* Copyright 2002-2005 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.core.support;
import javax.jms.ConnectionFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jms.core.JmsTemplate;
/**
* Convenient super class for application classes that need JMS access.
*
* <p>Requires a ConnectionFactory or a JmsTemplate instance to be set.
* It will create its own JmsTemplate if a ConnectionFactory is passed in.
* A custom JmsTemplate instance can be created for a given ConnectionFactory
* through overriding the <code>createJmsTemplate</code> method.
*
* @author Mark Pollack
* @since 1.1.1
* @see #setConnectionFactory
* @see #setJmsTemplate
* @see #createJmsTemplate
* @see org.springframework.jms.core.JmsTemplate
*/
public abstract class JmsGatewaySupport implements InitializingBean {
protected final Log logger = LogFactory.getLog(getClass());
private JmsTemplate jmsTemplate;
/**
* Set the JMS connection factory to be used by the gateway.
* Will automatically create a JmsTemplate for the given ConnectionFactory.
* @see #createJmsTemplate
* @see #setConnectionFactory(javax.jms.ConnectionFactory)
* @param connectionFactory
*/
public final void setConnectionFactory(ConnectionFactory connectionFactory) {
this.jmsTemplate = createJmsTemplate(connectionFactory);
}
/**
* Create a JmsTemplate for the given ConnectionFactory.
* Only invoked if populating the gateway with a ConnectionFactory reference.
* <p>Can be overridden in subclasses to provide a JmsTemplate instance with
* a different configuration or the JMS 1.0.2 version, JmsTemplate102.
* @param connectionFactory the JMS ConnectionFactory to create a JmsTemplate for
* @return the new JmsTemplate instance
* @see #setConnectionFactory
* @see org.springframework.jms.core.JmsTemplate102
*/
protected JmsTemplate createJmsTemplate(ConnectionFactory connectionFactory) {
return new JmsTemplate(connectionFactory);
}
/**
* Return the JMS ConnectionFactory used by the gateway.
*/
public final ConnectionFactory getConnectionFactory() {
return (this.jmsTemplate != null ? this.jmsTemplate.getConnectionFactory() : null);
}
/**
* Set the JmsTemplate for the gateway.
* @param jmsTemplate
* @see #setConnectionFactory(javax.jms.ConnectionFactory)
*/
public final void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
/**
* Return the JmsTemplate for the gateway.
*/
public final JmsTemplate getJmsTemplate() {
return jmsTemplate;
}
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
if (this.jmsTemplate == null) {
throw new IllegalArgumentException("connectionFactory or jmsTemplate is required");
}
try {
initGateway();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of JMS gateway failed: " + ex.getMessage(), ex);
}
}
/**
* Subclasses can override this for custom initialization behavior.
* Gets called after population of this instance's bean properties.
* @throws java.lang.Exception if initialization fails
*/
protected void initGateway() throws Exception {
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Classes supporting the org.springframework.jms.core package.
Contains a base class for JmsTemplate usage.
</body>
</html>

View File

@ -0,0 +1,609 @@
/*
* Copyright 2002-2008 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 java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.jms.Connection;
import javax.jms.JMSException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.Lifecycle;
import org.springframework.jms.JmsException;
import org.springframework.jms.connection.ConnectionFactoryUtils;
import org.springframework.jms.support.JmsUtils;
import org.springframework.jms.support.destination.JmsDestinationAccessor;
import org.springframework.util.ClassUtils;
/**
* Common base class for all containers which need to implement listening
* based on a JMS Connection (either shared or freshly obtained for each attempt).
* Inherits basic Connection and Session configuration handling from the
* {@link org.springframework.jms.support.JmsAccessor} base class.
*
* <p>This class provides basic lifecycle management, in particular management
* of a shared JMS Connection. Subclasses are supposed to plug into this
* lifecycle, implementing the {@link #sharedConnectionEnabled()} as well
* as the {@link #doInitialize()} and {@link #doShutdown()} template methods.
*
* <p>This base class does not assume any specific listener programming model
* or listener invoker mechanism. It just provides the general runtime
* lifecycle management needed for any kind of JMS-based listening mechanism
* that operates on a JMS Connection/Session.
*
* <p>For a concrete listener programming model, check out the
* {@link AbstractMessageListenerContainer} subclass. For a concrete listener
* invoker mechanism, check out the {@link DefaultMessageListenerContainer} class.
*
* @author Juergen Hoeller
* @since 2.0.3
* @see #sharedConnectionEnabled()
* @see #doInitialize()
* @see #doShutdown()
*/
public abstract class AbstractJmsListeningContainer extends JmsDestinationAccessor
implements Lifecycle, BeanNameAware, DisposableBean {
private String clientId;
private boolean autoStartup = true;
private String beanName;
private Connection sharedConnection;
private boolean sharedConnectionStarted = false;
protected final Object sharedConnectionMonitor = new Object();
private boolean active = false;
private boolean running = false;
private final List pausedTasks = new LinkedList();
protected final Object lifecycleMonitor = new Object();
/**
* Specify the JMS client id for a shared Connection created and used
* by this container.
* <p>Note that client ids need to be unique among all active Connections
* of the underlying JMS provider. Furthermore, a client id can only be
* assigned if the original ConnectionFactory hasn't already assigned one.
* @see javax.jms.Connection#setClientID
* @see #setConnectionFactory
*/
public void setClientId(String clientId) {
this.clientId = clientId;
}
/**
* Return the JMS client ID for the shared Connection created and used
* by this container, if any.
*/
public String getClientId() {
return this.clientId;
}
/**
* Set whether to automatically start the container after initialization.
* <p>Default is "true"; set this to "false" to allow for manual startup
* through the {@link #start()} method.
*/
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
/**
* Return the bean name that this listener container has been assigned
* in its containing bean factory, if any.
*/
protected final String getBeanName() {
return this.beanName;
}
/**
* Delegates to {@link #validateConfiguration()} and {@link #initialize()}.
*/
public void afterPropertiesSet() {
super.afterPropertiesSet();
validateConfiguration();
initialize();
}
/**
* Validate the configuration of this container.
* <p>The default implementation is empty. To be overridden in subclasses.
*/
protected void validateConfiguration() {
}
/**
* Calls {@link #shutdown()} when the BeanFactory destroys the container instance.
* @see #shutdown()
*/
public void destroy() {
shutdown();
}
//-------------------------------------------------------------------------
// Lifecycle methods for starting and stopping the container
//-------------------------------------------------------------------------
/**
* Initialize this container.
* <p>Creates a JMS Connection, starts the {@link javax.jms.Connection}
* (if {@link #setAutoStartup(boolean) "autoStartup"} hasn't been turned off),
* and calls {@link #doInitialize()}.
* @throws org.springframework.jms.JmsException if startup failed
*/
public void initialize() throws JmsException {
try {
synchronized (this.lifecycleMonitor) {
this.active = true;
this.lifecycleMonitor.notifyAll();
}
if (this.autoStartup) {
doStart();
}
doInitialize();
}
catch (JMSException ex) {
synchronized (this.sharedConnectionMonitor) {
ConnectionFactoryUtils.releaseConnection(this.sharedConnection, getConnectionFactory(), this.autoStartup);
this.sharedConnection = null;
}
throw convertJmsAccessException(ex);
}
}
/**
* Stop the shared Connection, call {@link #doShutdown()},
* and close this container.
* @throws JmsException if shutdown failed
*/
public void shutdown() throws JmsException {
logger.debug("Shutting down JMS listener container");
boolean wasRunning = false;
synchronized (this.lifecycleMonitor) {
wasRunning = this.running;
this.running = false;
this.active = false;
this.lifecycleMonitor.notifyAll();
}
// Stop shared Connection early, if necessary.
if (wasRunning && sharedConnectionEnabled()) {
try {
stopSharedConnection();
}
catch (Throwable ex) {
logger.debug("Could not stop JMS Connection on shutdown", ex);
}
}
// Shut down the invokers.
try {
doShutdown();
}
catch (JMSException ex) {
throw convertJmsAccessException(ex);
}
finally {
if (sharedConnectionEnabled()) {
synchronized (this.sharedConnectionMonitor) {
ConnectionFactoryUtils.releaseConnection(this.sharedConnection, getConnectionFactory(), false);
this.sharedConnection = null;
}
}
}
}
/**
* Return whether this container is currently active,
* that is, whether it has been set up but not shut down yet.
*/
public final boolean isActive() {
synchronized (this.lifecycleMonitor) {
return this.active;
}
}
/**
* Start this container.
* @throws JmsException if starting failed
* @see #doStart
*/
public void start() throws JmsException {
try {
doStart();
}
catch (JMSException ex) {
throw convertJmsAccessException(ex);
}
}
/**
* Start the shared Connection, if any, and notify all invoker tasks.
* @throws JMSException if thrown by JMS API methods
* @see #startSharedConnection
*/
protected void doStart() throws JMSException {
// Lazily establish a shared Connection, if necessary.
if (sharedConnectionEnabled()) {
establishSharedConnection();
}
// Reschedule paused tasks, if any.
synchronized (this.lifecycleMonitor) {
this.running = true;
this.lifecycleMonitor.notifyAll();
resumePausedTasks();
}
// Start the shared Connection, if any.
if (sharedConnectionEnabled()) {
startSharedConnection();
}
}
/**
* Stop this container.
* @throws JmsException if stopping failed
* @see #doStop
*/
public void stop() throws JmsException {
try {
doStop();
}
catch (JMSException ex) {
throw convertJmsAccessException(ex);
}
}
/**
* Notify all invoker tasks and stop the shared Connection, if any.
* @throws JMSException if thrown by JMS API methods
* @see #stopSharedConnection
*/
protected void doStop() throws JMSException {
synchronized (this.lifecycleMonitor) {
this.running = false;
this.lifecycleMonitor.notifyAll();
}
if (sharedConnectionEnabled()) {
stopSharedConnection();
}
}
/**
* Determine whether this container is currently running,
* that is, whether it has been started and not stopped yet.
* @see #start()
* @see #stop()
* @see #runningAllowed()
*/
public final boolean isRunning() {
synchronized (this.lifecycleMonitor) {
return (this.running && runningAllowed());
}
}
/**
* Check whether this container's listeners are generally allowed to run.
* <p>This implementation always returns <code>true</code>; the default 'running'
* state is purely determined by {@link #start()} / {@link #stop()}.
* <p>Subclasses may override this method to check against temporary
* conditions that prevent listeners from actually running. In other words,
* they may apply further restrictions to the 'running' state, returning
* <code>false</code> if such a restriction prevents listeners from running.
*/
protected boolean runningAllowed() {
return true;
}
//-------------------------------------------------------------------------
// Management of a shared JMS Connection
//-------------------------------------------------------------------------
/**
* Establish a shared Connection for this container.
* <p>The default implementation delegates to {@link #createSharedConnection()},
* which does one immediate attempt and throws an exception if it fails.
* Can be overridden to have a recovery process in place, retrying
* until a Connection can be successfully established.
* @throws JMSException if thrown by JMS API methods
*/
protected void establishSharedConnection() throws JMSException {
synchronized (this.sharedConnectionMonitor) {
if (this.sharedConnection == null) {
this.sharedConnection = createSharedConnection();
logger.debug("Established shared JMS Connection");
}
}
}
/**
* Refresh the shared Connection that this container holds.
* <p>Called on startup and also after an infrastructure exception
* that occurred during invoker setup and/or execution.
* @throws JMSException if thrown by JMS API methods
*/
protected final void refreshSharedConnection() throws JMSException {
synchronized (this.sharedConnectionMonitor) {
ConnectionFactoryUtils.releaseConnection(
this.sharedConnection, getConnectionFactory(), this.sharedConnectionStarted);
this.sharedConnection = null;
this.sharedConnection = createSharedConnection();
if (this.sharedConnectionStarted) {
this.sharedConnection.start();
}
}
}
/**
* Create a shared Connection for this container.
* <p>The default implementation creates a standard Connection
* and prepares it through {@link #prepareSharedConnection}.
* @return the prepared Connection
* @throws JMSException if the creation failed
*/
protected Connection createSharedConnection() throws JMSException {
Connection con = createConnection();
try {
prepareSharedConnection(con);
return con;
}
catch (JMSException ex) {
JmsUtils.closeConnection(con);
throw ex;
}
}
/**
* Prepare the given Connection, which is about to be registered
* as shared Connection for this container.
* <p>The default implementation sets the specified client id, if any.
* Subclasses can override this to apply further settings.
* @param connection the Connection to prepare
* @throws JMSException if the preparation efforts failed
* @see #getClientId()
*/
protected void prepareSharedConnection(Connection connection) throws JMSException {
String clientId = getClientId();
if (clientId != null) {
connection.setClientID(clientId);
}
}
/**
* Start the shared Connection.
* @throws JMSException if thrown by JMS API methods
* @see javax.jms.Connection#start()
*/
protected void startSharedConnection() throws JMSException {
synchronized (this.sharedConnectionMonitor) {
this.sharedConnectionStarted = true;
if (this.sharedConnection != null) {
try {
this.sharedConnection.start();
}
catch (javax.jms.IllegalStateException ex) {
logger.debug("Ignoring Connection start exception - assuming already started: " + ex);
}
}
}
}
/**
* Stop the shared Connection.
* @throws JMSException if thrown by JMS API methods
* @see javax.jms.Connection#start()
*/
protected void stopSharedConnection() throws JMSException {
synchronized (this.sharedConnectionMonitor) {
this.sharedConnectionStarted = false;
if (this.sharedConnection != null) {
try {
this.sharedConnection.stop();
}
catch (javax.jms.IllegalStateException ex) {
logger.debug("Ignoring Connection stop exception - assuming already stopped: " + ex);
}
}
}
}
/**
* Return the shared JMS Connection maintained by this container.
* Available after initialization.
* @return the shared Connection (never <code>null</code>)
* @throws IllegalStateException if this container does not maintain a
* shared Connection, or if the Connection hasn't been initialized yet
* @see #sharedConnectionEnabled()
*/
protected final Connection getSharedConnection() {
if (!sharedConnectionEnabled()) {
throw new IllegalStateException(
"This listener container does not maintain a shared Connection");
}
synchronized (this.sharedConnectionMonitor) {
if (this.sharedConnection == null) {
throw new SharedConnectionNotInitializedException(
"This listener container's shared Connection has not been initialized yet");
}
return this.sharedConnection;
}
}
//-------------------------------------------------------------------------
// Management of paused tasks
//-------------------------------------------------------------------------
/**
* Take the given task object and reschedule it, either immediately if
* this container is currently running, or later once this container
* has been restarted.
* <p>If this container has already been shut down, the task will not
* get rescheduled at all.
* @param task the task object to reschedule
* @return whether the task has been rescheduled
* (either immediately or for a restart of this container)
* @see #doRescheduleTask
*/
protected final boolean rescheduleTaskIfNecessary(Object task) {
if (this.running) {
try {
doRescheduleTask(task);
}
catch (RuntimeException ex) {
logRejectedTask(task, ex);
this.pausedTasks.add(task);
}
return true;
}
else if (this.active) {
this.pausedTasks.add(task);
return true;
}
else {
return false;
}
}
/**
* Try to resume all paused tasks.
* Tasks for which rescheduling failed simply remain in paused mode.
*/
protected void resumePausedTasks() {
synchronized (this.lifecycleMonitor) {
if (!this.pausedTasks.isEmpty()) {
for (Iterator it = this.pausedTasks.iterator(); it.hasNext();) {
Object task = it.next();
try {
doRescheduleTask(task);
it.remove();
if (logger.isDebugEnabled()) {
logger.debug("Resumed paused task: " + task);
}
}
catch (RuntimeException ex) {
logRejectedTask(task, ex);
// Keep the task in paused mode...
}
}
}
}
}
public int getPausedTaskCount() {
synchronized (this.lifecycleMonitor) {
return this.pausedTasks.size();
}
}
/**
* Reschedule the given task object immediately.
* <p>To be implemented by subclasses if they ever call
* <code>rescheduleTaskIfNecessary</code>.
* This implementation throws an UnsupportedOperationException.
* @param task the task object to reschedule
* @see #rescheduleTaskIfNecessary
*/
protected void doRescheduleTask(Object task) {
throw new UnsupportedOperationException(
ClassUtils.getShortName(getClass()) + " does not support rescheduling of tasks");
}
/**
* Log a task that has been rejected by {@link #doRescheduleTask}.
* <p>The default implementation simply logs a corresponding message
* at debug level.
* @param task the rejected task object
* @param ex the exception thrown from {@link #doRescheduleTask}
*/
protected void logRejectedTask(Object task, RuntimeException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Listener container task [" + task + "] has been rejected and paused: " + ex);
}
}
//-------------------------------------------------------------------------
// Template methods to be implemented by subclasses
//-------------------------------------------------------------------------
/**
* Return whether a shared JMS Connection should be maintained
* by this container base class.
* @see #getSharedConnection()
*/
protected abstract boolean sharedConnectionEnabled();
/**
* Register any invokers within this container.
* <p>Subclasses need to implement this method for their specific
* invoker management process.
* <p>A shared JMS Connection, if any, will already have been
* started at this point.
* @throws JMSException if registration failed
* @see #getSharedConnection()
*/
protected abstract void doInitialize() throws JMSException;
/**
* Close the registered invokers.
* <p>Subclasses need to implement this method for their specific
* invoker management process.
* <p>A shared JMS Connection, if any, will automatically be closed
* <i>afterwards</i>.
* @throws JMSException if shutdown failed
* @see #shutdown()
*/
protected abstract void doShutdown() throws JMSException;
/**
* Exception that indicates that the initial setup of this container's
* shared JMS Connection failed. This is indicating to invokers that they need
* to establish the shared Connection themselves on first access.
*/
public static class SharedConnectionNotInitializedException extends RuntimeException {
/**
* Create a new SharedConnectionNotInitializedException.
* @param msg the detail message
*/
protected SharedConnectionNotInitializedException(String msg) {
super(msg);
}
}
}

View File

@ -0,0 +1,676 @@
/*
* Copyright 2002-2008 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 javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.Topic;
import org.springframework.jms.support.JmsUtils;
import org.springframework.util.Assert;
/**
* Abstract base class for message listener containers. Can either host
* a standard JMS {@link javax.jms.MessageListener} or a Spring-specific
* {@link SessionAwareMessageListener}.
*
* <p>Usually holds a single JMS {@link Connection} that all listeners are
* supposed to be registered on, which is the standard JMS way of managing
* listeners. Can alternatively also be used with a fresh Connection per
* listener, for J2EE-style XA-aware JMS messaging. The actual registration
* process is up to concrete subclasses.
*
* <p><b>NOTE:</b> The default behavior of this message listener container
* is to <b>never</b> propagate an exception thrown by a message listener up to
* the JMS provider. Instead, it will log any such exception at the error level.
* This means that from the perspective of the attendant JMS provider no such
* listener will ever fail.
*
* <p>The listener container offers the following message acknowledgment options:
* <ul>
* <li>"sessionAcknowledgeMode" set to "AUTO_ACKNOWLEDGE" (default):
* Automatic message acknowledgment <i>before</i> listener execution;
* no redelivery in case of exception thrown.
* <li>"sessionAcknowledgeMode" set to "CLIENT_ACKNOWLEDGE":
* Automatic message acknowledgment <i>after</i> successful listener execution;
* no redelivery in case of exception thrown.
* <li>"sessionAcknowledgeMode" set to "DUPS_OK_ACKNOWLEDGE":
* <i>Lazy</i> message acknowledgment during or after listener execution;
* <i>potential redelivery</i> in case of exception thrown.
* <li>"sessionTransacted" set to "true":
* Transactional acknowledgment after successful listener execution;
* <i>guaranteed redelivery</i> in case of exception thrown.
* </ul>
* The exact behavior might vary according to the concrete listener container
* and JMS provider used.
*
* <p>There are two solutions to the duplicate processing problem:
* <ul>
* <li>Either add <i>duplicate message detection</i> to your listener, in the
* form of a business entity existence check or a protocol table check. This
* usually just needs to be done in case of the JMSRedelivered flag being
* set on the incoming message (else just process straightforwardly).
* <li>Or wrap the <i>entire processing with an XA transaction</i>, covering the
* reception of the message as well as the execution of the message listener.
* This is only supported by {@link DefaultMessageListenerContainer}, through
* specifying a "transactionManager" (typically a
* {@link org.springframework.transaction.jta.JtaTransactionManager}, with
* a corresponding XA-aware JMS {@link javax.jms.ConnectionFactory} passed in as
* "connectionFactory").
* </ul>
* Note that XA transaction coordination adds significant runtime overhead,
* so it might be feasible to avoid it unless absolutely necessary.
*
* <p><b>Recommendations:</b>
* <ul>
* <li>The general recommendation is to set "sessionTransacted" to "true",
* typically in combination with local database transactions triggered by the
* listener implementation, through Spring's standard transaction facilities.
* This will work nicely in Tomcat or in a standalone environment, often
* combined with custom duplicate message detection (if it is unacceptable
* to ever process the same message twice).
* <li>Alternatively, specify a
* {@link org.springframework.transaction.jta.JtaTransactionManager} as
* "transactionManager" for a fully XA-aware JMS provider - typically when
* running on a J2EE server, but also for other environments with a JTA
* transaction manager present. This will give full "exactly-once" guarantees
* without custom duplicate message checks, at the price of additional
* runtime processing overhead.
* </ul>
*
* <p>Note that it is also possible to specify a
* {@link org.springframework.jms.connection.JmsTransactionManager} as external
* "transactionManager", providing fully synchronized Spring transactions based
* on local JMS transactions. The effect is similar to "sessionTransacted" set
* to "true", the difference being that this external transaction management
* will also affect independent JMS access code within the service layer
* (e.g. based on {@link org.springframework.jms.core.JmsTemplate} or
* {@link org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy}),
* not just direct JMS Session usage in a {@link SessionAwareMessageListener}.
*
* @author Juergen Hoeller
* @since 2.0
* @see #setMessageListener
* @see javax.jms.MessageListener
* @see SessionAwareMessageListener
* @see #handleListenerException
* @see DefaultMessageListenerContainer
* @see SimpleMessageListenerContainer
* @see org.springframework.jms.listener.endpoint.JmsMessageEndpointManager
*/
public abstract class AbstractMessageListenerContainer extends AbstractJmsListeningContainer {
private volatile Object destination;
private volatile String messageSelector;
private volatile Object messageListener;
private boolean subscriptionDurable = false;
private String durableSubscriptionName;
private ExceptionListener exceptionListener;
private boolean exposeListenerSession = true;
private boolean acceptMessagesWhileStopping = false;
/**
* Set the destination to receive messages from.
* <p>Alternatively, specify a "destinationName", to be dynamically
* resolved via the {@link org.springframework.jms.support.destination.DestinationResolver}.
* <p>Note: The destination may be replaced at runtime, with the listener
* container picking up the new destination immediately (works e.g. with
* DefaultMessageListenerContainer, as long as the cache level is less than
* CACHE_CONSUMER). However, this is considered advanced usage; use it with care!
* @see #setDestinationName(String)
*/
public void setDestination(Destination destination) {
Assert.notNull(destination, "'destination' must not be null");
this.destination = destination;
if (destination instanceof Topic && !(destination instanceof Queue)) {
// Clearly a Topic: let's set the "pubSubDomain" flag accordingly.
setPubSubDomain(true);
}
}
/**
* Return the destination to receive messages from. Will be <code>null</code>
* if the configured destination is not an actual {@link Destination} type;
* c.f. {@link #setDestinationName(String) when the destination is a String}.
*/
public Destination getDestination() {
return (this.destination instanceof Destination ? (Destination) this.destination : null);
}
/**
* Set the name of the destination to receive messages from.
* <p>The specified name will be dynamically resolved via the configured
* {@link #setDestinationResolver destination resolver}.
* <p>Alternatively, specify a JMS {@link Destination} object as "destination".
* <p>Note: The destination may be replaced at runtime, with the listener
* container picking up the new destination immediately (works e.g. with
* DefaultMessageListenerContainer, as long as the cache level is less than
* CACHE_CONSUMER). However, this is considered advanced usage; use it with care!
* @param destinationName the desired destination (can be <code>null</code>)
* @see #setDestination(javax.jms.Destination)
*/
public void setDestinationName(String destinationName) {
Assert.notNull(destinationName, "'destinationName' must not be null");
this.destination = destinationName;
}
/**
* Return the name of the destination to receive messages from.
* Will be <code>null</code> if the configured destination is not a
* {@link String} type; c.f. {@link #setDestination(Destination) when
* it is an actual Destination}.
*/
public String getDestinationName() {
return (this.destination instanceof String ? (String) this.destination : null);
}
/**
* Return a descriptive String for this container's JMS destination
* (never <code>null</code>).
*/
protected String getDestinationDescription() {
return this.destination.toString();
}
/**
* Set the JMS message selector expression (or <code>null</code> if none).
* Default is none.
* <p>See the JMS specification for a detailed definition of selector expressions.
* <p>Note: The message selector may be replaced at runtime, with the listener
* container picking up the new selector value immediately (works e.g. with
* DefaultMessageListenerContainer, as long as the cache level is less than
* CACHE_CONSUMER). However, this is considered advanced usage; use it with care!
*/
public void setMessageSelector(String messageSelector) {
this.messageSelector = messageSelector;
}
/**
* Return the JMS message selector expression (or <code>null</code> if none).
*/
public String getMessageSelector() {
return this.messageSelector;
}
/**
* Set the message listener implementation to register.
* This can be either a standard JMS {@link MessageListener} object
* or a Spring {@link SessionAwareMessageListener} object.
* <p>Note: The message listener may be replaced at runtime, with the listener
* container picking up the new listener object immediately (works e.g. with
* DefaultMessageListenerContainer, as long as the cache level is less than
* CACHE_CONSUMER). However, this is considered advanced usage; use it with care!
* @throws IllegalArgumentException if the supplied listener is not a
* {@link MessageListener} or a {@link SessionAwareMessageListener}
* @see javax.jms.MessageListener
* @see SessionAwareMessageListener
*/
public void setMessageListener(Object messageListener) {
checkMessageListener(messageListener);
this.messageListener = messageListener;
if (this.durableSubscriptionName == null) {
this.durableSubscriptionName = getDefaultSubscriptionName(messageListener);
}
}
/**
* Return the message listener object to register.
*/
public Object getMessageListener() {
return this.messageListener;
}
/**
* Check the given message listener, throwing an exception
* if it does not correspond to a supported listener type.
* <p>By default, only a standard JMS {@link MessageListener} object or a
* Spring {@link SessionAwareMessageListener} object will be accepted.
* @param messageListener the message listener object to check
* @throws IllegalArgumentException if the supplied listener is not a
* {@link MessageListener} or a {@link SessionAwareMessageListener}
* @see javax.jms.MessageListener
* @see SessionAwareMessageListener
*/
protected void checkMessageListener(Object messageListener) {
if (!(messageListener instanceof MessageListener ||
messageListener instanceof SessionAwareMessageListener)) {
throw new IllegalArgumentException(
"Message listener needs to be of type [" + MessageListener.class.getName() +
"] or [" + SessionAwareMessageListener.class.getName() + "]");
}
}
/**
* Determine the default subscription name for the given message listener.
* @param messageListener the message listener object to check
* @return the default subscription name
* @see SubscriptionNameProvider
*/
protected String getDefaultSubscriptionName(Object messageListener) {
if (messageListener instanceof SubscriptionNameProvider) {
return ((SubscriptionNameProvider) messageListener).getSubscriptionName();
}
else {
return messageListener.getClass().getName();
}
}
/**
* Set whether to make the subscription durable. The durable subscription name
* to be used can be specified through the "durableSubscriptionName" property.
* <p>Default is "false". Set this to "true" to register a durable subscription,
* typically in combination with a "durableSubscriptionName" value (unless
* your message listener class name is good enough as subscription name).
* <p>Only makes sense when listening to a topic (pub-sub domain).
* @see #setDurableSubscriptionName
*/
public void setSubscriptionDurable(boolean subscriptionDurable) {
this.subscriptionDurable = subscriptionDurable;
}
/**
* Return whether to make the subscription durable.
*/
public boolean isSubscriptionDurable() {
return this.subscriptionDurable;
}
/**
* Set the name of a durable subscription to create. To be applied in case
* of a topic (pub-sub domain) with subscription durability activated.
* <p>The durable subscription name needs to be unique within this client's
* JMS client id. Default is the class name of the specified message listener.
* <p>Note: Only 1 concurrent consumer (which is the default of this
* message listener container) is allowed for each durable subscription.
* @see #setSubscriptionDurable
* @see #setClientId
* @see #setMessageListener
*/
public void setDurableSubscriptionName(String durableSubscriptionName) {
this.durableSubscriptionName = durableSubscriptionName;
}
/**
* Return the name of a durable subscription to create, if any.
*/
public String getDurableSubscriptionName() {
return this.durableSubscriptionName;
}
/**
* Set the JMS ExceptionListener to notify in case of a JMSException thrown
* by the registered message listener or the invocation infrastructure.
*/
public void setExceptionListener(ExceptionListener exceptionListener) {
this.exceptionListener = exceptionListener;
}
/**
* Return the JMS ExceptionListener to notify in case of a JMSException thrown
* by the registered message listener or the invocation infrastructure, if any.
*/
public ExceptionListener getExceptionListener() {
return this.exceptionListener;
}
/**
* Set whether to expose the listener JMS Session to a registered
* {@link SessionAwareMessageListener} as well as to
* {@link org.springframework.jms.core.JmsTemplate} calls.
* <p>Default is "true", reusing the listener's {@link Session}.
* Turn this off to expose a fresh JMS Session fetched from the same
* underlying JMS {@link Connection} instead, which might be necessary
* on some JMS providers.
* <p>Note that Sessions managed by an external transaction manager will
* always get exposed to {@link org.springframework.jms.core.JmsTemplate}
* calls. So in terms of JmsTemplate exposure, this setting only affects
* locally transacted Sessions.
* @see SessionAwareMessageListener
*/
public void setExposeListenerSession(boolean exposeListenerSession) {
this.exposeListenerSession = exposeListenerSession;
}
/**
* Return whether to expose the listener JMS {@link Session} to a
* registered {@link SessionAwareMessageListener}.
*/
public boolean isExposeListenerSession() {
return this.exposeListenerSession;
}
/**
* Set whether to accept received messages while the listener container
* in the process of stopping.
* <p>Default is "false", rejecting such messages through aborting the
* receive attempt. Switch this flag on to fully process such messages
* even in the stopping phase, with the drawback that even newly sent
* messages might still get processed (if coming in before all receive
* timeouts have expired).
* <p><b>NOTE:</b> Aborting receive attempts for such incoming messages
* might lead to the provider's retry count decreasing for the affected
* messages. If you have a high number of concurrent consumers, make sure
* that the number of retries is higher than the number of consumers,
* to be on the safe side for all potential stopping scenarios.
*/
public void setAcceptMessagesWhileStopping(boolean acceptMessagesWhileStopping) {
this.acceptMessagesWhileStopping = acceptMessagesWhileStopping;
}
/**
* Return whether to accept received messages while the listener container
* in the process of stopping.
*/
public boolean isAcceptMessagesWhileStopping() {
return this.acceptMessagesWhileStopping;
}
protected void validateConfiguration() {
if (this.destination == null) {
throw new IllegalArgumentException("Property 'destination' or 'destinationName' is required");
}
if (isSubscriptionDurable() && !isPubSubDomain()) {
throw new IllegalArgumentException("A durable subscription requires a topic (pub-sub domain)");
}
}
//-------------------------------------------------------------------------
// Template methods for listener execution
//-------------------------------------------------------------------------
/**
* Execute the specified listener,
* committing or rolling back the transaction afterwards (if necessary).
* @param session the JMS Session to operate on
* @param message the received JMS Message
* @see #invokeListener
* @see #commitIfNecessary
* @see #rollbackOnExceptionIfNecessary
* @see #handleListenerException
*/
protected void executeListener(Session session, Message message) {
try {
doExecuteListener(session, message);
}
catch (Throwable ex) {
handleListenerException(ex);
}
}
/**
* Execute the specified listener,
* committing or rolling back the transaction afterwards (if necessary).
* @param session the JMS Session to operate on
* @param message the received JMS Message
* @throws JMSException if thrown by JMS API methods
* @see #invokeListener
* @see #commitIfNecessary
* @see #rollbackOnExceptionIfNecessary
* @see #convertJmsAccessException
*/
protected void doExecuteListener(Session session, Message message) throws JMSException {
if (!isAcceptMessagesWhileStopping() && !isRunning()) {
if (logger.isWarnEnabled()) {
logger.warn("Rejecting received message because of the listener container " +
"having been stopped in the meantime: " + message);
}
rollbackIfNecessary(session);
throw new MessageRejectedWhileStoppingException();
}
try {
invokeListener(session, message);
}
catch (JMSException ex) {
rollbackOnExceptionIfNecessary(session, ex);
throw ex;
}
catch (RuntimeException ex) {
rollbackOnExceptionIfNecessary(session, ex);
throw ex;
}
catch (Error err) {
rollbackOnExceptionIfNecessary(session, err);
throw err;
}
commitIfNecessary(session, message);
}
/**
* Invoke the specified listener: either as standard JMS MessageListener
* or (preferably) as Spring SessionAwareMessageListener.
* @param session the JMS Session to operate on
* @param message the received JMS Message
* @throws JMSException if thrown by JMS API methods
* @see #setMessageListener
*/
protected void invokeListener(Session session, Message message) throws JMSException {
Object listener = getMessageListener();
if (listener instanceof SessionAwareMessageListener) {
doInvokeListener((SessionAwareMessageListener) listener, session, message);
}
else if (listener instanceof MessageListener) {
doInvokeListener((MessageListener) listener, message);
}
else if (listener != null) {
throw new IllegalArgumentException(
"Only MessageListener and SessionAwareMessageListener supported: " + listener);
}
else {
throw new IllegalStateException("No message listener specified - see property 'messageListener'");
}
}
/**
* Invoke the specified listener as Spring SessionAwareMessageListener,
* exposing a new JMS Session (potentially with its own transaction)
* to the listener if demanded.
* @param listener the Spring SessionAwareMessageListener to invoke
* @param session the JMS Session to operate on
* @param message the received JMS Message
* @throws JMSException if thrown by JMS API methods
* @see SessionAwareMessageListener
* @see #setExposeListenerSession
*/
protected void doInvokeListener(SessionAwareMessageListener listener, Session session, Message message)
throws JMSException {
Connection conToClose = null;
Session sessionToClose = null;
try {
Session sessionToUse = session;
if (!isExposeListenerSession()) {
// We need to expose a separate Session.
conToClose = createConnection();
sessionToClose = createSession(conToClose);
sessionToUse = sessionToClose;
}
// Actually invoke the message listener...
listener.onMessage(message, sessionToUse);
// Clean up specially exposed Session, if any.
if (sessionToUse != session) {
if (sessionToUse.getTransacted() && isSessionLocallyTransacted(sessionToUse)) {
// Transacted session created by this container -> commit.
JmsUtils.commitIfNecessary(sessionToUse);
}
}
}
finally {
JmsUtils.closeSession(sessionToClose);
JmsUtils.closeConnection(conToClose);
}
}
/**
* Invoke the specified listener as standard JMS MessageListener.
* <p>Default implementation performs a plain invocation of the
* <code>onMessage</code> method.
* @param listener the JMS MessageListener to invoke
* @param message the received JMS Message
* @throws JMSException if thrown by JMS API methods
* @see javax.jms.MessageListener#onMessage
*/
protected void doInvokeListener(MessageListener listener, Message message) throws JMSException {
listener.onMessage(message);
}
/**
* Perform a commit or message acknowledgement, as appropriate.
* @param session the JMS Session to commit
* @param message the Message to acknowledge
* @throws javax.jms.JMSException in case of commit failure
*/
protected void commitIfNecessary(Session session, Message message) throws JMSException {
// Commit session or acknowledge message.
if (session.getTransacted()) {
// Commit necessary - but avoid commit call within a JTA transaction.
if (isSessionLocallyTransacted(session)) {
// Transacted session created by this container -> commit.
JmsUtils.commitIfNecessary(session);
}
}
else if (isClientAcknowledge(session)) {
message.acknowledge();
}
}
/**
* Perform a rollback, if appropriate.
* @param session the JMS Session to rollback
* @throws javax.jms.JMSException in case of a rollback error
*/
protected void rollbackIfNecessary(Session session) throws JMSException {
if (session.getTransacted() && isSessionLocallyTransacted(session)) {
// Transacted session created by this container -> rollback.
JmsUtils.rollbackIfNecessary(session);
}
}
/**
* Perform a rollback, handling rollback exceptions properly.
* @param session the JMS Session to rollback
* @param ex the thrown application exception or error
* @throws javax.jms.JMSException in case of a rollback error
*/
protected void rollbackOnExceptionIfNecessary(Session session, Throwable ex) throws JMSException {
try {
if (session.getTransacted() && isSessionLocallyTransacted(session)) {
// Transacted session created by this container -> rollback.
if (logger.isDebugEnabled()) {
logger.debug("Initiating transaction rollback on application exception", ex);
}
JmsUtils.rollbackIfNecessary(session);
}
}
catch (IllegalStateException ex2) {
logger.debug("Could not roll back because Session already closed", ex2);
}
catch (JMSException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
catch (RuntimeException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
catch (Error err) {
logger.error("Application exception overridden by rollback error", ex);
throw err;
}
}
/**
* Check whether the given Session is locally transacted, that is, whether
* its transaction is managed by this listener container's Session handling
* and not by an external transaction coordinator.
* <p>Note: The Session's own transacted flag will already have been checked
* before. This method is about finding out whether the Session's transaction
* is local or externally coordinated.
* @param session the Session to check
* @return whether the given Session is locally transacted
* @see #isSessionTransacted()
* @see org.springframework.jms.connection.ConnectionFactoryUtils#isSessionTransactional
*/
protected boolean isSessionLocallyTransacted(Session session) {
return isSessionTransacted();
}
/**
* Handle the given exception that arose during listener execution.
* <p>The default implementation logs the exception at error level,
* not propagating it to the JMS provider - assuming that all handling of
* acknowledgement and/or transactions is done by this listener container.
* This can be overridden in subclasses.
* @param ex the exception to handle
*/
protected void handleListenerException(Throwable ex) {
if (ex instanceof MessageRejectedWhileStoppingException) {
// Internal exception - has been handled before.
return;
}
if (ex instanceof JMSException) {
invokeExceptionListener((JMSException) ex);
}
if (isActive()) {
// Regular case: failed while active.
// Log at error level.
logger.warn("Execution of JMS message listener failed", ex);
}
else {
// Rare case: listener thread failed after container shutdown.
// Log at debug level, to avoid spamming the shutdown log.
logger.debug("Listener exception after container shutdown", ex);
}
}
/**
* Invoke the registered JMS ExceptionListener, if any.
* @param ex the exception that arose during JMS processing
* @see #setExceptionListener
*/
protected void invokeExceptionListener(JMSException ex) {
ExceptionListener exceptionListener = getExceptionListener();
if (exceptionListener != null) {
exceptionListener.onException(ex);
}
}
/**
* Internal exception class that indicates a rejected message on shutdown.
* Used to trigger a rollback for an external transaction manager in that case.
*/
private static class MessageRejectedWhileStoppingException extends RuntimeException {
}
}

View File

@ -0,0 +1,514 @@
/*
* Copyright 2002-2008 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 javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.Topic;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.jms.connection.ConnectionFactoryUtils;
import org.springframework.jms.connection.JmsResourceHolder;
import org.springframework.jms.connection.SingleConnectionFactory;
import org.springframework.jms.support.JmsUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.ResourceTransactionManager;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionSynchronizationUtils;
/**
* Base class for listener container implementations which are based on polling.
* Provides support for listener handling based on {@link javax.jms.MessageConsumer},
* optionally participating in externally managed transactions.
*
* <p>This listener container variant is built for repeated polling attempts,
* each invoking the {@link #receiveAndExecute} method. The MessageConsumer used
* may be reobtained fo reach attempt or cached inbetween attempts; this is up
* to the concrete implementation. The receive timeout for each attempt can be
* configured through the {@link #setReceiveTimeout "receiveTimeout"} property.
*
* <p>The underlying mechanism is based on standard JMS MessageConsumer handling,
* which is perfectly compatible with both native JMS and JMS in a J2EE environment.
* Neither the JMS <code>MessageConsumer.setMessageListener</code> facility
* nor the JMS ServerSessionPool facility is required. A further advantage
* of this approach is full control over the listening process, allowing for
* custom scaling and throttling and of concurrent message processing
* (which is up to concrete subclasses).
*
* <p>Message reception and listener execution can automatically be wrapped
* in transactions through passing a Spring
* {@link org.springframework.transaction.PlatformTransactionManager} into the
* {@link #setTransactionManager "transactionManager"} property. This will usually
* be a {@link org.springframework.transaction.jta.JtaTransactionManager} in a
* J2EE enviroment, in combination with a JTA-aware JMS ConnectionFactory obtained
* from JNDI (check your J2EE server's documentation).
*
* <p>This base class does not assume any specific mechanism for asynchronous
* execution of polling invokers. Check out {@link DefaultMessageListenerContainer}
* for a concrete implementation which is based on Spring's
* {@link org.springframework.core.task.TaskExecutor} abstraction,
* including dynamic scaling of concurrent consumers and automatic self recovery.
*
* @author Juergen Hoeller
* @since 2.0.3
* @see #createListenerConsumer
* @see #receiveAndExecute
* @see #setTransactionManager
*/
public abstract class AbstractPollingMessageListenerContainer extends AbstractMessageListenerContainer
implements BeanNameAware {
/**
* The default receive timeout: 1000 ms = 1 second.
*/
public static final long DEFAULT_RECEIVE_TIMEOUT = 1000;
private final MessageListenerContainerResourceFactory transactionalResourceFactory =
new MessageListenerContainerResourceFactory();
private boolean sessionTransactedCalled = false;
private boolean pubSubNoLocal = false;
private PlatformTransactionManager transactionManager;
private DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
private long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT;
public void setSessionTransacted(boolean sessionTransacted) {
super.setSessionTransacted(sessionTransacted);
this.sessionTransactedCalled = true;
}
/**
* Set whether to inhibit the delivery of messages published by its own connection.
* Default is "false".
* @see javax.jms.TopicSession#createSubscriber(javax.jms.Topic, String, boolean)
*/
public void setPubSubNoLocal(boolean pubSubNoLocal) {
this.pubSubNoLocal = pubSubNoLocal;
}
/**
* Return whether to inhibit the delivery of messages published by its own connection.
*/
protected boolean isPubSubNoLocal() {
return this.pubSubNoLocal;
}
/**
* Specify the Spring {@link org.springframework.transaction.PlatformTransactionManager}
* to use for transactional wrapping of message reception plus listener execution.
* <p>Default is none, not performing any transactional wrapping.
* If specified, this will usually be a Spring
* {@link org.springframework.transaction.jta.JtaTransactionManager} or one
* of its subclasses, in combination with a JTA-aware ConnectionFactory that
* this message listener container obtains its Connections from.
* <p><b>Note: Consider the use of local JMS transactions instead.</b>
* Simply switch the {@link #setSessionTransacted "sessionTransacted"} flag
* to "true" in order to use a locally transacted JMS Session for the entire
* receive processing, including any Session operations performed by a
* {@link SessionAwareMessageListener} (e.g. sending a response message).
* Alternatively, a {@link org.springframework.jms.connection.JmsTransactionManager}
* may be used for fully synchronized Spring transactions based on local JMS
* transactions. Check {@link AbstractMessageListenerContainer}'s javadoc for
* a discussion of transaction choices and message redelivery scenarios.
* @see org.springframework.transaction.jta.JtaTransactionManager
* @see org.springframework.jms.connection.JmsTransactionManager
*/
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* Return the Spring PlatformTransactionManager to use for transactional
* wrapping of message reception plus listener execution.
*/
protected final PlatformTransactionManager getTransactionManager() {
return this.transactionManager;
}
/**
* Specify the transaction name to use for transactional wrapping.
* Default is the bean name of this listener container, if any.
* @see org.springframework.transaction.TransactionDefinition#getName()
*/
public void setTransactionName(String transactionName) {
this.transactionDefinition.setName(transactionName);
}
/**
* Specify the transaction timeout to use for transactional wrapping, in <b>seconds</b>.
* Default is none, using the transaction manager's default timeout.
* @see org.springframework.transaction.TransactionDefinition#getTimeout()
* @see #setReceiveTimeout
*/
public void setTransactionTimeout(int transactionTimeout) {
this.transactionDefinition.setTimeout(transactionTimeout);
}
/**
* Set the timeout to use for receive calls, in <b>milliseconds</b>.
* The default is 1000 ms, that is, 1 second.
* <p><b>NOTE:</b> This value needs to be smaller than the transaction
* timeout used by the transaction manager (in the appropriate unit,
* of course). -1 indicates no timeout at all; however, this is only
* feasible if not running within a transaction manager.
* @see javax.jms.MessageConsumer#receive(long)
* @see javax.jms.MessageConsumer#receive()
* @see #setTransactionTimeout
*/
public void setReceiveTimeout(long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
}
public void initialize() {
// Set sessionTransacted=true in case of a non-JTA transaction manager.
if (!this.sessionTransactedCalled &&
this.transactionManager instanceof ResourceTransactionManager &&
!TransactionSynchronizationUtils.sameResourceFactory(
(ResourceTransactionManager) this.transactionManager, getConnectionFactory())) {
super.setSessionTransacted(true);
}
// Use bean name as default transaction name.
if (this.transactionDefinition.getName() == null) {
this.transactionDefinition.setName(getBeanName());
}
// Proceed with superclass initialization.
super.initialize();
}
/**
* Create a MessageConsumer for the given JMS Session,
* registering a MessageListener for the specified listener.
* @param session the JMS Session to work on
* @return the MessageConsumer
* @throws javax.jms.JMSException if thrown by JMS methods
* @see #receiveAndExecute
*/
protected MessageConsumer createListenerConsumer(Session session) throws JMSException {
Destination destination = getDestination();
if (destination == null) {
destination = resolveDestinationName(session, getDestinationName());
}
return createConsumer(session, destination);
}
/**
* Execute the listener for a message received from the given consumer,
* wrapping the entire operation in an external transaction if demanded.
* @param session the JMS Session to work on
* @param consumer the MessageConsumer to work on
* @return whether a message has been received
* @throws JMSException if thrown by JMS methods
* @see #doReceiveAndExecute
*/
protected boolean receiveAndExecute(Object invoker, Session session, MessageConsumer consumer)
throws JMSException {
if (this.transactionManager != null) {
// Execute receive within transaction.
TransactionStatus status = this.transactionManager.getTransaction(this.transactionDefinition);
boolean messageReceived = true;
try {
messageReceived = doReceiveAndExecute(invoker, session, consumer, status);
}
catch (JMSException ex) {
rollbackOnException(status, ex);
throw ex;
}
catch (RuntimeException ex) {
rollbackOnException(status, ex);
throw ex;
}
catch (Error err) {
rollbackOnException(status, err);
throw err;
}
this.transactionManager.commit(status);
return messageReceived;
}
else {
// Execute receive outside of transaction.
return doReceiveAndExecute(invoker, session, consumer, null);
}
}
/**
* Actually execute the listener for a message received from the given consumer,
* fetching all requires resources and invoking the listener.
* @param session the JMS Session to work on
* @param consumer the MessageConsumer to work on
* @param status the TransactionStatus (may be <code>null</code>)
* @return whether a message has been received
* @throws JMSException if thrown by JMS methods
* @see #doExecuteListener(javax.jms.Session, javax.jms.Message)
*/
protected boolean doReceiveAndExecute(
Object invoker, Session session, MessageConsumer consumer, TransactionStatus status)
throws JMSException {
Connection conToClose = null;
Session sessionToClose = null;
MessageConsumer consumerToClose = null;
try {
Session sessionToUse = session;
boolean transactional = false;
if (sessionToUse == null) {
sessionToUse = ConnectionFactoryUtils.doGetTransactionalSession(
getConnectionFactory(), this.transactionalResourceFactory, true);
transactional = (sessionToUse != null);
}
if (sessionToUse == null) {
Connection conToUse = null;
if (sharedConnectionEnabled()) {
conToUse = getSharedConnection();
}
else {
conToUse = createConnection();
conToClose = conToUse;
conToUse.start();
}
sessionToUse = createSession(conToUse);
sessionToClose = sessionToUse;
}
MessageConsumer consumerToUse = consumer;
if (consumerToUse == null) {
consumerToUse = createListenerConsumer(sessionToUse);
consumerToClose = consumerToUse;
}
Message message = receiveMessage(consumerToUse);
if (message != null) {
if (logger.isDebugEnabled()) {
logger.debug("Received message of type [" + message.getClass() + "] from consumer [" +
consumerToUse + "] of " + (transactional ? "transactional " : "") + "session [" +
sessionToUse + "]");
}
messageReceived(invoker, sessionToUse);
boolean exposeResource = (!transactional && isExposeListenerSession() &&
!TransactionSynchronizationManager.hasResource(getConnectionFactory()));
if (exposeResource) {
TransactionSynchronizationManager.bindResource(
getConnectionFactory(), new LocallyExposedJmsResourceHolder(sessionToUse));
}
try {
doExecuteListener(sessionToUse, message);
}
catch (Throwable ex) {
if (status != null) {
if (logger.isDebugEnabled()) {
logger.debug("Rolling back transaction because of listener exception thrown: " + ex);
}
status.setRollbackOnly();
}
handleListenerException(ex);
// Rethrow JMSException to indicate an infrastructure problem
// that may have to trigger recovery...
if (ex instanceof JMSException) {
throw (JMSException) ex;
}
}
finally {
if (exposeResource) {
TransactionSynchronizationManager.unbindResource(getConnectionFactory());
}
}
return true;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Consumer [" + consumerToUse + "] of " + (transactional ? "transactional " : "") +
"session [" + sessionToUse + "] did not receive a message");
}
noMessageReceived(invoker, sessionToUse);
return false;
}
}
finally {
JmsUtils.closeMessageConsumer(consumerToClose);
JmsUtils.closeSession(sessionToClose);
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true);
}
}
/**
* This implementation checks whether the Session is externally synchronized.
* In this case, the Session is not locally transacted, despite the listener
* container's "sessionTransacted" flag being set to "true".
* @see org.springframework.jms.connection.JmsResourceHolder
*/
protected boolean isSessionLocallyTransacted(Session session) {
if (!super.isSessionLocallyTransacted(session)) {
return false;
}
JmsResourceHolder resourceHolder =
(JmsResourceHolder) TransactionSynchronizationManager.getResource(getConnectionFactory());
return (resourceHolder == null || resourceHolder instanceof LocallyExposedJmsResourceHolder ||
!resourceHolder.containsSession(session));
}
/**
* Perform a rollback, handling rollback exceptions properly.
* @param status object representing the transaction
* @param ex the thrown listener exception or error
*/
private void rollbackOnException(TransactionStatus status, Throwable ex) {
logger.debug("Initiating transaction rollback on listener exception", ex);
try {
this.transactionManager.rollback(status);
}
catch (RuntimeException ex2) {
logger.error("Listener exception overridden by rollback exception", ex);
throw ex2;
}
catch (Error err) {
logger.error("Listener exception overridden by rollback error", ex);
throw err;
}
}
/**
* Receive a message from the given consumer.
* @param consumer the MessageConsumer to use
* @return the Message, or <code>null</code> if none
* @throws JMSException if thrown by JMS methods
*/
protected Message receiveMessage(MessageConsumer consumer) throws JMSException {
return (this.receiveTimeout < 0 ? consumer.receive() : consumer.receive(this.receiveTimeout));
}
/**
* Template method that gets called right when a new message has been received,
* before attempting to process it. Allows subclasses to react to the event
* of an actual incoming message, for example adapting their consumer count.
* @param invoker the invoker object (passed through)
* @param session the receiving JMS Session
*/
protected void messageReceived(Object invoker, Session session) {
}
/**
* Template method that gets called right <i>no</i> message has been received,
* before attempting to process it. Allows subclasses to react to the event
* of an actual incoming message, for example marking .
* @param invoker the invoker object (passed through)
* @param session the receiving JMS Session
*/
protected void noMessageReceived(Object invoker, Session session) {
}
//-------------------------------------------------------------------------
// JMS 1.1 factory methods, potentially overridden for JMS 1.0.2
//-------------------------------------------------------------------------
/**
* Fetch an appropriate Connection from the given JmsResourceHolder.
* <p>This implementation accepts any JMS 1.1 Connection.
* @param holder the JmsResourceHolder
* @return an appropriate Connection fetched from the holder,
* or <code>null</code> if none found
*/
protected Connection getConnection(JmsResourceHolder holder) {
return holder.getConnection();
}
/**
* Fetch an appropriate Session from the given JmsResourceHolder.
* <p>This implementation accepts any JMS 1.1 Session.
* @param holder the JmsResourceHolder
* @return an appropriate Session fetched from the holder,
* or <code>null</code> if none found
*/
protected Session getSession(JmsResourceHolder holder) {
return holder.getSession();
}
/**
* Create a JMS MessageConsumer for the given Session and Destination.
* <p>This implementation uses JMS 1.1 API.
* @param session the JMS Session to create a MessageConsumer for
* @param destination the JMS Destination to create a MessageConsumer for
* @return the new JMS MessageConsumer
* @throws javax.jms.JMSException if thrown by JMS API methods
*/
protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException {
// Only pass in the NoLocal flag in case of a Topic:
// Some JMS providers, such as WebSphere MQ 6.0, throw IllegalStateException
// in case of the NoLocal flag being specified for a Queue.
if (isPubSubDomain()) {
if (isSubscriptionDurable() && destination instanceof Topic) {
return session.createDurableSubscriber(
(Topic) destination, getDurableSubscriptionName(), getMessageSelector(), isPubSubNoLocal());
}
else {
return session.createConsumer(destination, getMessageSelector(), isPubSubNoLocal());
}
}
else {
return session.createConsumer(destination, getMessageSelector());
}
}
/**
* ResourceFactory implementation that delegates to this listener container's protected callback methods.
*/
private class MessageListenerContainerResourceFactory implements ConnectionFactoryUtils.ResourceFactory {
public Connection getConnection(JmsResourceHolder holder) {
return AbstractPollingMessageListenerContainer.this.getConnection(holder);
}
public Session getSession(JmsResourceHolder holder) {
return AbstractPollingMessageListenerContainer.this.getSession(holder);
}
public Connection createConnection() throws JMSException {
if (AbstractPollingMessageListenerContainer.this.sharedConnectionEnabled()) {
Connection sharedCon = AbstractPollingMessageListenerContainer.this.getSharedConnection();
return new SingleConnectionFactory(sharedCon).createConnection();
}
else {
return AbstractPollingMessageListenerContainer.this.createConnection();
}
}
public Session createSession(Connection con) throws JMSException {
return AbstractPollingMessageListenerContainer.this.createSession(con);
}
public boolean isSynchedLocalTransactionAllowed() {
return AbstractPollingMessageListenerContainer.this.isSessionTransacted();
}
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright 2002-2007 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 javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicSession;
import org.springframework.jms.connection.JmsResourceHolder;
/**
* A subclass of {@link DefaultMessageListenerContainer} for the JMS 1.0.2 specification,
* not relying on JMS 1.1 methods like SimpleMessageListenerContainer itself.
*
* <p>This class can be used for JMS 1.0.2 providers, offering the same facility as
* DefaultMessageListenerContainer does for JMS 1.1 providers.
*
* @author Juergen Hoeller
* @since 2.0
*/
public class DefaultMessageListenerContainer102 extends DefaultMessageListenerContainer {
/**
* This implementation overrides the superclass method to accept either
* a QueueConnection or a TopicConnection, depending on the domain.
*/
protected Connection getConnection(JmsResourceHolder holder) {
return holder.getConnection(isPubSubDomain() ? (Class) TopicConnection.class : QueueConnection.class);
}
/**
* This implementation overrides the superclass method to accept either
* a QueueSession or a TopicSession, depending on the domain.
*/
protected Session getSession(JmsResourceHolder holder) {
return holder.getSession(isPubSubDomain() ? (Class) TopicSession.class : QueueSession.class);
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected Connection createConnection() throws JMSException {
if (isPubSubDomain()) {
return ((TopicConnectionFactory) getConnectionFactory()).createTopicConnection();
}
else {
return ((QueueConnectionFactory) getConnectionFactory()).createQueueConnection();
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected Session createSession(Connection con) throws JMSException {
if (isPubSubDomain()) {
return ((TopicConnection) con).createTopicSession(isSessionTransacted(), getSessionAcknowledgeMode());
}
else {
return ((QueueConnection) con).createQueueSession(isSessionTransacted(), getSessionAcknowledgeMode());
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException {
if (isPubSubDomain()) {
if (isSubscriptionDurable()) {
return ((TopicSession) session).createDurableSubscriber(
(Topic) destination, getDurableSubscriptionName(), getMessageSelector(), isPubSubNoLocal());
}
else {
return ((TopicSession) session).createSubscriber(
(Topic) destination, getMessageSelector(), isPubSubNoLocal());
}
}
else {
return ((QueueSession) session).createReceiver((Queue) destination, getMessageSelector());
}
}
/**
* This implementation overrides the superclass method to avoid using
* JMS 1.1's Session <code>getAcknowledgeMode()</code> method.
* The best we can do here is to check the setting on the listener container.
*/
protected boolean isClientAcknowledge(Session session) throws JMSException {
return (getSessionAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE);
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2002-2008 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 javax.jms.Session;
import org.springframework.jms.connection.JmsResourceHolder;
/**
* JmsResourceHolder marker subclass that indicates local exposure,
* i.e. that does not indicate an externally managed transaction.
*
* @author Juergen Hoeller
* @since 2.5.2
*/
class LocallyExposedJmsResourceHolder extends JmsResourceHolder {
public LocallyExposedJmsResourceHolder(Session session) {
super(session);
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2002-2008 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 javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
/**
* Variant of the standard JMS {@link javax.jms.MessageListener} interface,
* offering not only the received Message but also the underlying
* JMS Session object. The latter can be used to send reply messages,
* without the need to access an external Connection/Session,
* i.e. without the need to access the underlying ConnectionFactory.
*
* <p>Supported by Spring's {@link DefaultMessageListenerContainer}
* and {@link SimpleMessageListenerContainer},
* as direct alternative to the standard JMS MessageListener interface.
* Typically <i>not</i> supported by JCA-based listener containers:
* For maximum compatibility, implement a standard JMS MessageListener instead.
*
* @author Juergen Hoeller
* @since 2.0
* @see AbstractMessageListenerContainer#setMessageListener
* @see DefaultMessageListenerContainer
* @see SimpleMessageListenerContainer
* @see org.springframework.jms.listener.endpoint.JmsMessageEndpointManager
* @see javax.jms.MessageListener
*/
public interface SessionAwareMessageListener {
/**
* Callback for processing a received JMS message.
* <p>Implementors are supposed to process the given Message,
* typically sending reply messages through the given Session.
* @param message the received JMS message (never <code>null</code>)
* @param session the underlying JMS Session (never <code>null</code>)
* @throws JMSException if thrown by JMS methods
*/
void onMessage(Message message, Session session) throws JMSException;
}

View File

@ -0,0 +1,346 @@
/*
* Copyright 2002-2008 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 java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.Topic;
import org.springframework.core.task.TaskExecutor;
import org.springframework.jms.support.JmsUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* Message listener container that uses the plain JMS client API's
* <code>MessageConsumer.setMessageListener()</code> method to
* create concurrent MessageConsumers for the specified listeners.
*
* <p><b>NOTE:</b> This class requires a JMS 1.1+ provider, because it builds on
* the domain-independent API. <b>Use the {@link SimpleMessageListenerContainer102}
* subclass for a JMS 1.0.2 provider, e.g. when running on a J2EE 1.3 server.</b>
*
* <p>This is the simplest form of a message listener container.
* It creates a fixed number of JMS Sessions to invoke the listener,
* not allowing for dynamic adaptation to runtime demands. Its main
* advantage is its low level of complexity and the minimum requirements
* on the JMS provider: Not even the ServerSessionPool facility is required.
*
* <p>See the {@link AbstractMessageListenerContainer} javadoc for details
* on acknowledge modes and transaction options.
*
* <p>For a different style of MessageListener handling, through looped
* <code>MessageConsumer.receive()</code> calls that also allow for
* transactional reception of messages (registering them with XA transactions),
* see {@link DefaultMessageListenerContainer}.
*
* @author Juergen Hoeller
* @since 2.0
* @see javax.jms.MessageConsumer#setMessageListener
* @see DefaultMessageListenerContainer
* @see org.springframework.jms.listener.endpoint.JmsMessageEndpointManager
*/
public class SimpleMessageListenerContainer extends AbstractMessageListenerContainer implements ExceptionListener {
private boolean pubSubNoLocal = false;
private int concurrentConsumers = 1;
private TaskExecutor taskExecutor;
private Set sessions;
private Set consumers;
private final Object consumersMonitor = new Object();
/**
* Set whether to inhibit the delivery of messages published by its own connection.
* Default is "false".
* @see javax.jms.TopicSession#createSubscriber(javax.jms.Topic, String, boolean)
*/
public void setPubSubNoLocal(boolean pubSubNoLocal) {
this.pubSubNoLocal = pubSubNoLocal;
}
/**
* Return whether to inhibit the delivery of messages published by its own connection.
*/
protected boolean isPubSubNoLocal() {
return this.pubSubNoLocal;
}
/**
* Specify the number of concurrent consumers to create. Default is 1.
* <p>Raising the number of concurrent consumers is recommendable in order
* to scale the consumption of messages coming in from a queue. However,
* note that any ordering guarantees are lost once multiple consumers are
* registered. In general, stick with 1 consumer for low-volume queues.
* <p><b>Do not raise the number of concurrent consumers for a topic.</b>
* This would lead to concurrent consumption of the same message,
* which is hardly ever desirable.
*/
public void setConcurrentConsumers(int concurrentConsumers) {
Assert.isTrue(concurrentConsumers > 0, "'concurrentConsumers' value must be at least 1 (one)");
this.concurrentConsumers = concurrentConsumers;
}
/**
* Set the Spring TaskExecutor to use for executing the listener once
* a message has been received by the provider.
* <p>Default is none, that is, to run in the JMS provider's own receive thread,
* blocking the provider's receive endpoint while executing the listener.
* <p>Specify a TaskExecutor for executing the listener in a different thread,
* rather than blocking the JMS provider, usually integrating with an existing
* thread pool. This allows to keep the number of concurrent consumers low (1)
* while still processing messages concurrently (decoupled from receiving!).
* <p><b>NOTE: Specifying a TaskExecutor for listener execution affects
* acknowledgement semantics.</b> Messages will then always get acknowledged
* before listener execution, with the underlying Session immediately reused
* for receiving the next message. Using this in combination with a transacted
* session or with client acknowledgement will lead to unspecified results!
* <p><b>NOTE: Concurrent listener execution via a TaskExecutor will lead
* to concurrent processing of messages that have been received by the same
* underlying Session.</b> As a consequence, it is not recommended to use
* this setting with a {@link SessionAwareMessageListener}, at least not
* if the latter performs actual work on the given Session. A standard
* {@link javax.jms.MessageListener} will work fine, in general.
* @see #setConcurrentConsumers
* @see org.springframework.core.task.SimpleAsyncTaskExecutor
* @see org.springframework.scheduling.commonj.WorkManagerTaskExecutor
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
protected void validateConfiguration() {
super.validateConfiguration();
if (isSubscriptionDurable() && this.concurrentConsumers != 1) {
throw new IllegalArgumentException("Only 1 concurrent consumer supported for durable subscription");
}
}
//-------------------------------------------------------------------------
// Implementation of AbstractMessageListenerContainer's template methods
//-------------------------------------------------------------------------
/**
* Always use a shared JMS Connection.
*/
protected final boolean sharedConnectionEnabled() {
return true;
}
/**
* Creates the specified number of concurrent consumers,
* in the form of a JMS Session plus associated MessageConsumer.
* @see #createListenerConsumer
*/
protected void doInitialize() throws JMSException {
establishSharedConnection();
initializeConsumers();
}
/**
* Re-initializes this container's JMS message consumers,
* if not initialized already.
*/
protected void doStart() throws JMSException {
super.doStart();
initializeConsumers();
}
/**
* Registers this listener container as JMS ExceptionListener on the shared connection.
*/
protected void prepareSharedConnection(Connection connection) throws JMSException {
super.prepareSharedConnection(connection);
connection.setExceptionListener(this);
}
/**
* JMS ExceptionListener implementation, invoked by the JMS provider in
* case of connection failures. Re-initializes this listener container's
* shared connection and its sessions and consumers.
* @param ex the reported connection exception
*/
public void onException(JMSException ex) {
// First invoke the user-specific ExceptionListener, if any.
invokeExceptionListener(ex);
// Now try to recover the shared Connection and all consumers...
if (logger.isInfoEnabled()) {
logger.info("Trying to recover from JMS Connection exception: " + ex);
}
try {
synchronized (this.consumersMonitor) {
this.sessions = null;
this.consumers = null;
}
refreshSharedConnection();
initializeConsumers();
logger.info("Successfully refreshed JMS Connection");
}
catch (JMSException recoverEx) {
logger.debug("Failed to recover JMS Connection", recoverEx);
logger.error("Encountered non-recoverable JMSException", ex);
}
}
/**
* Initialize the JMS Sessions and MessageConsumers for this container.
* @throws JMSException in case of setup failure
*/
protected void initializeConsumers() throws JMSException {
// Register Sessions and MessageConsumers.
synchronized (this.consumersMonitor) {
if (this.consumers == null) {
this.sessions = new HashSet(this.concurrentConsumers);
this.consumers = new HashSet(this.concurrentConsumers);
Connection con = getSharedConnection();
for (int i = 0; i < this.concurrentConsumers; i++) {
Session session = createSession(con);
MessageConsumer consumer = createListenerConsumer(session);
this.sessions.add(session);
this.consumers.add(consumer);
}
}
}
}
/**
* Create a MessageConsumer for the given JMS Session,
* registering a MessageListener for the specified listener.
* @param session the JMS Session to work on
* @return the MessageConsumer
* @throws JMSException if thrown by JMS methods
* @see #executeListener
*/
protected MessageConsumer createListenerConsumer(final Session session) throws JMSException {
Destination destination = getDestination();
if (destination == null) {
destination = resolveDestinationName(session, getDestinationName());
}
MessageConsumer consumer = createConsumer(session, destination);
if (this.taskExecutor != null) {
consumer.setMessageListener(new MessageListener() {
public void onMessage(final Message message) {
taskExecutor.execute(new Runnable() {
public void run() {
processMessage(message, session);
}
});
}
});
}
else {
consumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
processMessage(message, session);
}
});
}
return consumer;
}
/**
* Process a message received from the provider.
* <p>Executes the listener, exposing the current JMS Session as
* thread-bound resource (if "exposeListenerSession" is "true").
* @param message the received JMS Message
* @param session the JMS Session to operate on
* @see #executeListener
* @see #setExposeListenerSession
*/
protected void processMessage(Message message, Session session) {
boolean exposeResource = isExposeListenerSession();
if (exposeResource) {
TransactionSynchronizationManager.bindResource(
getConnectionFactory(), new LocallyExposedJmsResourceHolder(session));
}
try {
executeListener(session, message);
}
finally {
if (exposeResource) {
TransactionSynchronizationManager.unbindResource(getConnectionFactory());
}
}
}
/**
* Destroy the registered JMS Sessions and associated MessageConsumers.
*/
protected void doShutdown() throws JMSException {
logger.debug("Closing JMS MessageConsumers");
for (Iterator it = this.consumers.iterator(); it.hasNext();) {
MessageConsumer consumer = (MessageConsumer) it.next();
JmsUtils.closeMessageConsumer(consumer);
}
logger.debug("Closing JMS Sessions");
for (Iterator it = this.sessions.iterator(); it.hasNext();) {
Session session = (Session) it.next();
JmsUtils.closeSession(session);
}
}
//-------------------------------------------------------------------------
// JMS 1.1 factory methods, potentially overridden for JMS 1.0.2
//-------------------------------------------------------------------------
/**
* Create a JMS MessageConsumer for the given Session and Destination.
* <p>This implementation uses JMS 1.1 API.
* @param session the JMS Session to create a MessageConsumer for
* @param destination the JMS Destination to create a MessageConsumer for
* @return the new JMS MessageConsumer
* @throws JMSException if thrown by JMS API methods
*/
protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException {
// Only pass in the NoLocal flag in case of a Topic:
// Some JMS providers, such as WebSphere MQ 6.0, throw IllegalStateException
// in case of the NoLocal flag being specified for a Queue.
if (isPubSubDomain()) {
if (isSubscriptionDurable() && destination instanceof Topic) {
return session.createDurableSubscriber(
(Topic) destination, getDurableSubscriptionName(), getMessageSelector(), isPubSubNoLocal());
}
else {
return session.createConsumer(destination, getMessageSelector(), isPubSubNoLocal());
}
}
else {
return session.createConsumer(destination, getMessageSelector());
}
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright 2002-2007 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 javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicSession;
/**
* A subclass of {@link SimpleMessageListenerContainer} for the JMS 1.0.2 specification,
* not relying on JMS 1.1 methods like SimpleMessageListenerContainer itself.
*
* <p>This class can be used for JMS 1.0.2 providers, offering the same facility as
* SimpleMessageListenerContainer does for JMS 1.1 providers.
*
* @author Juergen Hoeller
* @since 2.0
*/
public class SimpleMessageListenerContainer102 extends SimpleMessageListenerContainer {
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected Connection createConnection() throws JMSException {
if (isPubSubDomain()) {
return ((TopicConnectionFactory) getConnectionFactory()).createTopicConnection();
}
else {
return ((QueueConnectionFactory) getConnectionFactory()).createQueueConnection();
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected Session createSession(Connection con) throws JMSException {
if (isPubSubDomain()) {
return ((TopicConnection) con).createTopicSession(isSessionTransacted(), getSessionAcknowledgeMode());
}
else {
return ((QueueConnection) con).createQueueSession(isSessionTransacted(), getSessionAcknowledgeMode());
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException {
if (isPubSubDomain()) {
if (isSubscriptionDurable()) {
return ((TopicSession) session).createDurableSubscriber(
(Topic) destination, getDurableSubscriptionName(), getMessageSelector(), isPubSubNoLocal());
}
else {
return ((TopicSession) session).createSubscriber(
(Topic) destination, getMessageSelector(), isPubSubNoLocal());
}
}
else {
return ((QueueSession) session).createReceiver((Queue) destination, getMessageSelector());
}
}
/**
* This implementation overrides the superclass method to avoid using
* JMS 1.1's Session <code>getAcknowledgeMode()</code> method.
* The best we can do here is to check the setting on the listener container.
*/
protected boolean isClientAcknowledge(Session session) throws JMSException {
return (getSessionAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE);
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2002-2008 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;
/**
* Interface to be implemented by message listener objects that suggest a specific
* name for a durable subscription that they might be registered with. Otherwise
* the listener class name will be used as a default subscription name.
*
* <p>Applies to {@link javax.jms.MessageListener} objects as well as to
* {@link SessionAwareMessageListener} objects and plain listener methods
* (as supported by {@link org.springframework.jms.listener.adapter.MessageListenerAdapter}.
*
* @author Juergen Hoeller
* @since 2.5.6
*/
public interface SubscriptionNameProvider {
/**
* Determine the subscription name for this message listener object.
*/
String getSubscriptionName();
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2002-2006 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 org.springframework.jms.JmsException;
/**
* Exception to be thrown when the execution of a listener method failed.
*
* @author Juergen Hoeller
* @since 2.0
* @see MessageListenerAdapter
*/
public class ListenerExecutionFailedException extends JmsException {
/**
* Constructor for ListenerExecutionFailedException.
* @param msg the detail message
* @param cause the exception thrown by the listener method
*/
public ListenerExecutionFailedException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,656 @@
/*
* Copyright 2002-2008 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.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;
/**
* Message listener adapter that delegates the handling of messages to target
* listener methods via reflection, with flexible message type conversion.
* Allows listener methods to operate on message content types, completely
* independent from the JMS API.
*
* <p><b>NOTE:</b> This class requires a JMS 1.1+ provider, because it builds
* on the domain-independent API. <b>Use the {@link MessageListenerAdapter102
* MessageListenerAdapter102} subclass for JMS 1.0.2 providers.</b>
*
* <p>By default, the content of incoming JMS messages gets extracted before
* being passed into the target listener method, to let the target method
* operate on message content types such as String or byte array instead of
* the raw {@link Message}. Message type conversion is delegated to a Spring
* JMS {@link MessageConverter}. By default, a {@link SimpleMessageConverter}
* {@link org.springframework.jms.support.converter.SimpleMessageConverter102 (102)}
* will be used. (If you do not want such automatic message conversion taking
* place, then be sure to set the {@link #setMessageConverter MessageConverter}
* to <code>null</code>.)
*
* <p>If a target listener method returns a non-null object (typically of a
* message content type such as <code>String</code> or byte array), it will get
* wrapped in a JMS <code>Message</code> and sent to the response destination
* (either the JMS "reply-to" destination or a
* {@link #setDefaultResponseDestination(javax.jms.Destination) specified default
* destination}).
*
* <p><b>Note:</b> The sending of response messages is only available when
* using the {@link SessionAwareMessageListener} entry point (typically through a
* Spring message listener container). Usage as standard JMS {@link MessageListener}
* does <i>not</i> support the generation of response messages.
*
* <p>Find below some examples of method signatures compliant with this
* adapter class. This first example handles all <code>Message</code> types
* and gets passed the contents of each <code>Message</code> type as an
* argument. No <code>Message</code> will be sent back as all of these
* methods return <code>void</code>.
*
* <pre class="code">public interface MessageContentsDelegate {
* void handleMessage(String text);
* void handleMessage(Map map);
* void handleMessage(byte[] bytes);
* void handleMessage(Serializable obj);
* }</pre>
*
* This next example handles all <code>Message</code> types and gets
* passed the actual (raw) <code>Message</code> as an argument. Again, no
* <code>Message</code> will be sent back as all of these methods return
* <code>void</code>.
*
* <pre class="code">public interface RawMessageDelegate {
* void handleMessage(TextMessage message);
* void handleMessage(MapMessage message);
* void handleMessage(BytesMessage message);
* void handleMessage(ObjectMessage message);
* }</pre>
*
* This next example illustrates a <code>Message</code> delegate
* that just consumes the <code>String</code> contents of
* {@link javax.jms.TextMessage TextMessages}. Notice also how the
* name of the <code>Message</code> handling method is different from the
* {@link #ORIGINAL_DEFAULT_LISTENER_METHOD original} (this will have to
* be configured in the attandant bean definition). Again, no <code>Message</code>
* will be sent back as the method returns <code>void</code>.
*
* <pre class="code">public interface TextMessageContentDelegate {
* void onMessage(String text);
* }</pre>
*
* This final example illustrates a <code>Message</code> delegate
* that just consumes the <code>String</code> contents of
* {@link javax.jms.TextMessage TextMessages}. Notice how the return type
* of this method is <code>String</code>: This will result in the configured
* {@link MessageListenerAdapter} sending a {@link javax.jms.TextMessage} in response.
*
* <pre class="code">public interface ResponsiveTextMessageContentDelegate {
* String handleMessage(String text);
* }</pre>
*
* For further examples and discussion please do refer to the Spring
* reference documentation which describes this class (and it's attendant
* XML configuration) in detail.
*
* @author Juergen Hoeller
* @since 2.0
* @see #setDelegate
* @see #setDefaultListenerMethod
* @see #setDefaultResponseDestination
* @see #setMessageConverter
* @see org.springframework.jms.support.converter.SimpleMessageConverter
* @see org.springframework.jms.listener.SessionAwareMessageListener
* @see org.springframework.jms.listener.AbstractMessageListenerContainer#setMessageListener
*/
public class MessageListenerAdapter implements MessageListener, SessionAwareMessageListener, SubscriptionNameProvider {
/**
* Out-of-the-box value for the default listener method: "handleMessage".
*/
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.
*/
public MessageListenerAdapter() {
initDefaultStrategies();
this.delegate = this;
}
/**
* Create a new {@link MessageListenerAdapter} for the given delegate.
* @param delegate the delegate object
*/
public MessageListenerAdapter(Object delegate) {
initDefaultStrategies();
setDelegate(delegate);
}
/**
* Set a target object to delegate message listening to.
* Specified listener methods have to be present on this target object.
* <p>If no explicit delegate object has been specified, listener
* methods are expected to present on this adapter instance, that is,
* on a custom subclass of this adapter, defining listener methods.
*/
public void setDelegate(Object delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
}
/**
* Return the target object to delegate message listening to.
*/
protected Object getDelegate() {
return this.delegate;
}
/**
* Specify the name of the default listener method to delegate to,
* for the case where no specific listener method has been determined.
* Out-of-the-box value is {@link #ORIGINAL_DEFAULT_LISTENER_METHOD "handleMessage"}.
* @see #getListenerMethodName
*/
public void setDefaultListenerMethod(String defaultListenerMethod) {
this.defaultListenerMethod = defaultListenerMethod;
}
/**
* Return the name of the default listener method to delegate to.
*/
protected String getDefaultListenerMethod() {
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)
*/
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
* conversion of the message argument. If the target method returns a
* non-null object, wrap in a JMS message and send it back.
* @param message the incoming JMS message
* @param session the JMS session to operate on
* @throws JMSException if thrown by JMS API methods
*/
public void onMessage(Message message, Session session) throws JMSException {
// Check whether the delegate is a MessageListener impl itself.
// In that case, the adapter will simply act as a pass-through.
Object delegate = getDelegate();
if (delegate != this) {
if (delegate instanceof SessionAwareMessageListener) {
if (session != null) {
((SessionAwareMessageListener) delegate).onMessage(message, session);
return;
}
else if (!(delegate instanceof MessageListener)) {
throw new javax.jms.IllegalStateException("MessageListenerAdapter cannot handle a " +
"SessionAwareMessageListener delegate if it hasn't been invoked with a Session itself");
}
}
if (delegate instanceof MessageListener) {
((MessageListener) delegate).onMessage(message);
return;
}
}
// Regular case: find a handler method reflectively.
Object convertedMessage = extractMessage(message);
String methodName = getListenerMethodName(message, convertedMessage);
if (methodName == null) {
throw new javax.jms.IllegalStateException("No default listener method specified: " +
"Either specify a non-null value for the 'defaultListenerMethod' property or " +
"override the 'getListenerMethodName' method.");
}
// Invoke the handler method with appropriate arguments.
Object[] listenerArguments = buildListenerArguments(convertedMessage);
Object result = invokeListenerMethod(methodName, listenerArguments);
if (result != null) {
handleResult(result, message, session);
}
else {
logger.trace("No result object given - no result to handle");
}
}
public String getSubscriptionName() {
if (this.delegate instanceof SubscriptionNameProvider) {
return ((SubscriptionNameProvider) this.delegate).getSubscriptionName();
}
else {
return this.delegate.getClass().getName();
}
}
/**
* 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</code>
* @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.
* <p>The default implementation simply returns the configured
* default listener method, if any.
* @param originalMessage the JMS request message
* @param extractedMessage the converted JMS request message,
* to be passed into the listener method as argument
* @return the name of the listener method (never <code>null</code>)
* @throws JMSException if thrown by JMS API methods
* @see #setDefaultListenerMethod
*/
protected String getListenerMethodName(Message originalMessage, Object extractedMessage) throws JMSException {
return getDefaultListenerMethod();
}
/**
* Build an array of arguments to be passed into the target listener method.
* Allows for multiple method arguments to be built from a single message object.
* <p>The default implementation builds an array with the given message object
* as sole element. This means that the extracted message will always be passed
* into a <i>single</i> method argument, even if it is an array, with the target
* method having a corresponding single argument of the array's type declared.
* <p>This can be overridden to treat special message content such as arrays
* differently, for example passing in each element of the message array
* as distinct method argument.
* @param extractedMessage the content of the message
* @return the array of arguments to be passed into the
* listener method (each element of the array corresponding
* to a distinct method argument)
*/
protected Object[] buildListenerArguments(Object extractedMessage) {
return new Object[] {extractedMessage};
}
/**
* Invoke the specified listener method.
* @param methodName the name of the listener method
* @param arguments the message arguments to be passed in
* @return the result returned from the listener method
* @throws JMSException if thrown by JMS API methods
* @see #getListenerMethodName
* @see #buildListenerArguments
*/
protected Object invokeListenerMethod(String methodName, Object[] arguments) throws JMSException {
try {
MethodInvoker methodInvoker = new MethodInvoker();
methodInvoker.setTargetObject(getDelegate());
methodInvoker.setTargetMethod(methodName);
methodInvoker.setArguments(arguments);
methodInvoker.prepare();
return methodInvoker.invoke();
}
catch (InvocationTargetException ex) {
Throwable targetEx = ex.getTargetException();
if (targetEx instanceof JMSException) {
throw (JMSException) targetEx;
}
else {
throw new ListenerExecutionFailedException(
"Listener method '" + methodName + "' threw exception", targetEx);
}
}
catch (Throwable ex) {
throw new ListenerExecutionFailedException("Failed to invoke target method '" + methodName +
"' with arguments " + ObjectUtils.nullSafeToString(arguments), ex);
}
}
/**
* 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</code>)
* @param request the original request message
* @param session the JMS Session to operate on (may be <code>null</code>)
* @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</code> (never <code>null</code>)
* @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</code>
* it is returned; if it is <code>null</code>, then the configured
* {@link #resolveDefaultResponseDestination default response destination}
* is returned; if this too is <code>null</code>, 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</code>)
* @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,98 @@
/*
* Copyright 2002-2007 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.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import org.springframework.jms.support.JmsUtils;
import org.springframework.jms.support.converter.SimpleMessageConverter102;
/**
* A {@link MessageListenerAdapter} subclass for the JMS 1.0.2 specification,
* not relying on JMS 1.1 methods like MessageListenerAdapter itself.
*
* <p>This class can be used for JMS 1.0.2 providers, offering the same facility
* as MessageListenerAdapter does for JMS 1.1 providers.
*
* @author Juergen Hoeller
* @author Rick Evans
* @since 2.0
*/
public class MessageListenerAdapter102 extends MessageListenerAdapter {
/**
* Create a new instance of the {@link MessageListenerAdapter102} class
* with the default settings.
*/
public MessageListenerAdapter102() {
}
/**
* Create a new instance of the {@link MessageListenerAdapter102} class
* for the given delegate.
* @param delegate the target object to delegate message listening to
*/
public MessageListenerAdapter102(Object delegate) {
super(delegate);
}
/**
* Initialize the default implementations for the adapter's strategies:
* SimpleMessageConverter102.
* @see #setMessageConverter
* @see org.springframework.jms.support.converter.SimpleMessageConverter102
*/
protected void initDefaultStrategies() {
setMessageConverter(new SimpleMessageConverter102());
}
/**
* Overrides the superclass method to use the JMS 1.0.2 API to send a response.
* <p>Uses the JMS pub-sub API if the given destination is a topic,
* else uses the JMS queue API.
*/
protected void sendResponse(Session session, Destination destination, Message response) throws JMSException {
MessageProducer producer = null;
try {
if (destination instanceof Topic) {
producer = ((TopicSession) session).createPublisher((Topic) destination);
postProcessProducer(producer, response);
((TopicPublisher) producer).publish(response);
}
else {
producer = ((QueueSession) session).createSender((Queue) destination);
postProcessProducer(producer, response);
((QueueSender) producer).send(response);
}
}
finally {
JmsUtils.closeMessageProducer(producer);
}
}
}

View File

@ -0,0 +1,9 @@
<html>
<body>
Message listener adapter mechanism that delegates to target listener
methods, converting messages to appropriate message content types
(such as String or byte array) that get passed into listener methods.
</body>
</html>

View File

@ -0,0 +1,181 @@
/*
* Copyright 2002-2008 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.endpoint;
import javax.jms.Session;
import javax.resource.spi.ResourceAdapter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapper;
/**
* Default implementation of the {@link JmsActivationSpecFactory} interface.
* Supports the standard JMS properties as defined by the JMS 1.5 specification,
* as well as Spring's extended "maxConcurrency" and "prefetchSize" settings
* through autodetection of well-known vendor-specific provider properties.
*
* <p>An ActivationSpec factory is effectively dependent on the concrete
* JMS provider, e.g. on ActiveMQ. This default implementation simply
* guesses the ActivationSpec class name from the provider's class name
* ("ActiveMQResourceAdapter" -> "ActiveMQActivationSpec" in the same package,
* or "ActivationSpecImpl" in the same package as the ResourceAdapter class),
* and populates the ActivationSpec properties as suggested by the
* JCA 1.5 specification (Appendix B). Specify the 'activationSpecClass'
* property explicitly if these default naming rules do not apply.
*
* <p>Note: ActiveMQ, JORAM and WebSphere are supported in terms of extended
* settings (through the detection of their bean property naming conventions).
* The default ActivationSpec class detection rules may apply to other
* JMS providers as well.
*
* <p>Thanks to Agim Emruli and Laurie Chan for pointing out WebSphere MQ
* settings and contributing corresponding tests!
*
* @author Juergen Hoeller
* @since 2.5
* @see #setActivationSpecClass
*/
public class DefaultJmsActivationSpecFactory extends StandardJmsActivationSpecFactory {
private static final String RESOURCE_ADAPTER_SUFFIX = "ResourceAdapter";
private static final String RESOURCE_ADAPTER_IMPL_SUFFIX = "ResourceAdapterImpl";
private static final String ACTIVATION_SPEC_SUFFIX = "ActivationSpec";
private static final String ACTIVATION_SPEC_IMPL_SUFFIX = "ActivationSpecImpl";
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
/**
* This implementation guesses the ActivationSpec class name from the
* provider's class name: e.g. "ActiveMQResourceAdapter" ->
* "ActiveMQActivationSpec" in the same package, or a class named
* "ActivationSpecImpl" in the same package as the ResourceAdapter class.
*/
protected Class determineActivationSpecClass(ResourceAdapter adapter) {
String adapterClassName = adapter.getClass().getName();
if (adapterClassName.endsWith(RESOURCE_ADAPTER_SUFFIX)) {
// e.g. ActiveMQ
String providerName =
adapterClassName.substring(0, adapterClassName.length() - RESOURCE_ADAPTER_SUFFIX.length());
String specClassName = providerName + ACTIVATION_SPEC_SUFFIX;
try {
return adapter.getClass().getClassLoader().loadClass(specClassName);
}
catch (ClassNotFoundException ex) {
logger.debug("No default <Provider>ActivationSpec class found: " + specClassName);
}
}
else if (adapterClassName.endsWith(RESOURCE_ADAPTER_IMPL_SUFFIX)){
//e.g. WebSphere
String providerName =
adapterClassName.substring(0, adapterClassName.length() - RESOURCE_ADAPTER_IMPL_SUFFIX.length());
String specClassName = providerName + ACTIVATION_SPEC_IMPL_SUFFIX;
try {
return adapter.getClass().getClassLoader().loadClass(specClassName);
}
catch (ClassNotFoundException ex) {
logger.debug("No default <Provider>ActivationSpecImpl class found: " + specClassName);
}
}
// e.g. JORAM
String providerPackage = adapterClassName.substring(0, adapterClassName.lastIndexOf('.') + 1);
String specClassName = providerPackage + ACTIVATION_SPEC_IMPL_SUFFIX;
try {
return adapter.getClass().getClassLoader().loadClass(specClassName);
}
catch (ClassNotFoundException ex) {
logger.debug("No default ActivationSpecImpl class found in provider package: " + specClassName);
}
// ActivationSpecImpl class in "inbound" subpackage (WebSphere MQ 6.0.2.1)
specClassName = providerPackage + "inbound." + ACTIVATION_SPEC_IMPL_SUFFIX;
try {
return adapter.getClass().getClassLoader().loadClass(specClassName);
}
catch (ClassNotFoundException ex) {
logger.debug("No default ActivationSpecImpl class found in inbound subpackage: " + specClassName);
}
throw new IllegalStateException("No ActivationSpec class defined - " +
"specify the 'activationSpecClass' property or override the 'determineActivationSpecClass' method");
}
/**
* This implementation supports Spring's extended "maxConcurrency"
* and "prefetchSize" settings through detecting corresponding
* ActivationSpec properties: "maxSessions"/"maxNumberOfWorks" and
* "maxMessagesPerSessions"/"maxMessages", respectively
* (following ActiveMQ's and JORAM's naming conventions).
*/
protected void populateActivationSpecProperties(BeanWrapper bw, JmsActivationSpecConfig config) {
super.populateActivationSpecProperties(bw, config);
if (config.getMaxConcurrency() > 0) {
if (bw.isWritableProperty("maxSessions")) {
// ActiveMQ
bw.setPropertyValue("maxSessions", Integer.toString(config.getMaxConcurrency()));
}
else if (bw.isWritableProperty("maxNumberOfWorks")) {
// JORAM
bw.setPropertyValue("maxNumberOfWorks", Integer.toString(config.getMaxConcurrency()));
}
else if (bw.isWritableProperty("maxConcurrency")){
// WebSphere
bw.setPropertyValue("maxConcurrency", Integer.toString(config.getMaxConcurrency()));
}
}
if (config.getPrefetchSize() > 0) {
if (bw.isWritableProperty("maxMessagesPerSessions")) {
// ActiveMQ
bw.setPropertyValue("maxMessagesPerSessions", Integer.toString(config.getPrefetchSize()));
}
else if (bw.isWritableProperty("maxMessages")) {
// JORAM
bw.setPropertyValue("maxMessages", Integer.toString(config.getPrefetchSize()));
}
else if(bw.isWritableProperty("maxBatchSize")){
// WebSphere
bw.setPropertyValue("maxBatchSize", Integer.toString(config.getPrefetchSize()));
}
}
}
/**
* This implementation maps <code>SESSION_TRANSACTED</code> onto an
* ActivationSpec property named "useRAManagedTransaction", if available
* (following ActiveMQ's naming conventions).
*/
protected void applyAcknowledgeMode(BeanWrapper bw, int ackMode) {
if (ackMode == Session.SESSION_TRANSACTED && bw.isWritableProperty("useRAManagedTransaction")) {
// ActiveMQ
bw.setPropertyValue("useRAManagedTransaction", "true");
}
else {
super.applyAcknowledgeMode(bw, ackMode);
}
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright 2002-2007 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.endpoint;
import javax.jms.Session;
/**
* Common configuration object for activating a JMS message endpoint.
* Gets converted into a provider-specific JCA 1.5 ActivationSpec
* object for activating the endpoint.
*
* <p>Typically used in combination with {@link JmsMessageEndpointManager},
* but not tied to it.
*
* @author Juergen Hoeller
* @since 2.5
* @see JmsActivationSpecFactory
* @see JmsMessageEndpointManager#setActivationSpecConfig
* @see javax.resource.spi.ResourceAdapter#endpointActivation
*/
public class JmsActivationSpecConfig {
private String destinationName;
private boolean pubSubDomain = false;
private boolean subscriptionDurable = false;
private String durableSubscriptionName;
private String clientId;
private String messageSelector;
private int acknowledgeMode = Session.AUTO_ACKNOWLEDGE;
private int maxConcurrency = -1;
private int prefetchSize = -1;
public void setDestinationName(String destinationName) {
this.destinationName = destinationName;
}
public String getDestinationName() {
return this.destinationName;
}
public void setPubSubDomain(boolean pubSubDomain) {
this.pubSubDomain = pubSubDomain;
}
public boolean isPubSubDomain() {
return this.pubSubDomain;
}
public void setSubscriptionDurable(boolean subscriptionDurable) {
this.subscriptionDurable = subscriptionDurable;
}
public boolean isSubscriptionDurable() {
return this.subscriptionDurable;
}
public void setDurableSubscriptionName(String durableSubscriptionName) {
this.durableSubscriptionName = durableSubscriptionName;
}
public String getDurableSubscriptionName() {
return this.durableSubscriptionName;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientId() {
return this.clientId;
}
public void setMessageSelector(String messageSelector) {
this.messageSelector = messageSelector;
}
public String getMessageSelector() {
return this.messageSelector;
}
public void setAcknowledgeMode(int acknowledgeMode) {
this.acknowledgeMode = acknowledgeMode;
}
public int getAcknowledgeMode() {
return this.acknowledgeMode;
}
public void setMaxConcurrency(int maxConcurrency) {
this.maxConcurrency = maxConcurrency;
}
public int getMaxConcurrency() {
return this.maxConcurrency;
}
public void setPrefetchSize(int prefetchSize) {
this.prefetchSize = prefetchSize;
}
public int getPrefetchSize() {
return this.prefetchSize;
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2002-2007 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.endpoint;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.ResourceAdapter;
/**
* Strategy interface for creating JCA 1.5 ActivationSpec objects
* based on a configured {@link JmsActivationSpecConfig} object.
*
* <p>JCA 1.5 ActivationSpec objects are typically JavaBeans, but
* unfortunately provider-specific. This strategy interface allows
* for plugging in any JCA-based JMS provider, creating corresponding
* ActivationSpec objects based on common JMS configuration settings.
*
* @author Juergen Hoeller
* @since 2.5
* @see JmsActivationSpecConfig
* @see JmsMessageEndpointManager#setActivationSpecFactory
* @see javax.resource.spi.ResourceAdapter#endpointActivation
*/
public interface JmsActivationSpecFactory {
/**
* Create a JCA 1.5 ActivationSpec object based on the given
* {@link JmsActivationSpecConfig} object.
* @param adapter the ResourceAdapter to create an ActivationSpec object for
* @param config the configured object holding common JMS settings
* @return the provider-specific JCA ActivationSpec object,
* representing the same settings
*/
ActivationSpec createActivationSpec(ResourceAdapter adapter, JmsActivationSpecConfig config);
}

View File

@ -0,0 +1,126 @@
/*
* Copyright 2002-2007 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.endpoint;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.resource.ResourceException;
import javax.resource.spi.UnavailableException;
import org.springframework.jca.endpoint.AbstractMessageEndpointFactory;
/**
* JMS-specific implementation of the JCA 1.5
* {@link javax.resource.spi.endpoint.MessageEndpointFactory} interface,
* providing transaction management capabilities for a JMS listener object
* (e.g. a {@link javax.jms.MessageListener} object).
*
* <p>Uses a static endpoint implementation, simply wrapping the
* specified message listener object and exposing all of its implemented
* interfaces on the endpoint instance.
*
* <p>Typically used with Spring's {@link JmsMessageEndpointManager},
* but not tied to it. As a consequence, this endpoint factory could
* also be used with programmatic endpoint management on a native
* {@link javax.resource.spi.ResourceAdapter} instance.
*
* @author Juergen Hoeller
* @since 2.5
* @see #setMessageListener
* @see #setTransactionManager
* @see JmsMessageEndpointManager
*/
public class JmsMessageEndpointFactory extends AbstractMessageEndpointFactory {
private MessageListener messageListener;
/**
* Set the JMS MessageListener for this endpoint.
*/
public void setMessageListener(MessageListener messageListener) {
this.messageListener = messageListener;
}
/**
* Creates a concrete JMS message endpoint, internal to this factory.
*/
protected AbstractMessageEndpoint createEndpointInternal() throws UnavailableException {
return new JmsMessageEndpoint();
}
/**
* Private inner class that implements the concrete JMS message endpoint.
*/
private class JmsMessageEndpoint extends AbstractMessageEndpoint implements MessageListener {
public void onMessage(Message message) {
boolean applyDeliveryCalls = !hasBeforeDeliveryBeenCalled();
if (applyDeliveryCalls) {
try {
beforeDelivery(null);
}
catch (ResourceException ex) {
throw new JmsResourceException(ex);
}
}
try {
messageListener.onMessage(message);
}
catch (RuntimeException ex) {
onEndpointException(ex);
throw ex;
}
catch (Error err) {
onEndpointException(err);
throw err;
}
finally {
if (applyDeliveryCalls) {
try {
afterDelivery();
}
catch (ResourceException ex) {
throw new JmsResourceException(ex);
}
}
}
}
protected ClassLoader getEndpointClassLoader() {
return messageListener.getClass().getClassLoader();
}
}
/**
* Internal exception thrown when a ResourceExeption has been encountered
* during the endpoint invocation.
* <p>Will only be used if the ResourceAdapter does not invoke the
* endpoint's <code>beforeDelivery</code> and <code>afterDelivery</code>
* directly, leavng it up to the concrete endpoint to apply those -
* and to handle any ResourceExceptions thrown from them.
*/
public static class JmsResourceException extends RuntimeException {
public JmsResourceException(ResourceException cause) {
super(cause);
}
}
}

View File

@ -0,0 +1,142 @@
/*
* Copyright 2002-2007 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.endpoint;
import javax.jms.MessageListener;
import javax.resource.ResourceException;
import org.springframework.jca.endpoint.GenericMessageEndpointManager;
import org.springframework.jms.support.destination.DestinationResolver;
/**
* Extension of the generic JCA 1.5
* {@link org.springframework.jca.endpoint.GenericMessageEndpointManager},
* adding JMS-specific support for ActivationSpec configuration.
*
* <p>Allows for defining a common {@link JmsActivationSpecConfig} object
* that gets converted into a provider-specific JCA 1.5 ActivationSpec
* object for activating the endpoint.
*
* <p><b>NOTE:</b> This JCA-based endpoint manager supports standard JMS
* {@link javax.jms.MessageListener} endpoints only. It does <i>not</i> support
* Spring's {@link org.springframework.jms.listener.SessionAwareMessageListener}
* variant, simply because the JCA endpoint management contract does not allow
* for obtaining the current JMS {@link javax.jms.Session}.
*
* @author Juergen Hoeller
* @since 2.5
* @see javax.jms.MessageListener
* @see #setActivationSpecConfig
* @see JmsActivationSpecConfig
* @see JmsActivationSpecFactory
* @see JmsMessageEndpointFactory
*/
public class JmsMessageEndpointManager extends GenericMessageEndpointManager {
private final JmsMessageEndpointFactory endpointFactory = new JmsMessageEndpointFactory();
private boolean messageListenerSet = false;
private JmsActivationSpecFactory activationSpecFactory = new DefaultJmsActivationSpecFactory();
private JmsActivationSpecConfig activationSpecConfig;
/**
* Set the JMS MessageListener for this endpoint.
* <p>This is a shortcut for configuring a dedicated JmsMessageEndpointFactory.
* @see JmsMessageEndpointFactory#setMessageListener
*/
public void setMessageListener(MessageListener messageListener) {
this.endpointFactory.setMessageListener(messageListener);
this.messageListenerSet = true;
}
/**
* Set the XA transaction manager to use for wrapping endpoint
* invocations, enlisting the endpoint resource in each such transaction.
* <p>The passed-in object may be a transaction manager which implements
* Spring's {@link org.springframework.transaction.jta.TransactionFactory}
* interface, or a plain {@link javax.transaction.TransactionManager}.
* <p>If no transaction manager is specified, the endpoint invocation
* will simply not be wrapped in an XA transaction. Consult your
* resource provider's ActivationSpec documentation for the local
* transaction options of your particular provider.
* <p>This is a shortcut for configuring a dedicated JmsMessageEndpointFactory.
* @see JmsMessageEndpointFactory#setTransactionManager
*/
public void setTransactionManager(Object transactionManager) {
this.endpointFactory.setTransactionManager(transactionManager);
}
/**
* Set the factory for concrete JCA 1.5 ActivationSpec objects,
* creating JCA ActivationSpecs based on
* {@link #setActivationSpecConfig JmsActivationSpecConfig} objects.
* <p>This factory is dependent on the concrete JMS provider, e.g. on ActiveMQ.
* The default implementation simply guesses the ActivationSpec class name
* from the provider's class name (e.g. "ActiveMQResourceAdapter" ->
* "ActiveMQActivationSpec" in the same package), and populates the
* ActivationSpec properties as suggested by the JCA 1.5 specification
* (plus a couple of autodetected vendor-specific properties).
* @see DefaultJmsActivationSpecFactory
*/
public void setActivationSpecFactory(JmsActivationSpecFactory activationSpecFactory) {
this.activationSpecFactory =
(activationSpecFactory != null ? activationSpecFactory : new DefaultJmsActivationSpecFactory());
}
/**
* Set the DestinationResolver to use for resolving destination names
* into the JCA 1.5 ActivationSpec "destination" property.
* <p>If not specified, destination names will simply be passed in as Strings.
* If specified, destination names will be resolved into Destination objects first.
* <p>Note that a DestinationResolver is usually specified on the JmsActivationSpecFactory
* (see {@link StandardJmsActivationSpecFactory#setDestinationResolver}). This is simply
* a shortcut for parameterizing the default JmsActivationSpecFactory; it will replace
* any custom JmsActivationSpecFactory that might have been set before.
* @see StandardJmsActivationSpecFactory#setDestinationResolver
*/
public void setDestinationResolver(DestinationResolver destinationResolver) {
DefaultJmsActivationSpecFactory factory = new DefaultJmsActivationSpecFactory();
factory.setDestinationResolver(destinationResolver);
this.activationSpecFactory = factory;
}
/**
* Specify the {@link JmsActivationSpecConfig} object that this endpoint manager
* should use for activating its listener.
* <p>This config object will be turned into a concrete JCA 1.5 ActivationSpec
* object through a {@link #setActivationSpecFactory JmsActivationSpecFactory}.
*/
public void setActivationSpecConfig(JmsActivationSpecConfig activationSpecConfig) {
this.activationSpecConfig = activationSpecConfig;
}
public void afterPropertiesSet() throws ResourceException {
if (this.messageListenerSet) {
setMessageEndpointFactory(this.endpointFactory);
}
if (this.activationSpecConfig != null) {
setActivationSpec(
this.activationSpecFactory.createActivationSpec(getResourceAdapter(), this.activationSpecConfig));
}
super.afterPropertiesSet();
}
}

View File

@ -0,0 +1,204 @@
/*
* Copyright 2002-2008 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.endpoint;
import java.util.Properties;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.Topic;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.ResourceAdapter;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.jms.support.destination.DestinationResolutionException;
import org.springframework.jms.support.destination.DestinationResolver;
/**
* Standard implementation of the {@link JmsActivationSpecFactory} interface.
* Supports the standard JMS properties as defined by the JMS 1.5 specification
* (Appendix B); ignores Spring's "maxConcurrency" and "prefetchSize" settings.
*
* <p>The 'activationSpecClass' property is required, explicitly defining
* the fully-qualified class name of the provider's ActivationSpec class
* (e.g. "org.apache.activemq.ra.ActiveMQActivationSpec").
*
* <p>Check out {@link DefaultJmsActivationSpecFactory} for an extended variant
* of this class, supporting some further default conventions beyond the plain
* JMS 1.5 specification.
*
* @author Juergen Hoeller
* @since 2.5
* @see #setActivationSpecClass
* @see DefaultJmsActivationSpecFactory
*/
public class StandardJmsActivationSpecFactory implements JmsActivationSpecFactory {
private Class activationSpecClass;
private Properties defaultProperties;
private DestinationResolver destinationResolver;
/**
* Specify the fully-qualified ActivationSpec class name for the target
* provider (e.g. "org.apache.activemq.ra.ActiveMQActivationSpec").
*/
public void setActivationSpecClass(Class activationSpecClass) {
this.activationSpecClass = activationSpecClass;
}
/**
* Specify custom default properties, with String keys and String values.
* <p>Applied to each ActivationSpec object before it gets populated with
* listener-specific settings. Allows for configuring vendor-specific properties
* beyond the Spring-defined settings in {@link JmsActivationSpecConfig}.
*/
public void setDefaultProperties(Properties defaultProperties) {
this.defaultProperties = defaultProperties;
}
/**
* Set the DestinationResolver to use for resolving destination names
* into the JCA 1.5 ActivationSpec "destination" property.
* <p>If not specified, destination names will simply be passed in as Strings.
* If specified, destination names will be resolved into Destination objects first.
* <p>Note that a DestinationResolver for use with this factory must be
* able to work <i>without</i> an active JMS Session: e.g.
* {@link org.springframework.jms.support.destination.JndiDestinationResolver}
* or {@link org.springframework.jms.support.destination.BeanFactoryDestinationResolver}
* but not {@link org.springframework.jms.support.destination.DynamicDestinationResolver}.
*/
public void setDestinationResolver(DestinationResolver destinationResolver) {
this.destinationResolver = destinationResolver;
}
public ActivationSpec createActivationSpec(ResourceAdapter adapter, JmsActivationSpecConfig config) {
Class activationSpecClassToUse = this.activationSpecClass;
if (activationSpecClassToUse == null) {
activationSpecClassToUse = determineActivationSpecClass(adapter);
if (activationSpecClassToUse == null) {
throw new IllegalStateException("Property 'activationSpecClass' is required");
}
}
ActivationSpec spec = (ActivationSpec) BeanUtils.instantiateClass(activationSpecClassToUse);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(spec);
if (this.defaultProperties != null) {
bw.setPropertyValues(this.defaultProperties);
}
populateActivationSpecProperties(bw, config);
return spec;
}
/**
* Determine the ActivationSpec class for the given ResourceAdapter,
* if possible. Called if no 'activationSpecClass' has been set explicitly
* @param adapter the ResourceAdapter to check
* @return the corresponding ActivationSpec class, or <code>null</code>
* if not determinable
* @see #setActivationSpecClass
*/
protected Class determineActivationSpecClass(ResourceAdapter adapter) {
return null;
}
/**
* Populate the given ApplicationSpec object with the settings
* defined in the given configuration object.
* <p>This implementation applies all standard JMS settings, but ignores
* "maxConcurrency" and "prefetchSize" - not supported in standard JCA 1.5.
* @param bw the BeanWrapper wrapping the ActivationSpec object
* @param config the configured object holding common JMS settings
*/
protected void populateActivationSpecProperties(BeanWrapper bw, JmsActivationSpecConfig config) {
String destinationName = config.getDestinationName();
boolean pubSubDomain = config.isPubSubDomain();
Object destination = destinationName;
if (this.destinationResolver != null) {
try {
destination = this.destinationResolver.resolveDestinationName(null, destinationName, pubSubDomain);
}
catch (JMSException ex) {
throw new DestinationResolutionException("Cannot resolve destination name [" + destinationName + "]", ex);
}
}
bw.setPropertyValue("destination", destination);
bw.setPropertyValue("destinationType", pubSubDomain ? Topic.class.getName() : Queue.class.getName());
if (bw.isWritableProperty("subscriptionDurability")) {
bw.setPropertyValue("subscriptionDurability", config.isSubscriptionDurable() ? "Durable" : "NonDurable");
}
else if (config.isSubscriptionDurable()) {
// Standard JCA 1.5 "subscriptionDurability" apparently not supported...
throw new IllegalArgumentException(
"Durable subscriptions not supported by underlying provider: " + this.activationSpecClass.getName());
}
if (config.getDurableSubscriptionName() != null) {
bw.setPropertyValue("subscriptionName", config.getDurableSubscriptionName());
}
if (config.getClientId() != null) {
bw.setPropertyValue("clientId", config.getClientId());
}
if (config.getMessageSelector() != null) {
bw.setPropertyValue("messageSelector", config.getMessageSelector());
}
applyAcknowledgeMode(bw, config.getAcknowledgeMode());
}
/**
* Apply the specified acknowledge mode to the ActivationSpec object.
* <p>This implementation applies the standard JCA 1.5 acknowledge modes
* "Auto-acknowledge" and "Dups-ok-acknowledge". It throws an exception in
* case of <code>CLIENT_ACKNOWLEDGE</code> or <code>SESSION_TRANSACTED</code>
* having been requested.
* @param bw the BeanWrapper wrapping the ActivationSpec object
* @param ackMode the configured acknowledge mode
* (according to the constants in {@link javax.jms.Session}
* @see javax.jms.Session#AUTO_ACKNOWLEDGE
* @see javax.jms.Session#DUPS_OK_ACKNOWLEDGE
* @see javax.jms.Session#CLIENT_ACKNOWLEDGE
* @see javax.jms.Session#SESSION_TRANSACTED
*/
protected void applyAcknowledgeMode(BeanWrapper bw, int ackMode) {
if (ackMode == Session.SESSION_TRANSACTED) {
throw new IllegalArgumentException("No support for SESSION_TRANSACTED: Only \"Auto-acknowledge\" " +
"and \"Dups-ok-acknowledge\" supported in standard JCA 1.5");
}
else if (ackMode == Session.CLIENT_ACKNOWLEDGE) {
throw new IllegalArgumentException("No support for CLIENT_ACKNOWLEDGE: Only \"Auto-acknowledge\" " +
"and \"Dups-ok-acknowledge\" supported in standard JCA 1.5");
}
else if (bw.isWritableProperty("acknowledgeMode")) {
bw.setPropertyValue("acknowledgeMode",
ackMode == Session.DUPS_OK_ACKNOWLEDGE ? "Dups-ok-acknowledge" : "Auto-acknowledge");
}
else if (ackMode == Session.DUPS_OK_ACKNOWLEDGE) {
// Standard JCA 1.5 "acknowledgeMode" apparently not supported (e.g. WebSphere MQ 6.0.2.1)
throw new IllegalArgumentException(
"Dups-ok-acknowledge not supported by underlying provider: " + this.activationSpecClass.getName());
}
}
}

View File

@ -0,0 +1,7 @@
<html>
<body>
This package provides JCA-based endpoint management for JMS message listeners.
</body>
</html>

View File

@ -0,0 +1,9 @@
<html>
<body>
This package contains the base message listener container facility.
It also offers the DefaultMessageListenerContainer and SimpleMessageListenerContainer
implementations, based on the plain JMS client API.
</body>
</html>

View File

@ -0,0 +1,179 @@
/*
* Copyright 2002-2008 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.serversession;
import javax.jms.JMSException;
import javax.jms.ServerSession;
import javax.jms.Session;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.task.TaskExecutor;
import org.springframework.jms.support.JmsUtils;
import org.springframework.scheduling.timer.TimerTaskExecutor;
/**
* Abstract base class for ServerSessionFactory implementations
* that pool ServerSessionFactory instances.
*
* <p>Provides a factory method that creates a poolable ServerSession
* (to be added as new instance to a pool), a callback method invoked
* when a ServerSession finished an execution of its listener (to return
* an instance to the pool), and a method to destroy a ServerSession instance
* (after removing an instance from the pool).
*
* @author Juergen Hoeller
* @since 2.0
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
* @see org.springframework.jms.listener.serversession.CommonsPoolServerSessionFactory
*/
public abstract class AbstractPoolingServerSessionFactory implements ServerSessionFactory {
protected final Log logger = LogFactory.getLog(getClass());
private TaskExecutor taskExecutor;
private int maxSize;
/**
* Specify the TaskExecutor to use for executing ServerSessions
* (and consequently, the underlying MessageListener).
* <p>Default is a {@link org.springframework.scheduling.timer.TimerTaskExecutor}
* for each pooled ServerSession, using one Thread per pooled JMS Session.
* Alternatives are a shared TimerTaskExecutor, sharing a single Thread
* for the execution of all ServerSessions, or a TaskExecutor
* implementation backed by a thread pool.
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
/**
* Return the TaskExecutor to use for executing ServerSessions.
*/
protected TaskExecutor getTaskExecutor() {
return this.taskExecutor;
}
/**
* Set the maximum size of the pool.
*/
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
/**
* Return the maximum size of the pool.
*/
public int getMaxSize() {
return this.maxSize;
}
/**
* Create a new poolable ServerSession.
* To be called when a new instance should be added to the pool.
* @param sessionManager the listener session manager to create the
* poolable ServerSession for
* @return the new poolable ServerSession
* @throws JMSException if creation failed
*/
protected final ServerSession createServerSession(ListenerSessionManager sessionManager) throws JMSException {
return new PoolableServerSession(sessionManager);
}
/**
* Destroy the given poolable ServerSession.
* To be called when an instance got removed from the pool.
* @param serverSession the poolable ServerSession to destroy
*/
protected final void destroyServerSession(ServerSession serverSession) {
if (serverSession != null) {
((PoolableServerSession) serverSession).close();
}
}
/**
* Template method called by a ServerSession if it finished
* execution of its listener and is ready to go back into the pool.
* <p>Subclasses should implement the actual returning of the instance
* to the pool.
* @param serverSession the ServerSession that finished its execution
* @param sessionManager the session manager that the ServerSession belongs to
*/
protected abstract void serverSessionFinished(
ServerSession serverSession, ListenerSessionManager sessionManager);
/**
* ServerSession implementation designed to be pooled.
* Creates a new JMS Session on instantiation, reuses it
* for all executions, and closes it on <code>close</code>.
* <p>Creates a TimerTaskExecutor (using a single Thread) per
* ServerSession, unless given a specific TaskExecutor to use.
*/
private class PoolableServerSession implements ServerSession {
private final ListenerSessionManager sessionManager;
private final Session session;
private TaskExecutor taskExecutor;
private TimerTaskExecutor internalExecutor;
public PoolableServerSession(final ListenerSessionManager sessionManager) throws JMSException {
this.sessionManager = sessionManager;
this.session = sessionManager.createListenerSession();
this.taskExecutor = getTaskExecutor();
if (this.taskExecutor == null) {
this.internalExecutor = new TimerTaskExecutor();
this.internalExecutor.afterPropertiesSet();
this.taskExecutor = this.internalExecutor;
}
}
public Session getSession() {
return this.session;
}
public void start() {
this.taskExecutor.execute(new Runnable() {
public void run() {
try {
sessionManager.executeListenerSession(session);
}
finally {
serverSessionFinished(PoolableServerSession.this, sessionManager);
}
}
});
}
public void close() {
if (this.internalExecutor != null) {
this.internalExecutor.destroy();
}
JmsUtils.closeSession(this.session);
}
}
}

View File

@ -0,0 +1,274 @@
/*
* Copyright 2002-2008 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.serversession;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.jms.JMSException;
import javax.jms.ServerSession;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
/**
* {@link ServerSessionFactory} implementation that holds JMS
* <code>ServerSessions</code> in a configurable Jakarta Commons Pool.
*
* <p>By default, an instance of <code>GenericObjectPool</code> is created.
* Subclasses may change the type of <code>ObjectPool</code> used by
* overriding the <code>createObjectPool</code> method.
*
* <p>Provides many configuration properties mirroring those of the Commons Pool
* <code>GenericObjectPool</code> class; these properties are passed to the
* <code>GenericObjectPool</code> during construction. If creating a subclass of this
* class to change the <code>ObjectPool</code> implementation type, pass in the values
* of configuration properties that are relevant to your chosen implementation.
*
* @author Juergen Hoeller
* @since 2.0
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
* @see GenericObjectPool
* @see #createObjectPool
* @see #setMaxSize
* @see #setMaxIdle
* @see #setMinIdle
* @see #setMaxWait
*/
public class CommonsPoolServerSessionFactory extends AbstractPoolingServerSessionFactory {
private int maxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;
private int minIdle = GenericObjectPool.DEFAULT_MIN_IDLE;
private long maxWait = GenericObjectPool.DEFAULT_MAX_WAIT;
private long timeBetweenEvictionRunsMillis = GenericObjectPool.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
private long minEvictableIdleTimeMillis = GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
private final Map serverSessionPools = Collections.synchronizedMap(new HashMap(1));
/**
* Create a CommonsPoolServerSessionFactory with default settings.
* Default maximum size of the pool is 8.
* @see #setMaxSize
* @see GenericObjectPool#setMaxActive
*/
public CommonsPoolServerSessionFactory() {
setMaxSize(GenericObjectPool.DEFAULT_MAX_ACTIVE);
}
/**
* Set the maximum number of idle ServerSessions in the pool.
* Default is 8.
* @see GenericObjectPool#setMaxIdle
*/
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
/**
* Return the maximum number of idle ServerSessions in the pool.
*/
public int getMaxIdle() {
return this.maxIdle;
}
/**
* Set the minimum number of idle ServerSessions in the pool.
* Default is 0.
* @see GenericObjectPool#setMinIdle
*/
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
/**
* Return the minimum number of idle ServerSessions in the pool.
*/
public int getMinIdle() {
return this.minIdle;
}
/**
* Set the maximum waiting time for fetching an ServerSession from the pool.
* Default is -1, waiting forever.
* @see GenericObjectPool#setMaxWait
*/
public void setMaxWait(long maxWait) {
this.maxWait = maxWait;
}
/**
* Return the maximum waiting time for fetching a ServerSession from the pool.
*/
public long getMaxWait() {
return this.maxWait;
}
/**
* Set the time between eviction runs that check idle ServerSessions
* whether they have been idle for too long or have become invalid.
* Default is -1, not performing any eviction.
* @see GenericObjectPool#setTimeBetweenEvictionRunsMillis
*/
public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}
/**
* Return the time between eviction runs that check idle ServerSessions.
*/
public long getTimeBetweenEvictionRunsMillis() {
return this.timeBetweenEvictionRunsMillis;
}
/**
* Set the minimum time that an idle ServerSession can sit in the pool
* before it becomes subject to eviction. Default is 1800000 (30 minutes).
* <p>Note that eviction runs need to be performed to take this
* setting into effect.
* @see #setTimeBetweenEvictionRunsMillis
* @see GenericObjectPool#setMinEvictableIdleTimeMillis
*/
public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
/**
* Return the minimum time that an idle ServerSession can sit in the pool.
*/
public long getMinEvictableIdleTimeMillis() {
return this.minEvictableIdleTimeMillis;
}
/**
* Returns a ServerSession from the pool, creating a new pool for the given
* session manager if necessary.
* @see #createObjectPool
*/
public ServerSession getServerSession(ListenerSessionManager sessionManager) throws JMSException {
ObjectPool pool = null;
synchronized (this.serverSessionPools) {
pool = (ObjectPool) this.serverSessionPools.get(sessionManager);
if (pool == null) {
if (logger.isInfoEnabled()) {
logger.info("Creating Commons ServerSession pool for: " + sessionManager);
}
pool = createObjectPool(sessionManager);
this.serverSessionPools.put(sessionManager, pool);
}
}
try {
return (ServerSession) pool.borrowObject();
}
catch (Exception ex) {
JMSException jmsEx = new JMSException("Failed to borrow ServerSession from pool");
jmsEx.setLinkedException(ex);
throw jmsEx;
}
}
/**
* Subclasses can override this if they want to return a specific Commons pool.
* They should apply any configuration properties to the pool here.
* <p>Default is a GenericObjectPool instance with the given pool size.
* @param sessionManager the session manager to use for
* creating and executing new listener sessions
* @return an empty Commons <code>ObjectPool</code>.
* @see org.apache.commons.pool.impl.GenericObjectPool
* @see #setMaxSize
*/
protected ObjectPool createObjectPool(ListenerSessionManager sessionManager) {
GenericObjectPool pool = new GenericObjectPool(createPoolableObjectFactory(sessionManager));
pool.setMaxActive(getMaxSize());
pool.setMaxIdle(getMaxIdle());
pool.setMinIdle(getMinIdle());
pool.setMaxWait(getMaxWait());
pool.setTimeBetweenEvictionRunsMillis(getTimeBetweenEvictionRunsMillis());
pool.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis());
return pool;
}
/**
* Create a Commons PoolableObjectFactory adapter for the given session manager.
* Calls <code>createServerSession</code> and <code>destroyServerSession</code>
* as defined by the AbstractPoolingServerSessionFactory class.
* @param sessionManager the session manager to use for
* creating and executing new listener sessions
* @return the Commons PoolableObjectFactory
* @see #createServerSession
* @see #destroyServerSession
*/
protected PoolableObjectFactory createPoolableObjectFactory(final ListenerSessionManager sessionManager) {
return new PoolableObjectFactory() {
public Object makeObject() throws JMSException {
return createServerSession(sessionManager);
}
public void destroyObject(Object obj) {
destroyServerSession((ServerSession) obj);
}
public boolean validateObject(Object obj) {
return true;
}
public void activateObject(Object obj) {
}
public void passivateObject(Object obj) {
}
};
}
/**
* Returns the given ServerSession, which just finished an execution
* of its listener, back to the pool.
*/
protected void serverSessionFinished(ServerSession serverSession, ListenerSessionManager sessionManager) {
ObjectPool pool = (ObjectPool) this.serverSessionPools.get(sessionManager);
if (pool == null) {
throw new IllegalStateException("No pool found for session manager [" + sessionManager + "]");
}
try {
pool.returnObject(serverSession);
}
catch (Exception ex) {
logger.error("Failed to return ServerSession to pool", ex);
}
}
/**
* Closes and removes the pool for the given session manager.
*/
public void close(ListenerSessionManager sessionManager) {
ObjectPool pool = (ObjectPool) this.serverSessionPools.remove(sessionManager);
if (pool != null) {
try {
pool.close();
}
catch (Exception ex) {
logger.error("Failed to close ServerSession pool", ex);
}
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2002-2008 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.serversession;
import javax.jms.JMSException;
import javax.jms.Session;
/**
* SPI interface for creating and executing JMS Sessions,
* pre-populated with a specific MessageListener.
* Implemented by ServerSessionMessageListenerContainer,
* accessed by ServerSessionFactory implementations.
*
* <p>Effectively, an instance that implements this interface
* represents a message listener container for a specific
* listener and destination.
*
* @author Juergen Hoeller
* @since 2.0
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
* @see ServerSessionFactory
* @see ServerSessionMessageListenerContainer
*/
public interface ListenerSessionManager {
/**
* Create a new JMS Session, pre-populated with this manager's
* MessageListener.
* @return the new JMS Session
* @throws JMSException if Session creation failed
* @see javax.jms.Session#setMessageListener(javax.jms.MessageListener)
*/
Session createListenerSession() throws JMSException;
/**
* Execute the given JMS Session, triggering its MessageListener
* with pre-loaded messages.
* @param session the JMS Session to invoke
* @see javax.jms.Session#run()
*/
void executeListenerSession(Session session);
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2002-2008 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.serversession;
import javax.jms.JMSException;
import javax.jms.ServerSession;
import org.springframework.jms.listener.serversession.ListenerSessionManager;
/**
* SPI interface to be implemented by components that manage
* JMS ServerSessions. Usually, but not necessarily, an implementation
* of this interface will hold a pool of ServerSessions.
*
* <p>The passed-in ListenerSessionManager has to be used for creating
* and executing JMS Sessions. This session manager is responsible for
* registering a MessageListener with all Sessions that it creates.
* Consequently, the ServerSessionFactory implementation has to
* concentrate on the actual lifecycle (e.g. pooling) of JMS Sessions,
* but is not concerned about Session creation or execution.
*
* @author Juergen Hoeller
* @since 2.0
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
* @see org.springframework.jms.listener.serversession.ListenerSessionManager
* @see org.springframework.jms.listener.serversession.ServerSessionMessageListenerContainer
*/
public interface ServerSessionFactory {
/**
* Retrieve a JMS ServerSession for the given session manager.
* @param sessionManager the session manager to use for
* creating and executing new listener sessions
* (implicitly indicating the target listener to invoke)
* @return the JMS ServerSession
* @throws JMSException if retrieval failed
*/
ServerSession getServerSession(ListenerSessionManager sessionManager) throws JMSException;
/**
* Close all ServerSessions for the given session manager.
* @param sessionManager the session manager used for
* creating and executing new listener sessions
* (implicitly indicating the target listener)
*/
void close(ListenerSessionManager sessionManager);
}

View File

@ -0,0 +1,256 @@
/*
* Copyright 2002-2008 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.serversession;
import javax.jms.Connection;
import javax.jms.ConnectionConsumer;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ServerSession;
import javax.jms.ServerSessionPool;
import javax.jms.Session;
import javax.jms.Topic;
import org.springframework.jms.listener.AbstractMessageListenerContainer;
import org.springframework.jms.support.JmsUtils;
/**
* Message listener container that builds on the {@link javax.jms.ServerSessionPool}
* SPI, creating JMS ServerSession instances through a pluggable
* {@link ServerSessionFactory}.
*
* <p><b>NOTE:</b> This class requires a JMS 1.1+ provider, because it builds on the
* domain-independent API. <b>Use the {@link ServerSessionMessageListenerContainer102}
* subclass for a JMS 1.0.2 provider, e.g. when running on a J2EE 1.3 server.</b>
*
* <p>The default ServerSessionFactory is a {@link SimpleServerSessionFactory},
* which will create a new ServerSession for each listener execution.
* Consider specifying a {@link CommonsPoolServerSessionFactory} to reuse JMS
* Sessions and/or to limit the number of concurrent ServerSession executions.
*
* <p>See the {@link AbstractMessageListenerContainer} javadoc for details
* on acknowledge modes and other configuration options.
*
* <p><b>This is an 'advanced' (special-purpose) message listener container.</b>
* For a simpler message listener container, in particular when using
* a JMS provider without ServerSessionPool support, consider using
* {@link org.springframework.jms.listener.SimpleMessageListenerContainer}.
* For a general one-stop shop that is nevertheless very flexible, consider
* {@link org.springframework.jms.listener.DefaultMessageListenerContainer}.
*
* @author Juergen Hoeller
* @since 2.0
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
* @see org.springframework.jms.listener.SimpleMessageListenerContainer
* @see org.springframework.jms.listener.endpoint.JmsMessageEndpointManager
*/
public class ServerSessionMessageListenerContainer extends AbstractMessageListenerContainer
implements ListenerSessionManager {
private ServerSessionFactory serverSessionFactory = new SimpleServerSessionFactory();
private int maxMessagesPerTask = 1;
private ConnectionConsumer consumer;
/**
* Set the Spring ServerSessionFactory to use.
* <p>Default is a plain SimpleServerSessionFactory.
* Consider using a CommonsPoolServerSessionFactory to reuse JMS Sessions
* and/or to limit the number of concurrent ServerSession executions.
* @see SimpleServerSessionFactory
* @see CommonsPoolServerSessionFactory
*/
public void setServerSessionFactory(ServerSessionFactory serverSessionFactory) {
this.serverSessionFactory =
(serverSessionFactory != null ? serverSessionFactory : new SimpleServerSessionFactory());
}
/**
* Return the Spring ServerSessionFactory to use.
*/
protected ServerSessionFactory getServerSessionFactory() {
return this.serverSessionFactory;
}
/**
* Set the maximum number of messages to load into a JMS Session.
* Default is 1.
* <p>See the corresponding JMS <code>createConnectionConsumer</code>
* argument for details.
* @see javax.jms.Connection#createConnectionConsumer
*/
public void setMaxMessagesPerTask(int maxMessagesPerTask) {
this.maxMessagesPerTask = maxMessagesPerTask;
}
/**
* Return the maximum number of messages to load into a JMS Session.
*/
protected int getMaxMessagesPerTask() {
return this.maxMessagesPerTask;
}
//-------------------------------------------------------------------------
// Implementation of AbstractMessageListenerContainer's template methods
//-------------------------------------------------------------------------
/**
* Always use a shared JMS Connection.
*/
protected final boolean sharedConnectionEnabled() {
return true;
}
/**
* Creates a JMS ServerSessionPool for the specified listener and registers
* it with a JMS ConnectionConsumer for the specified destination.
* @see #createServerSessionPool
* @see #createConsumer
*/
protected void doInitialize() throws JMSException {
establishSharedConnection();
Connection con = getSharedConnection();
Destination destination = getDestination();
if (destination == null) {
Session session = createSession(con);
try {
destination = resolveDestinationName(session, getDestinationName());
}
finally {
JmsUtils.closeSession(session);
}
}
ServerSessionPool pool = createServerSessionPool();
this.consumer = createConsumer(con, destination, pool);
}
/**
* Create a JMS ServerSessionPool for the specified message listener,
* via this container's ServerSessionFactory.
* <p>This message listener container implements the ListenerSessionManager
* interface, hence can be passed to the ServerSessionFactory itself.
* @return the ServerSessionPool
* @throws JMSException if creation of the ServerSessionPool failed
* @see #setServerSessionFactory
* @see ServerSessionFactory#getServerSession(ListenerSessionManager)
*/
protected ServerSessionPool createServerSessionPool() throws JMSException {
return new ServerSessionPool() {
public ServerSession getServerSession() throws JMSException {
logger.debug("JMS ConnectionConsumer requests ServerSession");
return getServerSessionFactory().getServerSession(ServerSessionMessageListenerContainer.this);
}
};
}
/**
* Return the JMS ConnectionConsumer used by this message listener container.
* Available after initialization.
*/
protected final ConnectionConsumer getConsumer() {
return this.consumer;
}
/**
* Close the JMS ServerSessionPool for the specified message listener,
* via this container's ServerSessionFactory, and subsequently also
* this container's JMS ConnectionConsumer.
* <p>This message listener container implements the ListenerSessionManager
* interface, hence can be passed to the ServerSessionFactory itself.
* @see #setServerSessionFactory
* @see ServerSessionFactory#getServerSession(ListenerSessionManager)
*/
protected void doShutdown() throws JMSException {
logger.debug("Closing ServerSessionFactory");
getServerSessionFactory().close(this);
logger.debug("Closing JMS ConnectionConsumer");
this.consumer.close();
}
//-------------------------------------------------------------------------
// Implementation of the ListenerSessionManager interface
//-------------------------------------------------------------------------
/**
* Create a JMS Session with the specified listener registered.
* Listener execution is delegated to the <code>executeListener</code> method.
* <p>Default implementation simply calls <code>setMessageListener</code>
* on a newly created JMS Session, according to the JMS specification's
* ServerSessionPool section.
* @return the JMS Session
* @throws JMSException if thrown by JMS API methods
* @see #executeListener
*/
public Session createListenerSession() throws JMSException {
final Session session = createSession(getSharedConnection());
session.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
executeListener(session, message);
}
});
return session;
}
/**
* Execute the given JMS Session, triggering invocation
* of its listener.
* <p>Default implementation simply calls <code>run()</code>
* on the JMS Session, according to the JMS specification's
* ServerSessionPool section.
* @param session the JMS Session to execute
*/
public void executeListenerSession(Session session) {
session.run();
}
//-------------------------------------------------------------------------
// JMS 1.1 factory methods, potentially overridden for JMS 1.0.2
//-------------------------------------------------------------------------
/**
* Create a JMS ConnectionConsumer for the given Connection.
* <p>This implementation uses JMS 1.1 API.
* @param con the JMS Connection to create a Session for
* @param destination the JMS Destination to listen to
* @param pool the ServerSessionpool to use
* @return the new JMS Session
* @throws JMSException if thrown by JMS API methods
*/
protected ConnectionConsumer createConsumer(Connection con, Destination destination, ServerSessionPool pool)
throws JMSException {
if (isSubscriptionDurable() && destination instanceof Topic) {
return con.createDurableConnectionConsumer(
(Topic) destination, getDurableSubscriptionName(), getMessageSelector(), pool, getMaxMessagesPerTask());
}
else {
return con.createConnectionConsumer(destination, getMessageSelector(), pool, getMaxMessagesPerTask());
}
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright 2002-2008 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.serversession;
import javax.jms.Connection;
import javax.jms.ConnectionConsumer;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.ServerSessionPool;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
/**
* A subclass of {@link ServerSessionMessageListenerContainer} for the JMS 1.0.2 specification,
* not relying on JMS 1.1 methods like ServerSessionMessageListenerContainer itself.
*
* <p>This class can be used for JMS 1.0.2 providers, offering the same facility as
* ServerSessionMessageListenerContainer does for JMS 1.1 providers.
*
* @author Juergen Hoeller
* @since 2.0
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
*/
public class ServerSessionMessageListenerContainer102 extends ServerSessionMessageListenerContainer {
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected Connection createConnection() throws JMSException {
if (isPubSubDomain()) {
return ((TopicConnectionFactory) getConnectionFactory()).createTopicConnection();
}
else {
return ((QueueConnectionFactory) getConnectionFactory()).createQueueConnection();
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected ConnectionConsumer createConsumer(Connection con, Destination destination, ServerSessionPool pool)
throws JMSException {
if (isPubSubDomain()) {
if (isSubscriptionDurable()) {
return ((TopicConnection) con).createDurableConnectionConsumer(
(Topic) destination, getDurableSubscriptionName(), getMessageSelector(), pool, getMaxMessagesPerTask());
}
else {
return ((TopicConnection) con).createConnectionConsumer(
(Topic) destination, getMessageSelector(), pool, getMaxMessagesPerTask());
}
}
else {
return ((QueueConnection) con).createConnectionConsumer(
(Queue) destination, getMessageSelector(), pool, getMaxMessagesPerTask());
}
}
/**
* This implementation overrides the superclass method to use JMS 1.0.2 API.
*/
protected Session createSession(Connection con) throws JMSException {
if (isPubSubDomain()) {
return ((TopicConnection) con).createTopicSession(isSessionTransacted(), getSessionAcknowledgeMode());
}
else {
return ((QueueConnection) con).createQueueSession(isSessionTransacted(), getSessionAcknowledgeMode());
}
}
/**
* This implementation overrides the superclass method to avoid using
* JMS 1.1's Session <code>getAcknowledgeMode()</code> method.
* The best we can do here is to check the setting on the listener container.
*/
protected boolean isClientAcknowledge(Session session) throws JMSException {
return (getSessionAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE);
}
}

View File

@ -0,0 +1,138 @@
/*
* Copyright 2002-2008 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.serversession;
import javax.jms.JMSException;
import javax.jms.ServerSession;
import javax.jms.Session;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.jms.support.JmsUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* The simplest possible implementation of the ServerSessionFactory SPI:
* creating a new ServerSession with a new JMS Session every time.
* This is the default used by ServerSessionMessageListenerContainer.
*
* <p>The execution of a ServerSession (and its MessageListener) gets delegated
* to a TaskExecutor. By default, a SimpleAsyncTaskExecutor will be used,
* creating a new Thread for every execution attempt. Alternatives are a
* TimerTaskExecutor, sharing a single Thread for the execution of all
* ServerSessions, or a TaskExecutor implementation backed by a thread pool.
*
* <p>To reuse JMS Sessions and/or to limit the number of concurrent
* ServerSession executions, consider using a pooling ServerSessionFactory:
* for example, CommonsPoolServerSessionFactory.
*
* @author Juergen Hoeller
* @since 2.0
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
* @see org.springframework.core.task.TaskExecutor
* @see org.springframework.core.task.SimpleAsyncTaskExecutor
* @see org.springframework.scheduling.timer.TimerTaskExecutor
* @see CommonsPoolServerSessionFactory
* @see ServerSessionMessageListenerContainer
*/
public class SimpleServerSessionFactory implements ServerSessionFactory {
/**
* Default thread name prefix: "SimpleServerSessionFactory-".
*/
public static final String DEFAULT_THREAD_NAME_PREFIX =
ClassUtils.getShortName(SimpleServerSessionFactory.class) + "-";
private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(DEFAULT_THREAD_NAME_PREFIX);
/**
* Specify the TaskExecutor to use for executing ServerSessions
* (and consequently, the underlying MessageListener).
* <p>Default is a SimpleAsyncTaskExecutor, creating a new Thread for
* every execution attempt. Alternatives are a TimerTaskExecutor,
* sharing a single Thread for the execution of all ServerSessions,
* or a TaskExecutor implementation backed by a thread pool.
* @see org.springframework.core.task.SimpleAsyncTaskExecutor
* @see org.springframework.scheduling.timer.TimerTaskExecutor
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
Assert.notNull(taskExecutor, "taskExecutor is required");
this.taskExecutor = taskExecutor;
}
/**
* Return the TaskExecutor to use for executing ServerSessions.
*/
protected TaskExecutor getTaskExecutor() {
return taskExecutor;
}
/**
* Creates a new SimpleServerSession with a new JMS Session
* for every call.
*/
public ServerSession getServerSession(ListenerSessionManager sessionManager) throws JMSException {
return new SimpleServerSession(sessionManager);
}
/**
* This implementation is empty, as there is no state held for
* each ListenerSessionManager.
*/
public void close(ListenerSessionManager sessionManager) {
}
/**
* ServerSession implementation that simply creates a new
* JMS Session and executes it via the specified TaskExecutor.
*/
private class SimpleServerSession implements ServerSession {
private final ListenerSessionManager sessionManager;
private final Session session;
public SimpleServerSession(ListenerSessionManager sessionManager) throws JMSException {
this.sessionManager = sessionManager;
this.session = sessionManager.createListenerSession();
}
public Session getSession() {
return session;
}
public void start() {
getTaskExecutor().execute(new Runnable() {
public void run() {
try {
sessionManager.executeListenerSession(session);
}
finally {
JmsUtils.closeSession(session);
}
}
});
}
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
This package contains the ServerSessionMessageListenerContainer implementation,
based on the standard JMS ServerSessionPool API.
</body>
</html>

View File

@ -0,0 +1,8 @@
<html>
<body>
This package contains integration classes for JMS,
allowing for Spring-style JMS access.
</body>
</html>

View File

@ -0,0 +1,442 @@
/*
* Copyright 2002-2007 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.remoting;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageFormatException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.TemporaryQueue;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jms.connection.ConnectionFactoryUtils;
import org.springframework.jms.support.JmsUtils;
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.remoting.RemoteAccessException;
import org.springframework.remoting.RemoteInvocationFailureException;
import org.springframework.remoting.support.DefaultRemoteInvocationFactory;
import org.springframework.remoting.support.RemoteInvocation;
import org.springframework.remoting.support.RemoteInvocationFactory;
import org.springframework.remoting.support.RemoteInvocationResult;
/**
* {@link org.aopalliance.intercept.MethodInterceptor} for accessing a
* JMS-based remote service.
*
* <p>Serializes remote invocation objects and deserializes remote invocation
* result objects. Uses Java serialization just like RMI, but with the JMS
* provider as communication infrastructure.
*
* <p>To be configured with a {@link javax.jms.QueueConnectionFactory} and a
* target queue (either as {@link javax.jms.Queue} reference or as queue name).
*
* <p>Thanks to James Strachan for the original prototype that this
* JMS invoker mechanism was inspired by!
*
* @author Juergen Hoeller
* @author James Strachan
* @since 2.0
* @see #setConnectionFactory
* @see #setQueue
* @see #setQueueName
* @see org.springframework.jms.remoting.JmsInvokerServiceExporter
* @see org.springframework.jms.remoting.JmsInvokerProxyFactoryBean
*/
public class JmsInvokerClientInterceptor implements MethodInterceptor, InitializingBean {
private ConnectionFactory connectionFactory;
private Object queue;
private DestinationResolver destinationResolver = new DynamicDestinationResolver();
private RemoteInvocationFactory remoteInvocationFactory = new DefaultRemoteInvocationFactory();
private MessageConverter messageConverter = new SimpleMessageConverter();
private long receiveTimeout = 0;
/**
* Set the QueueConnectionFactory to use for obtaining JMS QueueConnections.
*/
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
/**
* Return the QueueConnectionFactory to use for obtaining JMS QueueConnections.
*/
protected ConnectionFactory getConnectionFactory() {
return this.connectionFactory;
}
/**
* Set the target Queue to send invoker requests to.
*/
public void setQueue(Queue queue) {
this.queue = queue;
}
/**
* Set the name of target queue to send invoker requests to.
* The specified name will be dynamically resolved via the
* {@link #setDestinationResolver DestinationResolver}.
*/
public void setQueueName(String queueName) {
this.queue = queueName;
}
/**
* Set the DestinationResolver that is to be used to resolve Queue
* references for this accessor.
* <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) {
this.destinationResolver =
(destinationResolver != null ? destinationResolver : new DynamicDestinationResolver());
}
/**
* Set the RemoteInvocationFactory to use for this accessor.
* Default is a {@link org.springframework.remoting.support.DefaultRemoteInvocationFactory}.
* <p>A custom invocation factory can add further context information
* to the invocation, for example user credentials.
*/
public void setRemoteInvocationFactory(RemoteInvocationFactory remoteInvocationFactory) {
this.remoteInvocationFactory =
(remoteInvocationFactory != null ? remoteInvocationFactory : new DefaultRemoteInvocationFactory());
}
/**
* Specify the MessageConverter to use for turning
* {@link org.springframework.remoting.support.RemoteInvocation}
* objects into request messages, as well as response messages into
* {@link org.springframework.remoting.support.RemoteInvocationResult} objects.
* <p>Default is a {@link org.springframework.jms.support.converter.SimpleMessageConverter},
* using a standard JMS {@link javax.jms.ObjectMessage} for each invocation /
* invocation result object.
* <p>Custom implementations may generally adapt Serializables into
* special kinds of messages, or might be specifically tailored for
* translating RemoteInvocation(Result)s into specific kinds of messages.
*/
public void setMessageConverter(MessageConverter messageConverter) {
this.messageConverter = (messageConverter != null ? messageConverter : new SimpleMessageConverter());
}
/**
* Set the timeout to use for receiving the response message for a request
* (in milliseconds).
* <p>The default is 0, which indicates a blocking receive without timeout.
* @see javax.jms.MessageConsumer#receive(long)
* @see javax.jms.MessageConsumer#receive()
*/
public void setReceiveTimeout(long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
}
/**
* Return the timeout to use for receiving the response message for a request
* (in milliseconds).
*/
protected long getReceiveTimeout() {
return this.receiveTimeout;
}
public void afterPropertiesSet() {
if (getConnectionFactory() == null) {
throw new IllegalArgumentException("Property 'connectionFactory' is required");
}
if (this.queue == null) {
throw new IllegalArgumentException("'queue' or 'queueName' is required");
}
}
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
return "JMS invoker proxy for queue [" + this.queue + "]";
}
RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
RemoteInvocationResult result = null;
try {
result = executeRequest(invocation);
}
catch (JMSException ex) {
throw convertJmsInvokerAccessException(ex);
}
try {
return recreateRemoteInvocationResult(result);
}
catch (Throwable ex) {
if (result.hasInvocationTargetException()) {
throw ex;
}
else {
throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() +
"] failed in JMS invoker remote service at queue [" + this.queue + "]", ex);
}
}
}
/**
* Create a new RemoteInvocation object for the given AOP method invocation.
* The default implementation delegates to the RemoteInvocationFactory.
* <p>Can be overridden in subclasses to provide custom RemoteInvocation
* subclasses, containing additional invocation parameters like user credentials.
* Note that it is preferable to use a custom RemoteInvocationFactory which
* is a reusable strategy.
* @param methodInvocation the current AOP method invocation
* @return the RemoteInvocation object
* @see RemoteInvocationFactory#createRemoteInvocation
*/
protected RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
return this.remoteInvocationFactory.createRemoteInvocation(methodInvocation);
}
/**
* Execute the given remote invocation, sending an invoker request message
* to this accessor's target queue and waiting for a corresponding response.
* @param invocation the RemoteInvocation to execute
* @return the RemoteInvocationResult object
* @throws JMSException in case of JMS failure
* @see #doExecuteRequest
*/
protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws JMSException {
Connection con = createConnection();
Session session = null;
try {
session = createSession(con);
Queue queueToUse = resolveQueue(session);
Message requestMessage = createRequestMessage(session, invocation);
con.start();
Message responseMessage = doExecuteRequest(session, queueToUse, requestMessage);
return extractInvocationResult(responseMessage);
}
finally {
JmsUtils.closeSession(session);
ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory(), true);
}
}
/**
* Create a new JMS Connection for this JMS invoker,
* ideally a <code>javax.jms.QueueConnection</code>.
* <p>The default implementation uses the
* <code>javax.jms.QueueConnectionFactory</code> API if available,
* falling back to a standard JMS 1.1 ConnectionFactory otherwise.
* This is necessary for working with generic JMS 1.1 connection pools
* (such as ActiveMQ's <code>org.apache.activemq.pool.PooledConnectionFactory</code>).
*/
protected Connection createConnection() throws JMSException {
ConnectionFactory cf = getConnectionFactory();
if (cf instanceof QueueConnectionFactory) {
return ((QueueConnectionFactory) cf).createQueueConnection();
}
else {
return cf.createConnection();
}
}
/**
* Create a new JMS Session for this JMS invoker,
* ideally a <code>javax.jms.QueueSession</code>.
* <p>The default implementation uses the
* <code>javax.jms.QueueConnection</code> API if available,
* falling back to a standard JMS 1.1 Connection otherwise.
* This is necessary for working with generic JMS 1.1 connection pools
* (such as ActiveMQ's <code>org.apache.activemq.pool.PooledConnectionFactory</code>).
*/
protected Session createSession(Connection con) throws JMSException {
if (con instanceof QueueConnection) {
return ((QueueConnection) con).createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
}
else {
return con.createSession(false, Session.AUTO_ACKNOWLEDGE);
}
}
/**
* Resolve this accessor's target queue.
* @param session the current JMS Session
* @return the resolved target Queue
* @throws JMSException if resolution failed
*/
protected Queue resolveQueue(Session session) throws JMSException {
if (this.queue instanceof Queue) {
return (Queue) this.queue;
}
else if (this.queue instanceof String) {
return resolveQueueName(session, (String) this.queue);
}
else {
throw new javax.jms.IllegalStateException(
"Queue object [" + this.queue + "] is neither a [javax.jms.Queue] nor a queue name String");
}
}
/**
* Resolve the given queue name into a JMS {@link javax.jms.Queue},
* via this accessor's {@link DestinationResolver}.
* @param session the current JMS Session
* @param queueName the name of the queue
* @return the located Queue
* @throws JMSException if resolution failed
* @see #setDestinationResolver
*/
protected Queue resolveQueueName(Session session, String queueName) throws JMSException {
return (Queue) this.destinationResolver.resolveDestinationName(session, queueName, false);
}
/**
* Create the invoker request message.
* <p>The default implementation creates a JMS ObjectMessage
* for the given RemoteInvocation object.
* @param session the current JMS Session
* @param invocation the remote invocation to send
* @return the JMS Message to send
* @throws JMSException if the message could not be created
*/
protected Message createRequestMessage(Session session, RemoteInvocation invocation) throws JMSException {
return this.messageConverter.toMessage(invocation, session);
}
/**
* Actually execute the given request, sending the invoker request message
* to the specified target queue and waiting for a corresponding response.
* <p>The default implementation is based on standard JMS send/receive,
* using a {@link javax.jms.TemporaryQueue} for receiving the response.
* @param session the JMS Session to use
* @param queue the resolved target Queue to send to
* @param requestMessage the JMS Message to send
* @return the RemoteInvocationResult object
* @throws JMSException in case of JMS failure
*/
protected Message doExecuteRequest(Session session, Queue queue, Message requestMessage) throws JMSException {
TemporaryQueue responseQueue = null;
MessageProducer producer = null;
MessageConsumer consumer = null;
try {
if (session instanceof QueueSession) {
// Perform all calls on QueueSession reference for JMS 1.0.2 compatibility...
QueueSession queueSession = (QueueSession) session;
responseQueue = queueSession.createTemporaryQueue();
QueueSender sender = queueSession.createSender(queue);
producer = sender;
consumer = queueSession.createReceiver(responseQueue);
requestMessage.setJMSReplyTo(responseQueue);
sender.send(requestMessage);
}
else {
// Standard JMS 1.1 API usage...
responseQueue = session.createTemporaryQueue();
producer = session.createProducer(queue);
consumer = session.createConsumer(responseQueue);
requestMessage.setJMSReplyTo(responseQueue);
producer.send(requestMessage);
}
long timeout = getReceiveTimeout();
return (timeout > 0 ? consumer.receive(timeout) : consumer.receive());
}
finally {
JmsUtils.closeMessageConsumer(consumer);
JmsUtils.closeMessageProducer(producer);
if (responseQueue != null) {
responseQueue.delete();
}
}
}
/**
* Extract the invocation result from the response message.
* <p>The default implementation expects a JMS ObjectMessage carrying
* a RemoteInvocationResult object. If an invalid response message is
* encountered, the <code>onInvalidResponse</code> callback gets invoked.
* @param responseMessage the response message
* @return the invocation result
* @throws JMSException is thrown if a JMS exception occurs
* @see #onInvalidResponse
*/
protected RemoteInvocationResult extractInvocationResult(Message responseMessage) throws JMSException {
Object content = this.messageConverter.fromMessage(responseMessage);
if (content instanceof RemoteInvocationResult) {
return (RemoteInvocationResult) content;
}
return onInvalidResponse(responseMessage);
}
/**
* Callback that is invoked by <code>extractInvocationResult</code>
* when it encounters an invalid response message.
* <p>The default implementation throws a MessageFormatException.
* @param responseMessage the invalid response message
* @return an alternative invocation result that should be
* returned to the caller (if desired)
* @throws JMSException if the invalid response should lead
* to an infrastructure exception propagated to the caller
* @see #extractInvocationResult
*/
protected RemoteInvocationResult onInvalidResponse(Message responseMessage) throws JMSException {
throw new MessageFormatException("Invalid response message: " + responseMessage);
}
/**
* Recreate the invocation result contained in the given RemoteInvocationResult
* object. The default implementation calls the default recreate method.
* <p>Can be overridden in subclass to provide custom recreation, potentially
* processing the returned result object.
* @param result the RemoteInvocationResult to recreate
* @return a return value if the invocation result is a successful return
* @throws Throwable if the invocation result is an exception
* @see org.springframework.remoting.support.RemoteInvocationResult#recreate()
*/
protected Object recreateRemoteInvocationResult(RemoteInvocationResult result) throws Throwable {
return result.recreate();
}
/**
* Convert the given JMS invoker access exception to an appropriate
* Spring RemoteAccessException.
* @param ex the exception to convert
* @return the RemoteAccessException to throw
*/
protected RemoteAccessException convertJmsInvokerAccessException(JMSException ex) {
throw new RemoteAccessException("Could not access JMS invoker queue [" + this.queue + "]", ex);
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 2002-2007 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.remoting;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.util.ClassUtils;
/**
* FactoryBean for JMS invoker proxies. Exposes the proxied service for use
* as a bean reference, using the specified service interface.
*
* <p>Serializes remote invocation objects and deserializes remote invocation
* result objects. Uses Java serialization just like RMI, but with the JMS
* provider as communication infrastructure.
*
* <p>To be configured with a {@link javax.jms.QueueConnectionFactory} and a
* target queue (either as {@link javax.jms.Queue} reference or as queue name).
*
* @author Juergen Hoeller
* @since 2.0
* @see #setConnectionFactory
* @see #setQueueName
* @see #setServiceInterface
* @see org.springframework.jms.remoting.JmsInvokerClientInterceptor
* @see org.springframework.jms.remoting.JmsInvokerServiceExporter
*/
public class JmsInvokerProxyFactoryBean extends JmsInvokerClientInterceptor
implements FactoryBean, BeanClassLoaderAware {
private Class serviceInterface;
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private Object serviceProxy;
/**
* Set the interface that the proxy must implement.
* @param serviceInterface the interface that the proxy must implement
* @throws IllegalArgumentException if the supplied <code>serviceInterface</code>
* is <code>null</code>, or if the supplied <code>serviceInterface</code>
* is not an interface type
*/
public void setServiceInterface(Class serviceInterface) {
if (serviceInterface == null || !serviceInterface.isInterface()) {
throw new IllegalArgumentException("'serviceInterface' must be an interface");
}
this.serviceInterface = serviceInterface;
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
public void afterPropertiesSet() {
super.afterPropertiesSet();
if (this.serviceInterface == null) {
throw new IllegalArgumentException("Property 'serviceInterface' is required");
}
this.serviceProxy = new ProxyFactory(this.serviceInterface, this).getProxy(this.beanClassLoader);
}
public Object getObject() {
return this.serviceProxy;
}
public Class getObjectType() {
return this.serviceInterface;
}
public boolean isSingleton() {
return true;
}
}

View File

@ -0,0 +1,191 @@
/*
* Copyright 2002-2008 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.remoting;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageFormatException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jms.listener.SessionAwareMessageListener;
import org.springframework.jms.support.JmsUtils;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.SimpleMessageConverter;
import org.springframework.remoting.support.RemoteInvocation;
import org.springframework.remoting.support.RemoteInvocationBasedExporter;
import org.springframework.remoting.support.RemoteInvocationResult;
/**
* JMS message listener that exports the specified service bean as a
* JMS service endpoint, accessible via a JMS invoker proxy.
*
* <p>Note that this class implements Spring's
* {@link org.springframework.jms.listener.SessionAwareMessageListener}
* interface, since it requires access to the active JMS Session.
* Hence, this class can only be used with message listener containers
* which support the SessionAwareMessageListener interface (e.g. Spring's
* {@link org.springframework.jms.listener.DefaultMessageListenerContainer}).
*
* <p>Thanks to James Strachan for the original prototype that this
* JMS invoker mechanism was inspired by!
*
* @author Juergen Hoeller
* @author James Strachan
* @since 2.0
* @see JmsInvokerClientInterceptor
* @see JmsInvokerProxyFactoryBean
*/
public class JmsInvokerServiceExporter extends RemoteInvocationBasedExporter
implements SessionAwareMessageListener, InitializingBean {
private MessageConverter messageConverter = new SimpleMessageConverter();
private boolean ignoreInvalidRequests = true;
private Object proxy;
/**
* Specify the MessageConverter to use for turning request messages into
* {@link org.springframework.remoting.support.RemoteInvocation} objects,
* as well as {@link org.springframework.remoting.support.RemoteInvocationResult}
* objects into response messages.
* <p>Default is a {@link org.springframework.jms.support.converter.SimpleMessageConverter},
* using a standard JMS {@link javax.jms.ObjectMessage} for each invocation /
* invocation result object.
* <p>Custom implementations may generally adapt Serializables into
* special kinds of messages, or might be specifically tailored for
* translating RemoteInvocation(Result)s into specific kinds of messages.
*/
public void setMessageConverter(MessageConverter messageConverter) {
this.messageConverter = (messageConverter != null ? messageConverter : new SimpleMessageConverter());
}
/**
* Set whether invalidly formatted messages should be discarded.
* Default is "true".
* <p>Switch this flag to "false" to throw an exception back to the
* listener container. This will typically lead to redelivery of
* the message, which is usually undesirable - since the message
* content will be the same (that is, still invalid).
*/
public void setIgnoreInvalidRequests(boolean ignoreInvalidRequests) {
this.ignoreInvalidRequests = ignoreInvalidRequests;
}
public void afterPropertiesSet() {
this.proxy = getProxyForService();
}
public void onMessage(Message requestMessage, Session session) throws JMSException {
RemoteInvocation invocation = readRemoteInvocation(requestMessage);
if (invocation != null) {
RemoteInvocationResult result = invokeAndCreateResult(invocation, this.proxy);
writeRemoteInvocationResult(requestMessage, session, result);
}
}
/**
* Read a RemoteInvocation from the given JMS message.
* @param requestMessage current request message
* @return the RemoteInvocation object (or <code>null</code>
* in case of an invalid message that will simply be ignored)
* @throws javax.jms.JMSException in case of message access failure
*/
protected RemoteInvocation readRemoteInvocation(Message requestMessage) throws JMSException {
Object content = this.messageConverter.fromMessage(requestMessage);
if (content instanceof RemoteInvocation) {
return (RemoteInvocation) content;
}
return onInvalidRequest(requestMessage);
}
/**
* Send the given RemoteInvocationResult as a JMS message to the originator.
* @param requestMessage current request message
* @param session the JMS Session to use
* @param result the RemoteInvocationResult object
* @throws javax.jms.JMSException if thrown by trying to send the message
*/
protected void writeRemoteInvocationResult(
Message requestMessage, Session session, RemoteInvocationResult result) throws JMSException {
Message response = createResponseMessage(requestMessage, session, result);
MessageProducer producer = session.createProducer(requestMessage.getJMSReplyTo());
try {
producer.send(response);
}
finally {
JmsUtils.closeMessageProducer(producer);
}
}
/**
* Create the invocation result response message.
* <p>The default implementation creates a JMS ObjectMessage for the given
* RemoteInvocationResult object. It 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 request message
* @param session the JMS session to use
* @param result the invocation result
* @return the message response to send
* @throws javax.jms.JMSException if creating the messsage failed
*/
protected Message createResponseMessage(Message request, Session session, RemoteInvocationResult result)
throws JMSException {
Message response = this.messageConverter.toMessage(result, session);
String correlation = request.getJMSCorrelationID();
if (correlation == null) {
correlation = request.getJMSMessageID();
}
response.setJMSCorrelationID(correlation);
return response;
}
/**
* Callback that is invoked by {@link #readRemoteInvocation}
* when it encounters an invalid request message.
* <p>The default implementation either discards the invalid message or
* throws a MessageFormatException - according to the "ignoreInvalidRequests"
* flag, which is set to "true" (that is, discard invalid messages) by default.
* @param requestMessage the invalid request message
* @return the RemoteInvocation to expose for the invalid request (typically
* <code>null</code> in case of an invalid message that will simply be ignored)
* @throws javax.jms.JMSException in case of the invalid request supposed
* to lead to an exception (instead of ignoring it)
* @see #readRemoteInvocation
* @see #setIgnoreInvalidRequests
*/
protected RemoteInvocation onInvalidRequest(Message requestMessage) throws JMSException {
if (this.ignoreInvalidRequests) {
if (logger.isWarnEnabled()) {
logger.warn("Invalid request message will be discarded: " + requestMessage);
}
return null;
}
else {
throw new MessageFormatException("Invalid request message: " + requestMessage);
}
}
}

View File

@ -0,0 +1,11 @@
<html>
<body>
Remoting classes for transparent Java-to-Java remoting via a JMS provider.
<p>Allows the target service to be load-balanced across a number of queue
receivers, and provides a level of indirection between the client and the
service: They only need to agree on a queue name and a service interface.
</body>
</html>

View File

@ -0,0 +1,212 @@
/*
* Copyright 2002-2008 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 javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Session;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Constants;
import org.springframework.jms.JmsException;
/**
* Base class for {@link org.springframework.jms.core.JmsTemplate} and other
* JMS-accessing gateway helpers, defining common properties such as the
* JMS {@link ConnectionFactory} to operate on. The subclass
* {@link org.springframework.jms.support.destination.JmsDestinationAccessor}
* adds further, destination-related properties.
*
* <p>Not intended to be used directly.
* See {@link org.springframework.jms.core.JmsTemplate}.
*
* @author Juergen Hoeller
* @since 1.2
* @see org.springframework.jms.support.destination.JmsDestinationAccessor
* @see org.springframework.jms.core.JmsTemplate
*/
public abstract class JmsAccessor implements InitializingBean {
/** Constants instance for javax.jms.Session */
private static final Constants sessionConstants = new Constants(Session.class);
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private ConnectionFactory connectionFactory;
private boolean sessionTransacted = false;
private int sessionAcknowledgeMode = Session.AUTO_ACKNOWLEDGE;
/**
* Set the ConnectionFactory to use for obtaining JMS {@link Connection Connections}.
*/
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
/**
* Return the ConnectionFactory that this accessor uses for obtaining
* JMS {@link Connection Connections}.
*/
public ConnectionFactory getConnectionFactory() {
return this.connectionFactory;
}
/**
* Set the transaction mode that is used when creating a JMS {@link Session}.
* Default is "false".
* <p>Note that within a JTA transaction, the parameters passed to
* <code>create(Queue/Topic)Session(boolean transacted, int acknowledgeMode)</code>
* method are not taken into account. Depending on the J2EE transaction context,
* the container makes its own decisions on these values. Analogously, these
* parameters are not taken into account within a locally managed transaction
* either, since the accessor operates on an existing JMS Session in this case.
* <p>Setting this flag to "true" will use a short local JMS transaction
* when running outside of a managed transaction, and a synchronized local
* JMS transaction in case of a managed transaction (other than an XA
* transaction) being present. The latter has the effect of a local JMS
* transaction being managed alongside the main transaction (which might
* be a native JDBC transaction), with the JMS transaction committing
* right after the main transaction.
* @see javax.jms.Connection#createSession(boolean, int)
*/
public void setSessionTransacted(boolean sessionTransacted) {
this.sessionTransacted = sessionTransacted;
}
/**
* Return whether the JMS {@link Session sessions} used by this
* accessor are supposed to be transacted.
* @see #setSessionTransacted(boolean)
*/
public boolean isSessionTransacted() {
return this.sessionTransacted;
}
/**
* Set the JMS acknowledgement mode by the name of the corresponding constant
* in the JMS {@link Session} interface, e.g. "CLIENT_ACKNOWLEDGE".
* <p>If you want to use vendor-specific extensions to the acknowledgment mode,
* use {@link #setSessionAcknowledgeModeName(String)} instead.
* @param constantName the name of the {@link Session} acknowledge mode constant
* @see javax.jms.Session#AUTO_ACKNOWLEDGE
* @see javax.jms.Session#CLIENT_ACKNOWLEDGE
* @see javax.jms.Session#DUPS_OK_ACKNOWLEDGE
* @see javax.jms.Connection#createSession(boolean, int)
*/
public void setSessionAcknowledgeModeName(String constantName) {
setSessionAcknowledgeMode(sessionConstants.asNumber(constantName).intValue());
}
/**
* Set the JMS acknowledgement mode that is used when creating a JMS
* {@link Session} to send a message.
* <p>Default is {@link Session#AUTO_ACKNOWLEDGE}.
* <p>Vendor-specific extensions to the acknowledgment mode can be set here as well.
* <p>Note that that inside an EJB the parameters to
* create(Queue/Topic)Session(boolean transacted, int acknowledgeMode) method
* are not taken into account. Depending on the transaction context in the EJB,
* the container makes its own decisions on these values. See section 17.3.5
* of the EJB spec.
* @param sessionAcknowledgeMode the acknowledgement mode constant
* @see javax.jms.Session#AUTO_ACKNOWLEDGE
* @see javax.jms.Session#CLIENT_ACKNOWLEDGE
* @see javax.jms.Session#DUPS_OK_ACKNOWLEDGE
* @see javax.jms.Connection#createSession(boolean, int)
*/
public void setSessionAcknowledgeMode(int sessionAcknowledgeMode) {
this.sessionAcknowledgeMode = sessionAcknowledgeMode;
}
/**
* Return the acknowledgement mode for JMS {@link Session sessions}.
*/
public int getSessionAcknowledgeMode() {
return this.sessionAcknowledgeMode;
}
public void afterPropertiesSet() {
if (getConnectionFactory() == null) {
throw new IllegalArgumentException("Property 'connectionFactory' is required");
}
}
/**
* Convert the specified checked {@link javax.jms.JMSException JMSException} to
* a Spring runtime {@link org.springframework.jms.JmsException JmsException}
* equivalent.
* <p>The default implementation delegates to the
* {@link org.springframework.jms.support.JmsUtils#convertJmsAccessException} method.
* @param ex the original checked {@link JMSException} to convert
* @return the Spring runtime {@link JmsException} wrapping <code>ex</code>
* @see org.springframework.jms.support.JmsUtils#convertJmsAccessException
*/
protected JmsException convertJmsAccessException(JMSException ex) {
return JmsUtils.convertJmsAccessException(ex);
}
//-------------------------------------------------------------------------
// JMS 1.1 factory methods, potentially overridden for JMS 1.0.2
//-------------------------------------------------------------------------
/**
* Create a JMS Connection via this template's ConnectionFactory.
* <p>This implementation uses JMS 1.1 API.
* @return the new JMS Connection
* @throws JMSException if thrown by JMS API methods
* @see javax.jms.ConnectionFactory#createConnection()
*/
protected Connection createConnection() throws JMSException {
return getConnectionFactory().createConnection();
}
/**
* Create a JMS Session for the given Connection.
* <p>This implementation uses JMS 1.1 API.
* @param con the JMS Connection to create a Session for
* @return the new JMS Session
* @throws JMSException if thrown by JMS API methods
* @see javax.jms.Connection#createSession(boolean, int)
*/
protected Session createSession(Connection con) throws JMSException {
return con.createSession(isSessionTransacted(), getSessionAcknowledgeMode());
}
/**
* Determine whether the given Session is in client acknowledge mode.
* <p>This implementation uses JMS 1.1 API.
* @param session the JMS Session to check
* @return whether the given Session is in client acknowledge mode
* @throws javax.jms.JMSException if thrown by JMS API methods
* @see javax.jms.Session#getAcknowledgeMode()
* @see javax.jms.Session#CLIENT_ACKNOWLEDGE
*/
protected boolean isClientAcknowledge(Session session) throws JMSException {
return (session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE);
}
}

View File

@ -0,0 +1,311 @@
/*
* Copyright 2002-2008 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 javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.QueueBrowser;
import javax.jms.QueueRequestor;
import javax.jms.Session;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jms.InvalidClientIDException;
import org.springframework.jms.InvalidDestinationException;
import org.springframework.jms.InvalidSelectorException;
import org.springframework.jms.JmsException;
import org.springframework.jms.JmsSecurityException;
import org.springframework.jms.MessageEOFException;
import org.springframework.jms.MessageFormatException;
import org.springframework.jms.MessageNotReadableException;
import org.springframework.jms.MessageNotWriteableException;
import org.springframework.jms.ResourceAllocationException;
import org.springframework.jms.TransactionInProgressException;
import org.springframework.jms.TransactionRolledBackException;
import org.springframework.jms.UncategorizedJmsException;
import org.springframework.util.Assert;
/**
* Generic utility methods for working with JMS. Mainly for internal use
* within the framework, but also useful for custom JMS access code.
*
* @author Juergen Hoeller
* @since 1.1
*/
public abstract class JmsUtils {
private static final Log logger = LogFactory.getLog(JmsUtils.class);
/**
* Close the given JMS Connection and ignore any thrown exception.
* This is useful for typical <code>finally</code> blocks in manual JMS code.
* @param con the JMS Connection to close (may be <code>null</code>)
*/
public static void closeConnection(Connection con) {
closeConnection(con, false);
}
/**
* Close the given JMS Connection and ignore any thrown exception.
* This is useful for typical <code>finally</code> blocks in manual JMS code.
* @param con the JMS Connection to close (may be <code>null</code>)
* @param stop whether to call <code>stop()</code> before closing
*/
public static void closeConnection(Connection con, boolean stop) {
if (con != null) {
try {
if (stop) {
try {
con.stop();
}
finally {
con.close();
}
}
else {
con.close();
}
}
catch (javax.jms.IllegalStateException ex) {
logger.debug("Ignoring Connection state exception - assuming already closed: " + ex);
}
catch (JMSException ex) {
logger.debug("Could not close JMS Connection", ex);
}
catch (Throwable ex) {
// We don't trust the JMS provider: It might throw RuntimeException or Error.
logger.debug("Unexpected exception on closing JMS Connection", ex);
}
}
}
/**
* Close the given JMS Session and ignore any thrown exception.
* This is useful for typical <code>finally</code> blocks in manual JMS code.
* @param session the JMS Session to close (may be <code>null</code>)
*/
public static void closeSession(Session session) {
if (session != null) {
try {
session.close();
}
catch (JMSException ex) {
logger.trace("Could not close JMS Session", ex);
}
catch (Throwable ex) {
// We don't trust the JMS provider: It might throw RuntimeException or Error.
logger.trace("Unexpected exception on closing JMS Session", ex);
}
}
}
/**
* Close the given JMS MessageProducer and ignore any thrown exception.
* This is useful for typical <code>finally</code> blocks in manual JMS code.
* @param producer the JMS MessageProducer to close (may be <code>null</code>)
*/
public static void closeMessageProducer(MessageProducer producer) {
if (producer != null) {
try {
producer.close();
}
catch (JMSException ex) {
logger.trace("Could not close JMS MessageProducer", ex);
}
catch (Throwable ex) {
// We don't trust the JMS provider: It might throw RuntimeException or Error.
logger.trace("Unexpected exception on closing JMS MessageProducer", ex);
}
}
}
/**
* Close the given JMS MessageConsumer and ignore any thrown exception.
* This is useful for typical <code>finally</code> blocks in manual JMS code.
* @param consumer the JMS MessageConsumer to close (may be <code>null</code>)
*/
public static void closeMessageConsumer(MessageConsumer consumer) {
if (consumer != null) {
// Clear interruptions to ensure that the consumer closes successfully...
// (working around misbehaving JMS providers such as ActiveMQ)
boolean wasInterrupted = Thread.interrupted();
try {
consumer.close();
}
catch (JMSException ex) {
logger.trace("Could not close JMS MessageConsumer", ex);
}
catch (Throwable ex) {
// We don't trust the JMS provider: It might throw RuntimeException or Error.
logger.trace("Unexpected exception on closing JMS MessageConsumer", ex);
}
finally {
if (wasInterrupted) {
// Reset the interrupted flag as it was before.
Thread.currentThread().interrupt();
}
}
}
}
/**
* Close the given JMS QueueBrowser and ignore any thrown exception.
* This is useful for typical <code>finally</code> blocks in manual JMS code.
* @param browser the JMS QueueBrowser to close (may be <code>null</code>)
*/
public static void closeQueueBrowser(QueueBrowser browser) {
if (browser != null) {
try {
browser.close();
}
catch (JMSException ex) {
logger.trace("Could not close JMS QueueBrowser", ex);
}
catch (Throwable ex) {
// We don't trust the JMS provider: It might throw RuntimeException or Error.
logger.trace("Unexpected exception on closing JMS QueueBrowser", ex);
}
}
}
/**
* Close the given JMS QueueRequestor and ignore any thrown exception.
* This is useful for typical <code>finally</code> blocks in manual JMS code.
* @param requestor the JMS QueueRequestor to close (may be <code>null</code>)
*/
public static void closeQueueRequestor(QueueRequestor requestor) {
if (requestor != null) {
try {
requestor.close();
}
catch (JMSException ex) {
logger.trace("Could not close JMS QueueRequestor", ex);
}
catch (Throwable ex) {
// We don't trust the JMS provider: It might throw RuntimeException or Error.
logger.trace("Unexpected exception on closing JMS QueueRequestor", ex);
}
}
}
/**
* Commit the Session if not within a JTA transaction.
* @param session the JMS Session to commit
* @throws JMSException if committing failed
*/
public static void commitIfNecessary(Session session) throws JMSException {
Assert.notNull(session, "Session must not be null");
try {
session.commit();
}
catch (javax.jms.TransactionInProgressException ex) {
// Ignore -> can only happen in case of a JTA transaction.
}
catch (javax.jms.IllegalStateException ex) {
// Ignore -> can only happen in case of a JTA transaction.
}
}
/**
* Rollback the Session if not within a JTA transaction.
* @param session the JMS Session to rollback
* @throws JMSException if committing failed
*/
public static void rollbackIfNecessary(Session session) throws JMSException {
Assert.notNull(session, "Session must not be null");
try {
session.rollback();
}
catch (javax.jms.TransactionInProgressException ex) {
// Ignore -> can only happen in case of a JTA transaction.
}
catch (javax.jms.IllegalStateException ex) {
// Ignore -> can only happen in case of a JTA transaction.
}
}
/**
* Build a descriptive exception message for the given JMSException,
* incorporating a linked exception's message if appropriate.
* @param ex the JMSException to build a message for
* @return the descriptive message String
* @see javax.jms.JMSException#getLinkedException()
*/
public static String buildExceptionMessage(JMSException ex) {
String message = ex.getMessage();
Exception linkedEx = ex.getLinkedException();
if (linkedEx != null && message.indexOf(linkedEx.getMessage()) == -1) {
message = message + "; nested exception is " + linkedEx;
}
return message;
}
/**
* Convert the specified checked {@link javax.jms.JMSException JMSException} to a
* Spring runtime {@link org.springframework.jms.JmsException JmsException} equivalent.
* @param ex the original checked JMSException to convert
* @return the Spring runtime JmsException wrapping the given exception
*/
public static JmsException convertJmsAccessException(JMSException ex) {
Assert.notNull(ex, "JMSException must not be null");
if (ex instanceof javax.jms.IllegalStateException) {
return new org.springframework.jms.IllegalStateException((javax.jms.IllegalStateException) ex);
}
if (ex instanceof javax.jms.InvalidClientIDException) {
return new InvalidClientIDException((javax.jms.InvalidClientIDException) ex);
}
if (ex instanceof javax.jms.InvalidDestinationException) {
return new InvalidDestinationException((javax.jms.InvalidDestinationException) ex);
}
if (ex instanceof javax.jms.InvalidSelectorException) {
return new InvalidSelectorException((javax.jms.InvalidSelectorException) ex);
}
if (ex instanceof javax.jms.JMSSecurityException) {
return new JmsSecurityException((javax.jms.JMSSecurityException) ex);
}
if (ex instanceof javax.jms.MessageEOFException) {
return new MessageEOFException((javax.jms.MessageEOFException) ex);
}
if (ex instanceof javax.jms.MessageFormatException) {
return new MessageFormatException((javax.jms.MessageFormatException) ex);
}
if (ex instanceof javax.jms.MessageNotReadableException) {
return new MessageNotReadableException((javax.jms.MessageNotReadableException) ex);
}
if (ex instanceof javax.jms.MessageNotWriteableException) {
return new MessageNotWriteableException((javax.jms.MessageNotWriteableException) ex);
}
if (ex instanceof javax.jms.ResourceAllocationException) {
return new ResourceAllocationException((javax.jms.ResourceAllocationException) ex);
}
if (ex instanceof javax.jms.TransactionInProgressException) {
return new TransactionInProgressException((javax.jms.TransactionInProgressException) ex);
}
if (ex instanceof javax.jms.TransactionRolledBackException) {
return new TransactionRolledBackException((javax.jms.TransactionRolledBackException) ex);
}
// fallback
return new UncategorizedJmsException(ex);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2002-2006 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 org.springframework.jms.JmsException;
/**
* Thrown by {@link MessageConverter} implementations when the conversion
* of an object to/from a {@link javax.jms.Message} fails.
*
* @author Mark Pollack
* @since 1.1
* @see MessageConverter
*/
public class MessageConversionException extends JmsException {
/**
* Create a new MessageConversionException.
* @param msg the detail message
*/
public MessageConversionException(String msg) {
super(msg);
}
/**
* Create a new MessageConversionException.
* @param msg the detail message
* @param cause the root cause (if any)
*/
public MessageConversionException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2002-2007 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.JMSException;
import javax.jms.Message;
import javax.jms.Session;
/**
* Strategy interface that specifies a converter between Java objects and JMS messages.
*
* <p>Check out {@link SimpleMessageConverter} for a default implementation,
* converting between the 'standard' message payloads and JMS Message types.
*
* @author Mark Pollack
* @author Juergen Hoeller
* @since 1.1
* @see org.springframework.jms.core.JmsTemplate#setMessageConverter
* @see org.springframework.jms.listener.adapter.MessageListenerAdapter#setMessageConverter
* @see org.springframework.jms.remoting.JmsInvokerClientInterceptor#setMessageConverter
* @see org.springframework.jms.remoting.JmsInvokerServiceExporter#setMessageConverter
*/
public interface MessageConverter {
/**
* Convert a Java object to a JMS Message using the supplied session
* to create the message object.
* @param object the object to convert
* @param session the Session to use for creating a JMS Message
* @return the JMS Message
* @throws javax.jms.JMSException if thrown by JMS API methods
* @throws MessageConversionException in case of conversion failure
*/
Message toMessage(Object object, Session session) throws JMSException, MessageConversionException;
/**
* Convert from a JMS Message to a Java object.
* @param message the message to convert
* @return the converted Java object
* @throws javax.jms.JMSException if thrown by JMS API methods
* @throws MessageConversionException in case of conversion failure
*/
Object fromMessage(Message message) throws JMSException, MessageConversionException;
}

View File

@ -0,0 +1,227 @@
/*
* Copyright 2002-2008 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.io.Serializable;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.jms.BytesMessage;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.springframework.util.ObjectUtils;
/**
* A simple message converter which is able to handle TextMessages, BytesMessages,
* MapMessages, and ObjectMessages. Used as default conversion strategy
* by {@link org.springframework.jms.core.JmsTemplate}, for
* <code>convertAndSend</code> and <code>receiveAndConvert</code> operations.
*
* <p>Converts a String to a {@link javax.jms.TextMessage}, a byte array to a
* {@link javax.jms.BytesMessage}, a Map to a {@link javax.jms.MapMessage}, and
* a Serializable object to a {@link javax.jms.ObjectMessage} (or vice versa).
*
* <p>This converter implementation works for both JMS 1.1 and JMS 1.0.2,
* except when extracting a byte array from a BytesMessage. So for converting
* BytesMessages with a JMS 1.0.2 provider, use {@link SimpleMessageConverter102}.
* (As you would expect, {@link org.springframework.jms.core.JmsTemplate102}
* uses SimpleMessageConverter102 as default.)
*
* @author Juergen Hoeller
* @since 1.1
* @see org.springframework.jms.core.JmsTemplate#convertAndSend
* @see org.springframework.jms.core.JmsTemplate#receiveAndConvert
*/
public class SimpleMessageConverter implements MessageConverter {
/**
* This implementation creates a TextMessage for a String, a
* BytesMessage for a byte array, a MapMessage for a Map,
* and an ObjectMessage for a Serializable object.
* @see #createMessageForString
* @see #createMessageForByteArray
* @see #createMessageForMap
* @see #createMessageForSerializable
*/
public Message toMessage(Object object, Session session) throws JMSException, MessageConversionException {
if (object instanceof Message) {
return (Message) object;
}
else if (object instanceof String) {
return createMessageForString((String) object, session);
}
else if (object instanceof byte[]) {
return createMessageForByteArray((byte[]) object, session);
}
else if (object instanceof Map) {
return createMessageForMap((Map) object, session);
}
else if (object instanceof Serializable) {
return createMessageForSerializable(((Serializable) object), session);
}
else {
throw new MessageConversionException("Cannot convert object of type [" +
ObjectUtils.nullSafeClassName(object) + "] to JMS message. Supported message " +
"payloads are: String, byte array, Map<String,?>, Serializable object.");
}
}
/**
* This implementation converts a TextMessage back to a String, a
* ByteMessage back to a byte array, a MapMessage back to a Map,
* and an ObjectMessage back to a Serializable object. Returns
* the plain Message object in case of an unknown message type.
* @see #extractStringFromMessage
* @see #extractByteArrayFromMessage
* @see #extractMapFromMessage
* @see #extractSerializableFromMessage
*/
public Object fromMessage(Message message) throws JMSException, MessageConversionException {
if (message instanceof TextMessage) {
return extractStringFromMessage((TextMessage) message);
}
else if (message instanceof BytesMessage) {
return extractByteArrayFromMessage((BytesMessage) message);
}
else if (message instanceof MapMessage) {
return extractMapFromMessage((MapMessage) message);
}
else if (message instanceof ObjectMessage) {
return extractSerializableFromMessage((ObjectMessage) message);
}
else {
return message;
}
}
/**
* Create a JMS TextMessage for the given String.
* @param text the String to convert
* @param session current JMS session
* @return the resulting message
* @throws JMSException if thrown by JMS methods
* @see javax.jms.Session#createTextMessage
*/
protected TextMessage createMessageForString(String text, Session session) throws JMSException {
return session.createTextMessage(text);
}
/**
* Create a JMS BytesMessage for the given byte array.
* @param bytes the byyte array to convert
* @param session current JMS session
* @return the resulting message
* @throws JMSException if thrown by JMS methods
* @see javax.jms.Session#createBytesMessage
*/
protected BytesMessage createMessageForByteArray(byte[] bytes, Session session) throws JMSException {
BytesMessage message = session.createBytesMessage();
message.writeBytes(bytes);
return message;
}
/**
* Create a JMS MapMessage for the given Map.
* @param map the Map to convert
* @param session current JMS session
* @return the resulting message
* @throws JMSException if thrown by JMS methods
* @see javax.jms.Session#createMapMessage
*/
protected MapMessage createMessageForMap(Map map, Session session) throws JMSException {
MapMessage message = session.createMapMessage();
for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
if (!(entry.getKey() instanceof String)) {
throw new MessageConversionException("Cannot convert non-String key of type [" +
ObjectUtils.nullSafeClassName(entry.getKey()) + "] to JMS MapMessage entry");
}
message.setObject((String) entry.getKey(), entry.getValue());
}
return message;
}
/**
* Create a JMS ObjectMessage for the given Serializable object.
* @param object the Serializable object to convert
* @param session current JMS session
* @return the resulting message
* @throws JMSException if thrown by JMS methods
* @see javax.jms.Session#createObjectMessage
*/
protected ObjectMessage createMessageForSerializable(Serializable object, Session session) throws JMSException {
return session.createObjectMessage(object);
}
/**
* Extract a String from the given TextMessage.
* @param message the message to convert
* @return the resulting String
* @throws JMSException if thrown by JMS methods
*/
protected String extractStringFromMessage(TextMessage message) throws JMSException {
return message.getText();
}
/**
* Extract a byte array from the given {@link BytesMessage}.
* @param message the message to convert
* @return the resulting byte array
* @throws JMSException if thrown by JMS methods
*/
protected byte[] extractByteArrayFromMessage(BytesMessage message) throws JMSException {
byte[] bytes = new byte[(int) message.getBodyLength()];
message.readBytes(bytes);
return bytes;
}
/**
* Extract a Map from the given {@link MapMessage}.
* @param message the message to convert
* @return the resulting Map
* @throws JMSException if thrown by JMS methods
*/
protected Map extractMapFromMessage(MapMessage message) throws JMSException {
Map map = new HashMap();
Enumeration en = message.getMapNames();
while (en.hasMoreElements()) {
String key = (String) en.nextElement();
map.put(key, message.getObject(key));
}
return map;
}
/**
* Extract a Serializable object from the given {@link ObjectMessage}.
* @param message the message to convert
* @return the resulting Serializable object
* @throws JMSException if thrown by JMS methods
*/
protected Serializable extractSerializableFromMessage(ObjectMessage message) throws JMSException {
return message.getObject();
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2002-2007 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.io.ByteArrayOutputStream;
import javax.jms.BytesMessage;
import javax.jms.JMSException;
/**
* A subclass of {@link SimpleMessageConverter} for the JMS 1.0.2 specification,
* not relying on JMS 1.1 methods like SimpleMessageConverter itself.
* This class can be used for JMS 1.0.2 providers, offering the same functionality
* as SimpleMessageConverter does for JMS 1.1 providers.
*
* <p>The only difference to the default SimpleMessageConverter is that BytesMessage
* is handled differently: namely, without using the <code>getBodyLength()</code>
* method which has been introduced in JMS 1.1 and is therefore not available on a
* JMS 1.0.2 provider.
*
* @author Juergen Hoeller
* @since 1.1.1
* @see javax.jms.BytesMessage#getBodyLength()
*/
public class SimpleMessageConverter102 extends SimpleMessageConverter {
public static final int BUFFER_SIZE = 4096;
/**
* Overrides superclass method to copy bytes from the message into a
* ByteArrayOutputStream, using a buffer, to avoid using the
* <code>getBodyLength()</code> method which has been introduced in
* JMS 1.1 and is therefore not available on a JMS 1.0.2 provider.
* @see javax.jms.BytesMessage#getBodyLength()
*/
protected byte[] extractByteArrayFromMessage(BytesMessage message) throws JMSException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(BUFFER_SIZE);
byte[] buffer = new byte[BUFFER_SIZE];
int bufferCount = -1;
while ((bufferCount = message.readBytes(buffer)) >= 0) {
baos.write(buffer, 0, bufferCount);
if (bufferCount < BUFFER_SIZE) {
break;
}
}
return baos.toByteArray();
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Provides a MessageConverter abstraction to convert
between Java objects and JMS messages.
</body>
</html>

View File

@ -0,0 +1,84 @@
/*
* Copyright 2002-2007 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.destination;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Session;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.util.Assert;
/**
* {@link DestinationResolver} implementation based on a Spring {@link BeanFactory}.
*
* <p>Will lookup Spring managed beans identified by bean name,
* expecting them to be of type <code>javax.jms.Destination</code>.
*
* @author Juergen Hoeller
* @since 2.5
* @see org.springframework.beans.factory.BeanFactory
*/
public class BeanFactoryDestinationResolver implements DestinationResolver, BeanFactoryAware {
private BeanFactory beanFactory;
/**
* Create a new instance of the {@link BeanFactoryDestinationResolver} class.
* <p>The BeanFactory to access must be set via <code>setBeanFactory</code>.
* @see #setBeanFactory
*/
public BeanFactoryDestinationResolver() {
}
/**
* Create a new instance of the {@link BeanFactoryDestinationResolver} class.
* <p>Use of this constructor is redundant if this object is being created
* by a Spring IoC container, as the supplied {@link BeanFactory} will be
* replaced by the {@link BeanFactory} that creates it (c.f. the
* {@link BeanFactoryAware} contract). So only use this constructor if you
* are using this class outside the context of a Spring IoC container.
* @param beanFactory the bean factory to be used to lookup {@link javax.jms.Destination Destinatiosn}
*/
public BeanFactoryDestinationResolver(BeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory is required");
this.beanFactory = beanFactory;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public Destination resolveDestinationName(Session session, String destinationName, boolean pubSubDomain)
throws JMSException {
Assert.state(this.beanFactory != null, "BeanFactory is required");
try {
return (Destination) this.beanFactory.getBean(destinationName, Destination.class);
}
catch (BeansException ex) {
throw new DestinationResolutionException(
"Failed to look up Destinaton bean with name '" + destinationName + "'", ex);
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2002-2006 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.destination;
/**
* Extension of the DestinationResolver interface,
* exposing methods for clearing the cache.
*
* @author Juergen Hoeller
* @since 2.0
*/
public interface CachingDestinationResolver extends DestinationResolver {
/**
* Remove the destination with the given name from the cache
* (if cached by this resolver in the first place).
* <p>To be called if access to the specified destination failed,
* assuming that the JMS Destination object might have become invalid.
* @param destinationName the name of the destination
*/
void removeFromCache(String destinationName);
/**
* Clear the entire destination cache.
* <p>To be called in case of general JMS provider failure.
*/
void clearCache();
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2002-2006 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.destination;
import org.springframework.jms.JmsException;
/**
* Thrown by a DestinationResolver when it cannot resolve a destination name.
*
* @author Juergen Hoeller
* @since 1.1
* @see DestinationResolver
*/
public class DestinationResolutionException extends JmsException {
/**
* Create a new DestinationResolutionException.
* @param msg the detail message
*/
public DestinationResolutionException(String msg) {
super(msg);
}
/**
* Create a new DestinationResolutionException.
* @param msg the detail message
* @param cause the root cause (if any)
*/
public DestinationResolutionException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2002-2007 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.destination;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Session;
/**
* Strategy interface for resolving JMS destinations.
*
* <p>Used by {@link org.springframework.jms.core.JmsTemplate} for resolving
* destination names from simple {@link String Strings} to actual
* {@link Destination} implementation instances.
*
* <p>The default {@link DestinationResolver} implementation used by
* {@link org.springframework.jms.core.JmsTemplate} instances is the
* {@link DynamicDestinationResolver} class. Consider using the
* {@link JndiDestinationResolver} for more advanced scenarios.
*
* @author Juergen Hoeller
* @since 1.1
* @see org.springframework.jms.core.JmsTemplate#setDestinationResolver
* @see org.springframework.jms.support.destination.DynamicDestinationResolver
* @see org.springframework.jms.support.destination.JndiDestinationResolver
*/
public interface DestinationResolver {
/**
* Resolve the given destination name, either as located resource
* or as dynamic destination.
* @param session the current JMS Session
* (may be <code>null</code> if the resolver implementation is able to work without it)
* @param destinationName the name of the destination
* @param pubSubDomain <code>true</code> if the domain is pub-sub, <code>false</code> if P2P
* @return the JMS destination (either a topic or a queue)
* @throws javax.jms.JMSException if the JMS Session failed to resolve the destination
* @throws DestinationResolutionException in case of general destination resolution failure
*/
Destination resolveDestinationName(Session session, String destinationName, boolean pubSubDomain)
throws JMSException;
}

View File

@ -0,0 +1,109 @@
/*
* Copyright 2002-2006 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.destination;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicSession;
import org.springframework.util.Assert;
/**
* Simple {@link DestinationResolver} implementation resolving destination names
* as dynamic destinations.
*
* <p>This implementation will work on both JMS 1.1 and JMS 1.0.2,
* because it uses the {@link javax.jms.QueueSession} or {@link javax.jms.TopicSession}
* methods if possible, falling back to JMS 1.1's generic {@link javax.jms.Session}
* methods.
*
* @author Juergen Hoeller
* @since 1.1
* @see javax.jms.QueueSession#createQueue
* @see javax.jms.TopicSession#createTopic
* @see javax.jms.Session#createQueue
* @see javax.jms.Session#createTopic
*/
public class DynamicDestinationResolver implements DestinationResolver {
/**
* Resolve the specified destination name as a dynamic destination.
* @param session the current JMS Session
* @param destinationName the name of the destination
* @param pubSubDomain <code>true</code> if the domain is pub-sub, <code>false</code> if P2P
* @return the JMS destination (either a topic or a queue)
* @throws javax.jms.JMSException if resolution failed
* @see #resolveTopic(javax.jms.Session, String)
* @see #resolveQueue(javax.jms.Session, String)
*/
public Destination resolveDestinationName(Session session, String destinationName, boolean pubSubDomain)
throws JMSException {
Assert.notNull(session, "Session must not be null");
Assert.notNull(destinationName, "Destination name must not be null");
if (pubSubDomain) {
return resolveTopic(session, destinationName);
}
else {
return resolveQueue(session, destinationName);
}
}
/**
* Resolve the given destination name to a {@link Topic}.
* @param session the current JMS Session
* @param topicName the name of the desired {@link Topic}
* @return the JMS {@link Topic}
* @throws javax.jms.JMSException if resolution failed
* @see Session#createTopic(String)
*/
protected Topic resolveTopic(Session session, String topicName) throws JMSException {
if (session instanceof TopicSession) {
// Cast to TopicSession: will work on both JMS 1.1 and 1.0.2
return ((TopicSession) session).createTopic(topicName);
}
else {
// Fall back to generic JMS Session: will only work on JMS 1.1
return session.createTopic(topicName);
}
}
/**
* Resolve the given destination name to a {@link Queue}.
* @param session the current JMS Session
* @param queueName the name of the desired {@link Queue}
* @return the JMS {@link Queue}
* @throws javax.jms.JMSException if resolution failed
* @see Session#createQueue(String)
*/
protected Queue resolveQueue(Session session, String queueName) throws JMSException {
if (session instanceof QueueSession) {
// Cast to QueueSession: will work on both JMS 1.1 and 1.0.2
return ((QueueSession) session).createQueue(queueName);
}
else {
// Fall back to generic JMS Session: will only work on JMS 1.1
return session.createQueue(queueName);
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright 2002-2007 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.destination;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Session;
import org.springframework.jms.support.JmsAccessor;
import org.springframework.util.Assert;
/**
* Base class for {@link org.springframework.jms.core.JmsTemplate} and other
* JMS-accessing gateway helpers, adding destination-related properties to
* {@link JmsAccessor JmsAccessor's} common properties.
*
* <p>Not intended to be used directly.
* See {@link org.springframework.jms.core.JmsTemplate}.
*
* @author Juergen Hoeller
* @since 1.2.5
* @see org.springframework.jms.support.JmsAccessor
* @see org.springframework.jms.core.JmsTemplate
*/
public abstract class JmsDestinationAccessor extends JmsAccessor {
private DestinationResolver destinationResolver = new DynamicDestinationResolver();
private boolean pubSubDomain = false;
/**
* Set the {@link DestinationResolver} that is to be used to resolve
* {@link javax.jms.Destination} references for this accessor.
* <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 accessor (never <code>null</code>).
*/
public DestinationResolver getDestinationResolver() {
return this.destinationResolver;
}
/**
* Configure the destination accessor with knowledge of the JMS domain used.
* Default is Point-to-Point (Queues).
* <p>For JMS 1.0.2 based accessors, this tells the JMS provider which class hierarchy
* to use in the implementation of its operations. For JMS 1.1 based accessors, this
* setting does usually not affect operations. However, for both JMS versions, this
* setting tells what type of destination to resolve if dynamic destinations are enabled.
* @param pubSubDomain "true" for the Publish/Subscribe domain ({@link javax.jms.Topic Topics}),
* "false" for the Point-to-Point domain ({@link javax.jms.Queue Queues})
* @see #setDestinationResolver
*/
public void setPubSubDomain(boolean pubSubDomain) {
this.pubSubDomain = pubSubDomain;
}
/**
* Return whether the Publish/Subscribe domain ({@link javax.jms.Topic Topics}) is used.
* Otherwise, the Point-to-Point domain ({@link javax.jms.Queue Queues}) is used.
*/
public boolean isPubSubDomain() {
return this.pubSubDomain;
}
/**
* Resolve the given destination name into a JMS {@link Destination},
* via this accessor's {@link DestinationResolver}.
* @param session the current JMS {@link Session}
* @param destinationName the name of the destination
* @return the located {@link Destination}
* @throws javax.jms.JMSException if resolution failed
* @see #setDestinationResolver
*/
protected Destination resolveDestinationName(Session session, String destinationName) throws JMSException {
return getDestinationResolver().resolveDestinationName(session, destinationName, isPubSubDomain());
}
}

Some files were not shown because too many files have changed in this diff Show More