Rename modules {org.springframework.*=>spring-*}

This renaming more intuitively expresses the relationship between
subprojects and the JAR artifacts they produce.

Tracking history across these renames is possible, but it requires
use of the --follow flag to `git log`, for example

    $ git log spring-aop/src/main/java/org/springframework/aop/Advisor.java

will show history up until the renaming event, where

    $ git log --follow spring-aop/src/main/java/org/springframework/aop/Advisor.java

will show history for all changes to the file, before and after the
renaming.

See http://chrisbeams.com/git-diff-across-renamed-directories
This commit is contained in:
Chris Beams
2012-01-20 22:51:02 +01:00
parent b6cb514d38
commit 02a4473c62
5671 changed files with 20 additions and 32 deletions

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,98 @@
/*
* Copyright 2002-2009 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) {
String linkedMessage = linkedEx.getMessage();
String causeMessage = cause.getMessage();
if (linkedMessage != null && (causeMessage == null || !causeMessage.contains(linkedMessage))) {
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,273 @@
/*
* Copyright 2002-2010 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.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;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* 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 PHASE_ATTRIBUTE = "phase";
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 = parserContext.getDelegate().getLocalName(child);
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);
}
else {
listenerDef.getPropertyValues().add("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().add("defaultListenerMethod", method);
if (containerEle.hasAttribute(MESSAGE_CONVERTER_ATTRIBUTE)) {
String messageConverter = containerEle.getAttribute(MESSAGE_CONVERTER_ATTRIBUTE);
if (!StringUtils.hasText(messageConverter)) {
parserContext.getReaderContext().error(
"Listener container 'message-converter' attribute contains empty value.", containerEle);
}
else {
listenerDef.getPropertyValues().add("messageConverter",
new RuntimeBeanReference(messageConverter));
}
}
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().add(
pubSubDomain ? "defaultResponseTopicName" : "defaultResponseQueueName", responseDestination);
if (containerDef.getPropertyValues().contains("destinationResolver")) {
listenerDef.getPropertyValues().add("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().add("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().add("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().add("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().add("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().add("pubSubDomain", pubSubDomain);
configDef.getPropertyValues().add("subscriptionDurable", 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().add("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 acknowledgeMode;
}
else {
return null;
}
}
protected boolean indicatesPubSubConfig(BeanDefinition configDef) {
return (Boolean) configDef.getPropertyValues().getPropertyValue("pubSubDomain").getValue();
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2002-2010 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");
if (containerEle.hasAttribute(RESOURCE_ADAPTER_ATTRIBUTE)) {
String resourceAdapterBeanName = containerEle.getAttribute(RESOURCE_ADAPTER_ATTRIBUTE);
if (!StringUtils.hasText(resourceAdapterBeanName)) {
parserContext.getReaderContext().error(
"Listener container 'resource-adapter' attribute contains empty value.", containerEle);
}
else {
containerDef.getPropertyValues().add("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().add("activationSpecFactory",
new RuntimeBeanReference(activationSpecFactoryBeanName));
}
if (StringUtils.hasText(destinationResolverBeanName)) {
containerDef.getPropertyValues().add("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().add("acknowledgeMode", acknowledgeMode);
}
String transactionManagerBeanName = containerEle.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE);
if (StringUtils.hasText(transactionManagerBeanName)) {
containerDef.getPropertyValues().add("transactionManager",
new RuntimeBeanReference(transactionManagerBeanName));
}
String concurrency = containerEle.getAttribute(CONCURRENCY_ATTRIBUTE);
if (StringUtils.hasText(concurrency)) {
configDef.getPropertyValues().add("concurrency", concurrency);
}
String prefetch = containerEle.getAttribute(PREFETCH_ATTRIBUTE);
if (StringUtils.hasText(prefetch)) {
configDef.getPropertyValues().add("prefetchSize", new Integer(prefetch));
}
String phase = containerEle.getAttribute(PHASE_ATTRIBUTE);
if (StringUtils.hasText(phase)) {
containerDef.getPropertyValues().add("phase", phase);
}
containerDef.getPropertyValues().add("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,175 @@
/*
* Copyright 2002-2010 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 ERROR_HANDLER_ATTRIBUTE = "error-handler";
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);
}
}
if (StringUtils.hasText(connectionFactoryBeanName)) {
containerDef.getPropertyValues().add("connectionFactory",
new RuntimeBeanReference(connectionFactoryBeanName));
}
String taskExecutorBeanName = containerEle.getAttribute(TASK_EXECUTOR_ATTRIBUTE);
if (StringUtils.hasText(taskExecutorBeanName)) {
containerDef.getPropertyValues().add("taskExecutor",
new RuntimeBeanReference(taskExecutorBeanName));
}
String errorHandlerBeanName = containerEle.getAttribute(ERROR_HANDLER_ATTRIBUTE);
if (StringUtils.hasText(errorHandlerBeanName)) {
containerDef.getPropertyValues().add("errorHandler",
new RuntimeBeanReference(errorHandlerBeanName));
}
String destinationResolverBeanName = containerEle.getAttribute(DESTINATION_RESOLVER_ATTRIBUTE);
if (StringUtils.hasText(destinationResolverBeanName)) {
containerDef.getPropertyValues().add("destinationResolver",
new RuntimeBeanReference(destinationResolverBeanName));
}
String cache = containerEle.getAttribute(CACHE_ATTRIBUTE);
if (StringUtils.hasText(cache)) {
if (containerType.startsWith("simple")) {
if (!("auto".equals(cache) || "consumer".equals(cache))) {
parserContext.getReaderContext().warning(
"'cache' attribute not actively supported for listener container of type \"simple\". " +
"Effective runtime behavior will be equivalent to \"consumer\" / \"auto\".", containerEle);
}
}
else {
containerDef.getPropertyValues().add("cacheLevelName", "CACHE_" + cache.toUpperCase());
}
}
Integer acknowledgeMode = parseAcknowledgeMode(containerEle, parserContext);
if (acknowledgeMode != null) {
if (acknowledgeMode == Session.SESSION_TRANSACTED) {
containerDef.getPropertyValues().add("sessionTransacted", Boolean.TRUE);
}
else {
containerDef.getPropertyValues().add("sessionAcknowledgeMode", acknowledgeMode);
}
}
String transactionManagerBeanName = containerEle.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE);
if (StringUtils.hasText(transactionManagerBeanName)) {
if (containerType.startsWith("simple")) {
parserContext.getReaderContext().error(
"'transaction-manager' attribute not supported for listener container of type \"simple\".", containerEle);
}
else {
containerDef.getPropertyValues().add("transactionManager",
new RuntimeBeanReference(transactionManagerBeanName));
}
}
String concurrency = containerEle.getAttribute(CONCURRENCY_ATTRIBUTE);
if (StringUtils.hasText(concurrency)) {
containerDef.getPropertyValues().add("concurrency", concurrency);
}
String prefetch = containerEle.getAttribute(PREFETCH_ATTRIBUTE);
if (StringUtils.hasText(prefetch)) {
if (containerType.startsWith("default")) {
containerDef.getPropertyValues().add("maxMessagesPerTask", new Integer(prefetch));
}
}
String phase = containerEle.getAttribute(PHASE_ATTRIBUTE);
if (StringUtils.hasText(phase)) {
containerDef.getPropertyValues().add("phase", phase);
}
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,9 @@
/**
*
* Support package for declarative messaging configuration,
* with XML schema being the primary configuration format.
*
*/
package org.springframework.jms.config;

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,525 @@
/*
* Copyright 2002-2012 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.TemporaryQueue;
import javax.jms.TemporaryTopic;
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<Integer, LinkedList<Session>> cachedSessions =
new HashMap<Integer, LinkedList<Session>>();
/**
* 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 (LinkedList<Session> sessionList : this.cachedSessions.values()) {
synchronized (sessionList) {
for (Session session : sessionList) {
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<Session> sessionList;
synchronized (this.cachedSessions) {
sessionList = this.cachedSessions.get(mode);
if (sessionList == null) {
sessionList = new LinkedList<Session>();
this.cachedSessions.put(mode, sessionList);
}
}
Session session = null;
synchronized (sessionList) {
if (!sessionList.isEmpty()) {
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<Session> sessionList) {
List<Class> classes = new ArrayList<Class>(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(),
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<Session> sessionList;
private final Map<DestinationCacheKey, MessageProducer> cachedProducers =
new HashMap<DestinationCacheKey, MessageProducer>();
private final Map<ConsumerCacheKey, MessageConsumer> cachedConsumers =
new HashMap<ConsumerCacheKey, MessageConsumer>();
private boolean transactionOpen = false;
public CachedSessionInvocationHandler(Session target, LinkedList<Session> 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]);
}
else if (methodName.equals("hashCode")) {
// Use hashCode of Session proxy.
return 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((Session) 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 if (methodName.startsWith("create")) {
this.transactionOpen = true;
if (isCacheProducers() && (methodName.equals("createProducer") ||
methodName.equals("createSender") || methodName.equals("createPublisher"))) {
// Destination argument being null is ok for a producer
return getCachedProducer((Destination) args[0]);
}
else if (isCacheConsumers()) {
// let raw JMS invocation throw an exception if Destination (i.e. args[0]) is null
if ((methodName.equals("createConsumer") || methodName.equals("createReceiver") ||
methodName.equals("createSubscriber"))) {
Destination dest = (Destination) args[0];
if (dest != null && !(dest instanceof TemporaryQueue || dest instanceof TemporaryTopic)) {
return getCachedConsumer(dest,
(args.length > 1 ? (String) args[1] : null),
(args.length > 2 && (Boolean) args[2]),
null);
}
}
else if (methodName.equals("createDurableSubscriber")) {
Destination dest = (Destination) args[0];
if (dest != null) {
return getCachedConsumer(dest,
(args.length > 2 ? (String) args[2] : null),
(args.length > 3 && (Boolean) args[3]),
(String) args[1]);
}
}
}
}
try {
return method.invoke(this.target, args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
private MessageProducer getCachedProducer(Destination dest) throws JMSException {
DestinationCacheKey cacheKey = (dest != null ? new DestinationCacheKey(dest) : null);
MessageProducer producer = this.cachedProducers.get(cacheKey);
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(cacheKey, producer);
}
return new CachedMessageProducer(producer);
}
private MessageConsumer getCachedConsumer(
Destination dest, String selector, boolean noLocal, String subscription) throws JMSException {
ConsumerCacheKey cacheKey = new ConsumerCacheKey(dest, selector, noLocal, subscription);
MessageConsumer consumer = 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(Session 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<Map.Entry<ConsumerCacheKey, MessageConsumer>> it = this.cachedConsumers.entrySet().iterator(); it.hasNext();) {
Map.Entry<ConsumerCacheKey, MessageConsumer> entry = it.next();
if (entry.getKey().subscription != null) {
entry.getValue().close();
it.remove();
}
}
// Allow for multiple close calls...
boolean returned = false;
synchronized (this.sessionList) {
if (!this.sessionList.contains(proxy)) {
this.sessionList.addLast(proxy);
returned = true;
}
}
if (returned && logger.isTraceEnabled()) {
logger.trace("Returned cached Session: " + this.target);
}
}
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 (MessageProducer producer : this.cachedProducers.values()) {
producer.close();
}
for (MessageConsumer consumer : this.cachedConsumers.values()) {
consumer.close();
}
}
finally {
this.cachedProducers.clear();
this.cachedConsumers.clear();
// Now actually close the Session.
this.target.close();
}
}
}
/**
* Simple wrapper class around a Destination reference.
* Used as the cache key when caching MessageProducer objects.
*/
private static class DestinationCacheKey {
private final Destination destination;
private String destinationString;
public DestinationCacheKey(Destination destination) {
Assert.notNull(destination, "Destination must not be null");
this.destination = destination;
}
private String getDestinationString() {
if (this.destinationString == null) {
this.destinationString = this.destination.toString();
}
return this.destinationString;
}
protected boolean destinationEquals(DestinationCacheKey otherKey) {
return (this.destination.getClass().equals(otherKey.destination.getClass()) &&
(this.destination.equals(otherKey.destination) ||
getDestinationString().equals(otherKey.getDestinationString())));
}
public boolean equals(Object other) {
// Effectively checking object equality as well as toString equality.
// On WebSphere MQ, Destination objects do not implement equals...
return (other == this || destinationEquals((DestinationCacheKey) other));
}
public int hashCode() {
// Can't use a more specific hashCode since we can't rely on
// this.destination.hashCode() actually being the same value
// for equivalent destinations... Thanks a lot, WebSphere MQ!
return this.destination.getClass().hashCode();
}
}
/**
* Simple wrapper class around a Destination and other consumer attributes.
* Used as the cache key when caching MessageConsumer objects.
*/
private static class ConsumerCacheKey extends DestinationCacheKey {
private final String selector;
private final boolean noLocal;
private final String subscription;
public ConsumerCacheKey(Destination destination, String selector, boolean noLocal, String subscription) {
super(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 (destinationEquals(otherKey) &&
ObjectUtils.nullSafeEquals(this.selector, otherKey.selector) &&
this.noLocal == otherKey.noLocal &&
ObjectUtils.nullSafeEquals(this.subscription, otherKey.subscription));
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.ArrayList;
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<ExceptionListener> delegates = new ArrayList<ExceptionListener>(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 this.delegates.toArray(new ExceptionListener[this.delegates.size()]);
}
public void onException(JMSException ex) {
for (ExceptionListener listener : this.delegates) {
listener.onException(ex);
}
}
}

View File

@@ -0,0 +1,416 @@
/*
* Copyright 2002-2009 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.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<JmsResourceHolder, Object> {
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(JmsResourceHolder resourceHolder) {
try {
resourceHolder.commitAll();
}
catch (JMSException ex) {
throw new SynchedLocalTransactionFailedException("Local JMS transaction failed to commit", ex);
}
}
protected void releaseResource(JmsResourceHolder resourceHolder, Object resourceKey) {
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,207 @@
/*
* Copyright 2002-2010 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.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<Connection> connections = new LinkedList<Connection>();
private final List<Session> sessions = new LinkedList<Session>();
private final Map<Connection, List<Session>> sessionsPerConnection =
new HashMap<Connection, List<Session>>();
/**
* 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<Session> sessions = this.sessionsPerConnection.get(connection);
if (sessions == null) {
sessions = new LinkedList<Session>();
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() ? this.connections.get(0) : null);
}
public Connection getConnection(Class<? extends Connection> connectionType) {
return CollectionUtils.findValueOfType(this.connections, connectionType);
}
public Session getSession() {
return (!this.sessions.isEmpty() ? this.sessions.get(0) : null);
}
public Session getSession(Class<? extends Session> sessionType) {
return getSession(sessionType, null);
}
public Session getSession(Class<? extends Session> sessionType, Connection connection) {
List<Session> sessions = this.sessions;
if (connection != null) {
sessions = this.sessionsPerConnection.get(connection);
}
return CollectionUtils.findValueOfType(sessions, sessionType);
}
public void commitAll() throws JMSException {
for (Session session : this.sessions) {
try {
session.commit();
}
catch (TransactionInProgressException ex) {
// Ignore -> can only happen in case of a JTA transaction.
}
// Let IllegalStateException through: It might point out an unexpectedly closed session.
}
}
public void closeAll() {
for (Session session : this.sessions) {
try {
session.close();
}
catch (Throwable ex) {
logger.debug("Could not close synchronized JMS Session after transaction", ex);
}
}
for (Connection con : this.connections) {
ConnectionFactoryUtils.releaseConnection(con, this.connectionFactory, true);
}
this.connections.clear();
this.sessions.clear();
this.sessionsPerConnection.clear();
}
}

View File

@@ -0,0 +1,321 @@
/*
* Copyright 2002-2009 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>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();
}
public void flush() {
// no-op
}
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2002-2009 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
* @deprecated as of Spring 3.0, in favor of the JMS 1.1 based {@link JmsTransactionManager}
*/
@Deprecated
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,588 @@
/*
* Copyright 2002-2009 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 the JMS 1.0.2 API.
*
* <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;
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;
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) {
logger.warn("Encountered a JMSException - resetting the underlying JMS Connection", 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 == Session.SESSION_TRANSACTED);
int ackMode = (transacted ? Session.AUTO_ACKNOWLEDGE : mode);
// 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<Class> classes = new ArrayList<Class>(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(),
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]);
}
else if (method.getName().equals("hashCode")) {
// Use hashCode of Connection proxy.
return 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.
synchronized (connectionMonitor) {
if (!started) {
this.target.start();
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];
Integer ackMode = (Integer) args[1];
Integer mode = (transacted ? 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,131 @@
/*
* Copyright 2002-2009 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
* @deprecated as of Spring 3.0, in favor of the JMS 1.1 based {@link SingleConnectionFactory}
*/
@Deprecated
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,327 @@
/*
* Copyright 2002-2009 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 which 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
*/
protected Connection getTransactionAwareConnectionProxy(Connection target) {
List<Class> classes = new ArrayList<Class>(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(),
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]);
}
else if (method.getName().equals("hashCode")) {
// Use hashCode of Connection proxy.
return 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<Class> classes = new ArrayList<Class>(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(),
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]);
}
else if (method.getName().equals("hashCode")) {
// Use hashCode of Connection proxy.
return 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,300 @@
/*
* Copyright 2002-2010 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<JmsUserCredentials> threadBoundCredentials =
new NamedThreadLocal<JmsUserCredentials>("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.remove();
}
/**
* 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 = 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 = 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 = 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,9 @@
/**
*
* Provides a PlatformTransactionManager implementation for a single
* JMS ConnectionFactory, and a SingleConnectionFactory adapter.
*
*/
package org.springframework.jms.connection;

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<T> {
/**
* 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
*/
T 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
*/
<T> T execute(SessionCallback<T> 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
*/
<T> T execute(ProducerCallback<T> 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
*/
<T> T execute(Destination destination, ProducerCallback<T> 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
*/
<T> T execute(String destinationName, ProducerCallback<T> 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
*/
<T> T browse(BrowserCallback<T> 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
*/
<T> T browse(Queue queue, BrowserCallback<T> 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
*/
<T> T browse(String queueName, BrowserCallback<T> 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
*/
<T> T browseSelected(String messageSelector, BrowserCallback<T> 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
*/
<T> T browseSelected(Queue queue, String messageSelector, BrowserCallback<T> 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
*/
<T> T browseSelected(String queueName, String messageSelector, BrowserCallback<T> action) throws JmsException;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,257 @@
/*
* Copyright 2002-2009 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
* @deprecated as of Spring 3.0, in favor of the JMS 1.1 based {@link JmsTemplate}
*/
@Deprecated
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<T> {
/**
* 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
*/
T doInJms(Session session, MessageProducer producer) throws JMSException;
}

View File

@@ -0,0 +1,44 @@
/*
* 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.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<T> {
/**
* 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
*/
T doInJms(Session session) throws JMSException;
}

View File

@@ -0,0 +1,9 @@
/**
*
* Core package of the JMS support.
* Provides a JmsTemplate class and various callback interfaces.
*
*/
package org.springframework.jms.core;

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2002-2009 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 {@link #createJmsTemplate} 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 {
/** Logger available to subclasses */
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 this.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,9 @@
/**
*
* Classes supporting the <code>org.springframework.jms.core</code> package.
* Contains a base class for JmsTemplate usage.
*
*/
package org.springframework.jms.core.support;

View File

@@ -0,0 +1,639 @@
/*
* Copyright 2002-2012 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.SmartLifecycle;
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 SmartLifecycle, BeanNameAware, DisposableBean {
private String clientId;
private boolean autoStartup = true;
private int phase = Integer.MAX_VALUE;
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<Object> pausedTasks = new LinkedList<Object>();
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 boolean isAutoStartup() {
return this.autoStartup;
}
/**
* Specify the phase in which this container should be started and
* stopped. The startup order proceeds from lowest to highest, and
* the shutdown order is the reverse of that. By default this value
* is Integer.MAX_VALUE meaning that this container starts as late
* as possible and stops as soon as possible.
*/
public void setPhase(int phase) {
this.phase = phase;
}
/**
* Return the phase in which this container will be started and stopped.
*/
public int getPhase() {
return this.phase;
}
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()}.
*/
@Override
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();
}
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;
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);
}
}
public void stop(Runnable callback) {
this.stop();
callback.run();
}
/**
* 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...
}
}
}
}
}
/**
* Determine the number of currently paused tasks, if any.
*/
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.
*/
@SuppressWarnings("serial")
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,710 @@
/*
* Copyright 2002-2012 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;
import org.springframework.util.ErrorHandler;
/**
* 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. However, if error handling is necessary, then
* any implementation of the {@link ErrorHandler} strategy may be provided to
* the {@link #setErrorHandler(ErrorHandler)} method. Note that JMSExceptions
* <b>will</b> be passed to the ErrorHandler in addition to (but after) being
* passed to an {@link ExceptionListener}, if one has been provided.
*
* <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 ErrorHandler errorHandler;
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 an ErrorHandler to be invoked in case of any uncaught exceptions thrown
* while processing a Message. By default there will be <b>no</b> ErrorHandler
* so that error-level logging is the only result.
*/
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
/**
* 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;
}
@Override
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
*/
@SuppressWarnings("rawtypes")
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
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
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 (message != null && 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 warn level,
* not propagating it to the JMS provider &mdash; 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.
// Invoke ErrorHandler if available.
invokeErrorHandler(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);
}
}
/**
* Invoke the registered ErrorHandler, if any. Log at warn level otherwise.
* @param ex the uncaught error that arose during JMS processing.
* @see #setErrorHandler
*/
protected void invokeErrorHandler(Throwable ex) {
if (this.errorHandler != null) {
this.errorHandler.handleError(ex);
}
else if (logger.isWarnEnabled()) {
logger.warn("Execution of JMS message listener failed, and no ErrorHandler has been set.", ex);
}
}
/**
* Internal exception class that indicates a rejected message on shutdown.
* Used to trigger a rollback for an external transaction manager in that case.
*/
@SuppressWarnings("serial")
private static class MessageRejectedWhileStoppingException extends RuntimeException {
}
}

View File

@@ -0,0 +1,540 @@
/*
* Copyright 2002-2012 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 {
/**
* 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;
private volatile Boolean commitAfterNoMessageReceived;
@Override
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;
}
@Override
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;
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;
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());
}
}
// Indicate that a message has been received.
return true;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Consumer [" + consumerToUse + "] of " + (transactional ? "transactional " : "") +
"session [" + sessionToUse + "] did not receive a message");
}
noMessageReceived(invoker, sessionToUse);
// Nevertheless call commit, in order to reset the transaction timeout (if any).
// However, don't do this on Tibco since this may lead to a deadlock there.
if (shouldCommitAfterNoMessageReceived(sessionToUse)) {
commitIfNecessary(sessionToUse, message);
}
// Indicate that no message has been received.
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
*/
@Override
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));
}
/**
* Determine whether to trigger a commit after no message has been received.
* This is a good idea on any JMS provider other than Tibco, which is what
* this default implementation checks for.
* @param session the current JMS Session which received no message
* @return whether to call {@link #commitIfNecessary} on the given Session
*/
protected boolean shouldCommitAfterNoMessageReceived(Session session) {
if (this.commitAfterNoMessageReceived == null) {
Session target = ConnectionFactoryUtils.getTargetSession(session);
this.commitAfterNoMessageReceived = !target.getClass().getName().startsWith("com.tibco.tibjms.");
}
return this.commitAfterNoMessageReceived;
}
/**
* 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 when <i>no</i> message has been received,
* before returning to the receive loop again. Allows subclasses to react to
* the event of no incoming message, for example marking the invoker as idle.
* @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,117 @@
/*
* Copyright 2002-2009 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
* @deprecated as of Spring 3.0, in favor of the JMS 1.1 based {@link DefaultMessageListenerContainer}
*/
@Deprecated
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() ? 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() ? 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-2009 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<M extends Message> {
/**
* 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(M message, Session session) throws JMSException;
}

View File

@@ -0,0 +1,395 @@
/*
* Copyright 2002-2012 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.Set;
import java.util.concurrent.Executor;
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.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>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 boolean connectLazily = false;
private int concurrentConsumers = 1;
private Executor taskExecutor;
private Set<Session> sessions;
private Set<MessageConsumer> 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 whether to connect lazily, i.e. whether to establish the JMS Connection
* and the corresponding Sessions and MessageConsumers as late as possible -
* in the start phase of this container.
* <p>Default is "false": connecting early, i.e. during the bean initialization phase.
* Set this flag to "true" in order to switch to lazy connecting if your target broker
* is likely to not have started up yet and you prefer to not even try a connection.
* @see #start()
* @see #initialize()
*/
public void setConnectLazily(boolean connectLazily) {
this.connectLazily = connectLazily;
}
/**
* Specify concurrency limits via a "lower-upper" String, e.g. "5-10", or a simple
* upper limit String, e.g. "10".
* <p>This listener container will always hold on to the maximum number of
* consumers {@link #setConcurrentConsumers} since it is unable to scale.
* <p>This property is primarily supported for configuration compatibility with
* {@link DefaultMessageListenerContainer}. For this local listener container,
* generally use {@link #setConcurrentConsumers} instead.
*/
public void setConcurrency(String concurrency) {
try {
int separatorIndex = concurrency.indexOf('-');
if (separatorIndex != -1) {
setConcurrentConsumers(Integer.parseInt(concurrency.substring(separatorIndex + 1, concurrency.length())));
}
else {
setConcurrentConsumers(Integer.parseInt(concurrency));
}
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException("Invalid concurrency value [" + concurrency + "]: only " +
"single maximum integer (e.g. \"5\") and minimum-maximum combo (e.g. \"3-5\") supported. " +
"Note that SimpleMessageListenerContainer will effectively ignore the minimum value and " +
"always keep a fixed number of consumers according to the maximum value.");
}
}
/**
* 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(Executor taskExecutor) {
this.taskExecutor = taskExecutor;
}
@Override
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.
*/
@Override
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
*/
@Override
protected void doInitialize() throws JMSException {
if (!this.connectLazily) {
try {
establishSharedConnection();
}
catch (JMSException ex) {
logger.debug("Could not connect on initialization - registering message consumers lazily", ex);
return;
}
initializeConsumers();
}
}
/**
* Re-initializes this container's JMS message consumers,
* if not initialized already.
*/
@Override
protected void doStart() throws JMSException {
super.doStart();
initializeConsumers();
}
/**
* Registers this listener container as JMS ExceptionListener on the shared connection.
*/
@Override
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<Session>(this.concurrentConsumers);
this.consumers = new HashSet<MessageConsumer>(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.
*/
@Override
protected void doShutdown() throws JMSException {
logger.debug("Closing JMS MessageConsumers");
for (MessageConsumer consumer : this.consumers) {
JmsUtils.closeMessageConsumer(consumer);
}
logger.debug("Closing JMS Sessions");
for (Session session : this.sessions) {
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,99 @@
/*
* Copyright 2002-2009 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
* @deprecated as of Spring 3.0, in favor of the JMS 1.1 based {@link SimpleMessageListenerContainer}
*/
@Deprecated
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,653 @@
/*
* Copyright 2002-2009 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>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}
* 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<Message>, 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
*/
@SuppressWarnings("unchecked")
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() {
Object delegate = getDelegate();
if (delegate != this && delegate instanceof SubscriptionNameProvider) {
return ((SubscriptionNameProvider) delegate).getSubscriptionName();
}
else {
return 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,100 @@
/*
* Copyright 2002-2009 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
* @deprecated as of Spring 3.0, in favor of the JMS 1.1 based {@link MessageListenerAdapter}
*/
@Deprecated
public class MessageListenerAdapter102 extends MessageListenerAdapter {
/**
* Create a new instance of the MessageListenerAdapter102 class
* with the default settings.
*/
public MessageListenerAdapter102() {
}
/**
* Create a new instance of the 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,10 @@
/**
*
* 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.
*
*/
package org.springframework.jms.listener.adapter;

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,204 @@
/*
* Copyright 2002-2010 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 org.springframework.core.Constants;
/**
* 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 {
/** Constants instance for javax.jms.Session */
private static final Constants sessionConstants = new Constants(Session.class);
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;
}
/**
* Set the JMS acknowledgement mode by the name of the corresponding constant
* in the JMS {@link Session} interface, e.g. "CLIENT_ACKNOWLEDGE".
* <p>Note that JCA resource adapters generally only support auto and dups-ok
* (see Spring's {@link StandardJmsActivationSpecFactory}). ActiveMQ also
* supports "SESSION_TRANSACTED" in the form of RA-managed transactions
* (automatically translated by Spring's {@link DefaultJmsActivationSpecFactory}.
* @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.Session#SESSION_TRANSACTED
* @see StandardJmsActivationSpecFactory
* @see DefaultJmsActivationSpecFactory
*/
public void setAcknowledgeModeName(String constantName) {
setAcknowledgeMode(sessionConstants.asNumber(constantName).intValue());
}
/**
* Set the JMS acknowledgement mode to use.
* @see javax.jms.Session#AUTO_ACKNOWLEDGE
* @see javax.jms.Session#CLIENT_ACKNOWLEDGE
* @see javax.jms.Session#DUPS_OK_ACKNOWLEDGE
* @see javax.jms.Session#SESSION_TRANSACTED
*/
public void setAcknowledgeMode(int acknowledgeMode) {
this.acknowledgeMode = acknowledgeMode;
}
/**
* Return the JMS acknowledgement mode to use.
*/
public int getAcknowledgeMode() {
return this.acknowledgeMode;
}
/**
* Specify concurrency limits via a "lower-upper" String, e.g. "5-10", or a simple
* upper limit String, e.g. "10".
* <p>JCA listener containers will always scale from zero to the given upper limit.
* A specified lower limit will effectively be ignored.
* <p>This property is primarily supported for configuration compatibility with
* {@link org.springframework.jms.listener.DefaultMessageListenerContainer}.
* For this activation config, generally use {@link #setMaxConcurrency} instead.
*/
public void setConcurrency(String concurrency) {
try {
int separatorIndex = concurrency.indexOf('-');
if (separatorIndex != -1) {
setMaxConcurrency(Integer.parseInt(concurrency.substring(separatorIndex + 1, concurrency.length())));
}
else {
setMaxConcurrency(Integer.parseInt(concurrency));
}
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException("Invalid concurrency value [" + concurrency + "]: only " +
"single maximum integer (e.g. \"5\") and minimum-maximum combo (e.g. \"3-5\") supported. " +
"Note that JmsActivationSpecConfig will effectively ignore the minimum value and " +
"scale from zero up to the number of consumers according to the maximum value.");
}
}
/**
* Specify the maximum number of consumers/sessions to use, effectively
* controlling the number of concurrent invocations on the target listener.
*/
public void setMaxConcurrency(int maxConcurrency) {
this.maxConcurrency = maxConcurrency;
}
/**
* Return the maximum number of consumers/sessions to use.
*/
public int getMaxConcurrency() {
return this.maxConcurrency;
}
/**
* Specify the maximum number of messages to load into a session
* (a kind of batch size).
*/
public void setPrefetchSize(int prefetchSize) {
this.prefetchSize = prefetchSize;
}
/**
* Return the maximum number of messages to load into a session.
*/
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,203 @@
/*
* Copyright 2002-2009 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.Map;
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 Map<String, String> 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(Map<String, String> 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,8 @@
/**
*
* This package provides JCA-based endpoint management for JMS message listeners.
*
*/
package org.springframework.jms.listener.endpoint;

View File

@@ -0,0 +1,10 @@
/**
*
* This package contains the base message listener container facility.
* It also offers the DefaultMessageListenerContainer and SimpleMessageListenerContainer
* implementations, based on the plain JMS client API.
*
*/
package org.springframework.jms.listener;

View File

@@ -0,0 +1,9 @@
/**
*
* This package contains integration classes for JMS,
* allowing for Spring-style JMS access.
*
*/
package org.springframework.jms;

View File

@@ -0,0 +1,434 @@
/*
* Copyright 2002-2011 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;
import org.springframework.util.ClassUtils;
/**
* {@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 static final boolean jms11Available = ClassUtils.hasMethod(ConnectionFactory.class, "createConnection");
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;
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.
*/
protected Connection createConnection() throws JMSException {
ConnectionFactory cf = getConnectionFactory();
if (jms11Available) {
return cf.createConnection();
}
else {
return ((QueueConnectionFactory) cf).createQueueConnection();
}
}
/**
* Create a new JMS Session for this JMS invoker.
*/
protected Session createSession(Connection con) throws JMSException {
if (jms11Available) {
return con.createSession(false, Session.AUTO_ACKNOWLEDGE);
}
else {
return ((QueueConnection) con).createQueueSession(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 (jms11Available) {
// Standard JMS 1.1 API usage...
responseQueue = session.createTemporaryQueue();
producer = session.createProducer(queue);
consumer = session.createConsumer(responseQueue);
requestMessage.setJMSReplyTo(responseQueue);
producer.send(requestMessage);
}
else {
// Perform all calls on QueueSession reference for JMS 1.0.2 compatibility...
// DEPRECATED but kept around with the deprecated JmsTemplate102 etc classes for the time being.
QueueSession queueSession = (QueueSession) session;
responseQueue = queueSession.createTemporaryQueue();
QueueSender sender = queueSession.createSender(queue);
producer = sender;
consumer = queueSession.createReceiver(responseQueue);
requestMessage.setJMSReplyTo(responseQueue);
sender.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-2009 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<Object>, 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-2009 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<Message>, 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,12 @@
/**
*
* 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.
*
*/
package org.springframework.jms.remoting;

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,319 @@
/*
* Copyright 2002-2009 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) {
if (message == null) {
message = linkedEx.toString();
}
else {
String linkedMessage = linkedEx.getMessage();
if (linkedMessage != null && !message.contains(linkedMessage)) {
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,373 @@
/*
* Copyright 2002-2011 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 java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import javax.jms.BytesMessage;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.xml.transform.Result;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.type.JavaType;
import org.springframework.oxm.Marshaller;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Message converter that uses the Jackson library to convert messages to and from JSON.
* Maps an object to a {@link BytesMessage}, or to a {@link TextMessage} if the
* {@link #setTargetType targetType} is set to {@link MessageType#TEXT}.
* Converts from a {@link TextMessage} or {@link BytesMessage} to an object.
*
* @author Mark Pollack
* @author Dave Syer
* @author Juergen Hoeller
* @since 3.1
*/
public class MappingJacksonMessageConverter implements MessageConverter {
/**
* The default encoding used for writing to text messages: UTF-8.
*/
public static final String DEFAULT_ENCODING = "UTF-8";
private ObjectMapper objectMapper = new ObjectMapper();
private MessageType targetType = MessageType.BYTES;
private String encoding = DEFAULT_ENCODING;
private String encodingPropertyName;
private String typeIdPropertyName;
private Map<String, Class<?>> idClassMappings = new HashMap<String, Class<?>>();
private Map<Class<?>, String> classIdMappings = new HashMap<Class<?>, String>();
/**
* Specify the {@link ObjectMapper} to use instead of using the default.
*/
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
}
/**
* Specify whether {@link #toMessage(Object, Session)} should marshal to a
* {@link BytesMessage} or a {@link TextMessage}.
* <p>The default is {@link MessageType#BYTES}, i.e. this converter marshals to
* a {@link BytesMessage}. Note that the default version of this converter
* supports {@link MessageType#BYTES} and {@link MessageType#TEXT} only.
* @see MessageType#BYTES
* @see MessageType#TEXT
*/
public void setTargetType(MessageType targetType) {
Assert.notNull(targetType, "MessageType must not be null");
this.targetType = targetType;
}
/**
* Specify the encoding to use when converting to and from text-based
* message body content. The default encoding will be "UTF-8".
* <p>When reading from a a text-based message, an encoding may have been
* suggested through a special JMS property which will then be preferred
* over the encoding set on this MessageConverter instance.
* @see #setEncodingPropertyName
*/
public void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* Specify the name of the JMS message property that carries the encoding from
* bytes to String and back is BytesMessage is used during the conversion process.
* <p>Default is none. Setting this property is optional; if not set, UTF-8 will
* be used for decoding any incoming bytes message.
* @see #setEncoding
*/
public void setEncodingPropertyName(String encodingPropertyName) {
this.encodingPropertyName = encodingPropertyName;
}
/**
* Specify the name of the JMS message property that carries the type id for the
* contained object: either a mapped id value or a raw Java class name.
* <p>Default is none. <b>NOTE: This property needs to be set in order to allow
* for converting from an incoming message to a Java object.</b>
* @see #setTypeIdMappings
*/
public void setTypeIdPropertyName(String typeIdPropertyName) {
this.typeIdPropertyName = typeIdPropertyName;
}
/**
* Specify mappings from type ids to Java classes, if desired.
* This allows for synthetic ids in the type id message property,
* instead of transferring Java class names.
* <p>Default is no custom mappings, i.e. transferring raw Java class names.
* @param typeIdMappings a Map with type id values as keys and Java classes as values
*/
public void setTypeIdMappings(Map<String, Class<?>> typeIdMappings) {
this.idClassMappings = new HashMap<String, Class<?>>();
for (Map.Entry<String, Class<?>> entry : typeIdMappings.entrySet()) {
String id = entry.getKey();
Class<?> clazz = entry.getValue();
this.idClassMappings.put(id, clazz);
this.classIdMappings.put(clazz, id);
}
}
public Message toMessage(Object object, Session session) throws JMSException, MessageConversionException {
Message message;
try {
switch (this.targetType) {
case TEXT:
message = mapToTextMessage(object, session, this.objectMapper);
break;
case BYTES:
message = mapToBytesMessage(object, session, this.objectMapper);
break;
default:
message = mapToMessage(object, session, this.objectMapper, this.targetType);
}
}
catch (IOException ex) {
throw new MessageConversionException("Could not map JSON object [" + object + "]", ex);
}
setTypeIdOnMessage(object, message);
return message;
}
public Object fromMessage(Message message) throws JMSException, MessageConversionException {
try {
JavaType targetJavaType = getJavaTypeForMessage(message);
return convertToObject(message, targetJavaType);
}
catch (IOException ex) {
throw new MessageConversionException("Failed to convert JSON message content", ex);
}
}
/**
* Map the given object to a {@link TextMessage}.
* @param object the object to be mapped
* @param session current JMS session
* @param objectMapper the mapper to use
* @return the resulting message
* @throws JMSException if thrown by JMS methods
* @throws IOException in case of I/O errors
* @see Session#createBytesMessage
* @see Marshaller#marshal(Object, Result)
*/
protected TextMessage mapToTextMessage(Object object, Session session, ObjectMapper objectMapper)
throws JMSException, IOException {
StringWriter writer = new StringWriter();
objectMapper.writeValue(writer, object);
return session.createTextMessage(writer.toString());
}
/**
* Map the given object to a {@link BytesMessage}.
* @param object the object to be mapped
* @param session current JMS session
* @param objectMapper the mapper to use
* @return the resulting message
* @throws JMSException if thrown by JMS methods
* @throws IOException in case of I/O errors
* @see Session#createBytesMessage
* @see Marshaller#marshal(Object, Result)
*/
protected BytesMessage mapToBytesMessage(Object object, Session session, ObjectMapper objectMapper)
throws JMSException, IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(bos, this.encoding);
objectMapper.writeValue(writer, object);
BytesMessage message = session.createBytesMessage();
message.writeBytes(bos.toByteArray());
if (this.encodingPropertyName != null) {
message.setStringProperty(this.encodingPropertyName, this.encoding);
}
return message;
}
/**
* Template method that allows for custom message mapping.
* Invoked when {@link #setTargetType} is not {@link MessageType#TEXT} or
* {@link MessageType#BYTES}.
* <p>The default implementation throws an {@link IllegalArgumentException}.
* @param object the object to marshal
* @param session the JMS Session
* @param objectMapper the mapper to use
* @param targetType the target message type (other than TEXT or BYTES)
* @return the resulting message
* @throws JMSException if thrown by JMS methods
* @throws IOException in case of I/O errors
*/
protected Message mapToMessage(Object object, Session session, ObjectMapper objectMapper, MessageType targetType)
throws JMSException, IOException {
throw new IllegalArgumentException("Unsupported message type [" + targetType +
"]. MappingJacksonMessageConverter by default only supports TextMessages and BytesMessages.");
}
/**
* Set a type id for the given payload object on the given JMS Message.
* <p>The default implementation consults the configured type id mapping and
* sets the resulting value (either a mapped id or the raw Java class name)
* into the configured type id message property.
* @param object the payload object to set a type id for
* @param message the JMS Message to set the type id on
* @throws JMSException if thrown by JMS methods
* @see #getJavaTypeForMessage(javax.jms.Message)
* @see #setTypeIdPropertyName(String)
* @see #setTypeIdMappings(java.util.Map)
*/
protected void setTypeIdOnMessage(Object object, Message message) throws JMSException {
if (this.typeIdPropertyName != null) {
String typeId = this.classIdMappings.get(object.getClass());
if (typeId == null) {
typeId = object.getClass().getName();
}
message.setStringProperty(this.typeIdPropertyName, typeId);
}
}
/**
* Convenience method to dispatch to converters for individual message types.
*/
private Object convertToObject(Message message, JavaType targetJavaType) throws JMSException, IOException {
if (message instanceof TextMessage) {
return convertFromTextMessage((TextMessage) message, targetJavaType);
}
else if (message instanceof BytesMessage) {
return convertFromBytesMessage((BytesMessage) message, targetJavaType);
}
else {
return convertFromMessage(message, targetJavaType);
}
}
/**
* Convert a TextMessage to a Java Object with the specified type.
* @param message the input message
* @param targetJavaType the target type
* @return the message converted to an object
* @throws JMSException if thrown by JMS
* @throws IOException in case of I/O errors
*/
protected Object convertFromTextMessage(TextMessage message, JavaType targetJavaType)
throws JMSException, IOException {
String body = message.getText();
return this.objectMapper.readValue(body, targetJavaType);
}
/**
* Convert a BytesMessage to a Java Object with the specified type.
* @param message the input message
* @param targetJavaType the target type
* @return the message converted to an object
* @throws JMSException if thrown by JMS
* @throws IOException in case of I/O errors
*/
protected Object convertFromBytesMessage(BytesMessage message, JavaType targetJavaType)
throws JMSException, IOException {
String encoding = this.encoding;
if (this.encodingPropertyName != null && message.propertyExists(this.encodingPropertyName)) {
encoding = message.getStringProperty(this.encodingPropertyName);
}
byte[] bytes = new byte[(int) message.getBodyLength()];
message.readBytes(bytes);
try {
String body = new String(bytes, encoding);
return this.objectMapper.readValue(body, targetJavaType);
}
catch (UnsupportedEncodingException ex) {
throw new MessageConversionException("Cannot convert bytes to String", ex);
}
}
/**
* Template method that allows for custom message mapping.
* Invoked when {@link #setTargetType} is not {@link MessageType#TEXT} or
* {@link MessageType#BYTES}.
* <p>The default implementation throws an {@link IllegalArgumentException}.
* @param message the input message
* @param targetJavaType the target type
* @return the message converted to an object
* @throws JMSException if thrown by JMS
* @throws IOException in case of I/O errors
*/
protected Object convertFromMessage(Message message, JavaType targetJavaType)
throws JMSException, IOException {
throw new IllegalArgumentException("Unsupported message type [" + message.getClass() +
"]. MappingJacksonMessageConverter by default only supports TextMessages and BytesMessages.");
}
/**
* Determine a Jackson JavaType for the given JMS Message,
* typically parsing a type id message property.
* <p>The default implementation parses the configured type id property name
* and consults the configured type id mapping. This can be overridden with
* a different strategy, e.g. doing some heuristics based on message origin.
* @param object the payload object to set a type id for
* @param message the JMS Message to set the type id on
* @throws JMSException if thrown by JMS methods
* @see #setTypeIdOnMessage(Object, javax.jms.Message)
* @see #setTypeIdPropertyName(String)
* @see #setTypeIdMappings(java.util.Map)
*/
protected JavaType getJavaTypeForMessage(Message message) throws JMSException {
String typeId = message.getStringProperty(this.typeIdPropertyName);
if (typeId == null) {
throw new MessageConversionException("Could not find type id property [" + this.typeIdPropertyName + "]");
}
Class mappedClass = this.idClassMappings.get(typeId);
if (mappedClass != null) {
return TypeFactory.type(mappedClass);
}
try {
return TypeFactory.type(ClassUtils.forName(typeId, getClass().getClassLoader()));
}
catch (Throwable ex) {
throw new MessageConversionException("Failed to resolve type id [" + typeId + "]", ex);
}
}
}

View File

@@ -0,0 +1,317 @@
/*
* Copyright 2002-2011 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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import javax.jms.BytesMessage;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;
import org.springframework.oxm.XmlMappingException;
import org.springframework.util.Assert;
/**
* Spring JMS {@link MessageConverter} that uses a {@link Marshaller} and {@link Unmarshaller}.
* Marshals an object to a {@link BytesMessage}, or to a {@link TextMessage} if the
* {@link #setTargetType targetType} is set to {@link MessageType#TEXT}.
* Unmarshals from a {@link TextMessage} or {@link BytesMessage} to an object.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
* @see org.springframework.jms.core.JmsTemplate#convertAndSend
* @see org.springframework.jms.core.JmsTemplate#receiveAndConvert
*/
public class MarshallingMessageConverter implements MessageConverter, InitializingBean {
private Marshaller marshaller;
private Unmarshaller unmarshaller;
private MessageType targetType = MessageType.BYTES;
/**
* Construct a new <code>MarshallingMessageConverter</code> with no {@link Marshaller}
* or {@link Unmarshaller} set. The marshaller must be set after construction by invoking
* {@link #setMarshaller(Marshaller)} and {@link #setUnmarshaller(Unmarshaller)} .
*/
public MarshallingMessageConverter() {
}
/**
* Construct a new <code>MarshallingMessageConverter</code> with the given {@link Marshaller} set.
* <p>If the given {@link Marshaller} also implements the {@link Unmarshaller} interface,
* it is used for both marshalling and unmarshalling. Otherwise, an exception is thrown.
* <p>Note that all {@link Marshaller} implementations in Spring also implement the
* {@link Unmarshaller} interface, so that you can safely use this constructor.
* @param marshaller object used as marshaller and unmarshaller
* @throws IllegalArgumentException when <code>marshaller</code> does not implement the
* {@link Unmarshaller} interface as well
*/
public MarshallingMessageConverter(Marshaller marshaller) {
Assert.notNull(marshaller, "Marshaller must not be null");
if (!(marshaller instanceof Unmarshaller)) {
throw new IllegalArgumentException(
"Marshaller [" + marshaller + "] does not implement the Unmarshaller " +
"interface. Please set an Unmarshaller explicitly by using the " +
"MarshallingMessageConverter(Marshaller, Unmarshaller) constructor.");
}
else {
this.marshaller = marshaller;
this.unmarshaller = (Unmarshaller) marshaller;
}
}
/**
* Construct a new <code>MarshallingMessageConverter</code> with the
* given Marshaller and Unmarshaller.
* @param marshaller the Marshaller to use
* @param unmarshaller the Unmarshaller to use
*/
public MarshallingMessageConverter(Marshaller marshaller, Unmarshaller unmarshaller) {
Assert.notNull(marshaller, "Marshaller must not be null");
Assert.notNull(unmarshaller, "Unmarshaller must not be null");
this.marshaller = marshaller;
this.unmarshaller = unmarshaller;
}
/**
* Set the {@link Marshaller} to be used by this message converter.
*/
public void setMarshaller(Marshaller marshaller) {
this.marshaller = marshaller;
}
/**
* Set the {@link Unmarshaller} to be used by this message converter.
*/
public void setUnmarshaller(Unmarshaller unmarshaller) {
this.unmarshaller = unmarshaller;
}
/**
* Specify whether {@link #toMessage(Object, Session)} should marshal to
* a {@link BytesMessage} or a {@link TextMessage}.
* <p>The default is {@link MessageType#BYTES}, i.e. this converter marshals
* to a {@link BytesMessage}. Note that the default version of this converter
* supports {@link MessageType#BYTES} and {@link MessageType#TEXT} only.
* @see MessageType#BYTES
* @see MessageType#TEXT
*/
public void setTargetType(MessageType targetType) {
Assert.notNull(targetType, "MessageType must not be null");
this.targetType = targetType;
}
public void afterPropertiesSet() {
Assert.notNull(this.marshaller, "Property 'marshaller' is required");
Assert.notNull(this.unmarshaller, "Property 'unmarshaller' is required");
}
/**
* This implementation marshals the given object to a {@link javax.jms.TextMessage} or
* {@link javax.jms.BytesMessage}. The desired message type can be defined by setting
* the {@link #setTargetType "marshalTo"} property.
* @see #marshalToTextMessage
* @see #marshalToBytesMessage
*/
public Message toMessage(Object object, Session session) throws JMSException, MessageConversionException {
try {
switch (this.targetType) {
case TEXT:
return marshalToTextMessage(object, session, this.marshaller);
case BYTES:
return marshalToBytesMessage(object, session, this.marshaller);
default:
return marshalToMessage(object, session, this.marshaller, this.targetType);
}
}
catch (XmlMappingException ex) {
throw new MessageConversionException("Could not marshal [" + object + "]", ex);
}
catch (IOException ex) {
throw new MessageConversionException("Could not marshal [" + object + "]", ex);
}
}
/**
* This implementation unmarshals the given {@link Message} into an object.
* @see #unmarshalFromTextMessage
* @see #unmarshalFromBytesMessage
*/
public Object fromMessage(Message message) throws JMSException, MessageConversionException {
try {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
return unmarshalFromTextMessage(textMessage, this.unmarshaller);
}
else if (message instanceof BytesMessage) {
BytesMessage bytesMessage = (BytesMessage) message;
return unmarshalFromBytesMessage(bytesMessage, this.unmarshaller);
}
else {
return unmarshalFromMessage(message, this.unmarshaller);
}
}
catch (IOException ex) {
throw new MessageConversionException("Could not access message content: " + message, ex);
}
catch (XmlMappingException ex) {
throw new MessageConversionException("Could not unmarshal message: " + message, ex);
}
}
/**
* Marshal the given object to a {@link TextMessage}.
* @param object the object to be marshalled
* @param session current JMS session
* @param marshaller the marshaller to use
* @return the resulting message
* @throws JMSException if thrown by JMS methods
* @throws IOException in case of I/O errors
* @throws XmlMappingException in case of OXM mapping errors
* @see Session#createTextMessage
* @see Marshaller#marshal(Object, Result)
*/
protected TextMessage marshalToTextMessage(Object object, Session session, Marshaller marshaller)
throws JMSException, IOException, XmlMappingException {
StringWriter writer = new StringWriter();
Result result = new StreamResult(writer);
marshaller.marshal(object, result);
return session.createTextMessage(writer.toString());
}
/**
* Marshal the given object to a {@link BytesMessage}.
* @param object the object to be marshalled
* @param session current JMS session
* @param marshaller the marshaller to use
* @return the resulting message
* @throws JMSException if thrown by JMS methods
* @throws IOException in case of I/O errors
* @throws XmlMappingException in case of OXM mapping errors
* @see Session#createBytesMessage
* @see Marshaller#marshal(Object, Result)
*/
protected BytesMessage marshalToBytesMessage(Object object, Session session, Marshaller marshaller)
throws JMSException, IOException, XmlMappingException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
StreamResult streamResult = new StreamResult(bos);
marshaller.marshal(object, streamResult);
BytesMessage message = session.createBytesMessage();
message.writeBytes(bos.toByteArray());
return message;
}
/**
* Template method that allows for custom message marshalling.
* Invoked when {@link #setTargetType} is not {@link MessageType#TEXT} or
* {@link MessageType#BYTES}.
* <p>The default implementation throws an {@link IllegalArgumentException}.
* @param object the object to marshal
* @param session the JMS session
* @param marshaller the marshaller to use
* @param targetType the target message type (other than TEXT or BYTES)
* @return the resulting message
* @throws JMSException if thrown by JMS methods
* @throws IOException in case of I/O errors
* @throws XmlMappingException in case of OXM mapping errors
*/
protected Message marshalToMessage(Object object, Session session, Marshaller marshaller, MessageType targetType)
throws JMSException, IOException, XmlMappingException {
throw new IllegalArgumentException("Unsupported message type [" + targetType +
"]. MarshallingMessageConverter by default only supports TextMessages and BytesMessages.");
}
/**
* Unmarshal the given {@link TextMessage} into an object.
* @param message the message
* @param unmarshaller the unmarshaller to use
* @return the unmarshalled object
* @throws JMSException if thrown by JMS methods
* @throws IOException in case of I/O errors
* @throws XmlMappingException in case of OXM mapping errors
* @see Unmarshaller#unmarshal(Source)
*/
protected Object unmarshalFromTextMessage(TextMessage message, Unmarshaller unmarshaller)
throws JMSException, IOException, XmlMappingException {
Source source = new StreamSource(new StringReader(message.getText()));
return unmarshaller.unmarshal(source);
}
/**
* Unmarshal the given {@link BytesMessage} into an object.
* @param message the message
* @param unmarshaller the unmarshaller to use
* @return the unmarshalled object
* @throws JMSException if thrown by JMS methods
* @throws IOException in case of I/O errors
* @throws XmlMappingException in case of OXM mapping errors
* @see Unmarshaller#unmarshal(Source)
*/
protected Object unmarshalFromBytesMessage(BytesMessage message, Unmarshaller unmarshaller)
throws JMSException, IOException, XmlMappingException {
byte[] bytes = new byte[(int) message.getBodyLength()];
message.readBytes(bytes);
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
StreamSource source = new StreamSource(bis);
return unmarshaller.unmarshal(source);
}
/**
* Template method that allows for custom message unmarshalling.
* Invoked when {@link #fromMessage(Message)} is invoked with a message
* that is not a {@link TextMessage} or {@link BytesMessage}.
* <p>The default implementation throws an {@link IllegalArgumentException}.
* @param message the message
* @param unmarshaller the unmarshaller to use
* @return the unmarshalled object
* @throws JMSException if thrown by JMS methods
* @throws IOException in case of I/O errors
* @throws XmlMappingException in case of OXM mapping errors
*/
protected Object unmarshalFromMessage(Message message, Unmarshaller unmarshaller)
throws JMSException, IOException, XmlMappingException {
throw new IllegalArgumentException("Unsupported message type [" + message.getClass() +
"]. MarshallingMessageConverter by default only supports TextMessages and BytesMessages.");
}
}

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,32 @@
/*
* Copyright 2002-2009 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;
/**
* Constants that indicate a target message type to convert to: a
* {@link javax.jms.TextMessage}, a {@link javax.jms.BytesMessage},
* a {@link javax.jms.MapMessage} or an {@link ObjectMessage}.
*
* @author Juergen Hoeller
* @since 3.0
* @see MarshallingMessageConverter#setTargetType
*/
public enum MessageType {
TEXT, BYTES, MAP, OBJECT
}

View File

@@ -0,0 +1,218 @@
/*
* Copyright 2002-2009 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.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).
*
* @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 (Map.Entry entry : map.entrySet()) {
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<String, Object> map = new HashMap<String, Object>();
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,66 @@
/*
* Copyright 2002-2009 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()
* @deprecated as of Spring 3.0, in favor of the JMS 1.1 based {@link SimpleMessageConverter}
*/
@Deprecated
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,9 @@
/**
*
* Provides a MessageConverter abstraction to convert
* between Java objects and JMS messages.
*
*/
package org.springframework.jms.support.converter;

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

View File

@@ -0,0 +1,161 @@
/*
* Copyright 2002-2009 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 java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.Topic;
import javax.naming.NamingException;
import org.springframework.jndi.JndiLocatorSupport;
import org.springframework.util.Assert;
/**
* {@link DestinationResolver} implementation which interprets destination names
* as JNDI locations (with a configurable fallback strategy).
*
* <p>Allows for customizing the JNDI environment if necessary, for example
* specifying appropriate JNDI environment properties.
*
* <p>Dynamic queues and topics get cached by destination name. As a consequence,
* you need to use unique destination names across both queues and topics.
* Caching can be turned off through the {@link #setCache "cache"} flag.
*
* <p>Note that the fallback to resolution of dynamic destinations
* is turned <i>off</i> by default. Switch the
* {@link #setFallbackToDynamicDestination "fallbackToDynamicDestination"}
* flag on to enable this functionality.
*
* @author Mark Pollack
* @author Juergen Hoeller
* @since 1.1
* @see #setJndiTemplate
* @see #setJndiEnvironment
* @see #setCache
* @see #setFallbackToDynamicDestination
*/
public class JndiDestinationResolver extends JndiLocatorSupport implements CachingDestinationResolver {
private boolean cache = true;
private boolean fallbackToDynamicDestination = false;
private DestinationResolver dynamicDestinationResolver = new DynamicDestinationResolver();
private final Map<String, Destination> destinationCache = new ConcurrentHashMap<String, Destination>();
/**
* Set whether to cache resolved destinations. Default is "true".
* <p>This flag can be turned off to re-lookup a destination for each operation,
* which allows for hot restarting of destinations. This is mainly useful
* during development.
* <p>Note that dynamic queues and topics get cached by destination name.
* As a consequence, you need to use unique destination names across both
* queues and topics.
*/
public void setCache(boolean cache) {
this.cache = cache;
}
/**
* Set whether this resolver is supposed to create dynamic destinations
* if the destination name is not found in JNDI. Default is "false".
* <p>Turn this flag on to enable transparent fallback to dynamic destinations.
* @see #setDynamicDestinationResolver
*/
public void setFallbackToDynamicDestination(boolean fallbackToDynamicDestination) {
this.fallbackToDynamicDestination = fallbackToDynamicDestination;
}
/**
* Set the {@link DestinationResolver} to use when falling back to dynamic
* destinations.
* <p>The default is Spring's standard {@link DynamicDestinationResolver}.
* @see #setFallbackToDynamicDestination
* @see DynamicDestinationResolver
*/
public void setDynamicDestinationResolver(DestinationResolver dynamicDestinationResolver) {
this.dynamicDestinationResolver = dynamicDestinationResolver;
}
public Destination resolveDestinationName(Session session, String destinationName, boolean pubSubDomain)
throws JMSException {
Assert.notNull(destinationName, "Destination name must not be null");
Destination dest = this.destinationCache.get(destinationName);
if (dest != null) {
validateDestination(dest, destinationName, pubSubDomain);
}
else {
try {
dest = lookup(destinationName, Destination.class);
validateDestination(dest, destinationName, pubSubDomain);
}
catch (NamingException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Destination [" + destinationName + "] not found in JNDI", ex);
}
if (this.fallbackToDynamicDestination) {
dest = this.dynamicDestinationResolver.resolveDestinationName(session, destinationName, pubSubDomain);
}
else {
throw new DestinationResolutionException(
"Destination [" + destinationName + "] not found in JNDI", ex);
}
}
if (this.cache) {
this.destinationCache.put(destinationName, dest);
}
}
return dest;
}
/**
* Validate the given Destination object, checking whether it matches
* the expected type.
* @param destination the Destination object to validate
* @param destinationName the name of the destination
* @param pubSubDomain <code>true</code> if a Topic is expected,
* <code>false</code> in case of a Queue
*/
protected void validateDestination(Destination destination, String destinationName, boolean pubSubDomain) {
Class targetClass = Queue.class;
if (pubSubDomain) {
targetClass = Topic.class;
}
if (!targetClass.isInstance(destination)) {
throw new DestinationResolutionException(
"Destination [" + destinationName + "] is not of expected type [" + targetClass.getName() + "]");
}
}
public void removeFromCache(String destinationName) {
this.destinationCache.remove(destinationName);
}
public void clearCache() {
this.destinationCache.clear();
}
}

View File

@@ -0,0 +1,8 @@
/**
*
* Support classes for Spring's JMS framework.
*
*/
package org.springframework.jms.support.destination;

View File

@@ -0,0 +1,9 @@
/**
*
* This package provides generic JMS support classes,
* to be used by higher-level classes like JmsTemplate.
*
*/
package org.springframework.jms.support;

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Spring's JMS support package, covering synchronous JMS access as well as message listener containers.
</p>
</body>
</html>