Initial import of the JMS module
This commit is contained in:
parent
dc20a9478f
commit
e6b7d6222a
|
|
@ -9,6 +9,7 @@
|
|||
<pathelement location="../org.springframework.aop"/>
|
||||
<pathelement location="../org.springframework.context"/>
|
||||
<pathelement location="../org.springframework.transaction"/>
|
||||
<pathelement location="../org.springframework.jms"/>
|
||||
<pathelement location="../org.springframework.aspects"/>
|
||||
<pathelement location="../org.springframework.jdbc"/>
|
||||
<pathelement location="../org.springframework.web"/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="org.springframework.jms">
|
||||
<property file="${basedir}/../build.properties"/>
|
||||
<import file="${basedir}/../build-spring-framework/package-bundle.xml"/>
|
||||
<import file="${basedir}/../spring-build/standard/default.xml"/>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml-stylesheet type="text/xsl" href="http://ivyrep.jayasoft.org/ivy-doc.xsl"?>
|
||||
<ivy-module
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://incubator.apache.org/ivy/schemas/ivy.xsd"
|
||||
version="1.3">
|
||||
|
||||
<info organisation="org.springframework" module="org.springframework.jms"/>
|
||||
|
||||
<configurations>
|
||||
<include file="${spring.build.dir}/common/default-ivy-configurations.xml"/>
|
||||
<conf name="commons-pool" extends="runtime" description="JARs needed to run with Commons Pool"/>
|
||||
<conf name="jca" extends="runtime" description="JARs needed to develop JCA beans"/>
|
||||
</configurations>
|
||||
|
||||
<publications>
|
||||
<artifact name="${ant.project.name}"/>
|
||||
<artifact name="${ant.project.name}-sources" type="src" ext="jar"/>
|
||||
</publications>
|
||||
|
||||
<dependencies>
|
||||
<dependency org="javax.jms" name="com.springsource.javax.jms" rev="1.1.0" conf="provided->compile"/>
|
||||
<dependency org="javax.resource" name="com.springsource.javax.resource" rev="1.5.0" conf="provided, jca->compile"/>
|
||||
<dependency org="javax.transaction" name="com.springsource.javax.transaction" rev="1.1.0" conf="provided->compile"/>
|
||||
<dependency org="org.aopalliance" name="com.springsource.org.aopalliance" rev="1.0.0" conf="compile->compile"/>
|
||||
<dependency org="org.apache.commons" name="com.springsource.org.apache.commons.logging" rev="1.1.1" conf="compile->compile"/>
|
||||
<dependency org="org.apache.commons" name="com.springsource.org.apache.commons.pool" rev="1.3.0" conf="optional, commons-pool->compile"/>
|
||||
<dependency org="org.springframework" name="org.springframework.aop" rev="latest.integration" conf="compile->compile"/>
|
||||
<dependency org="org.springframework" name="org.springframework.beans" rev="latest.integration" conf="compile->compile"/>
|
||||
<dependency org="org.springframework" name="org.springframework.context" rev="latest.integration" conf="compile->compile"/>
|
||||
<dependency org="org.springframework" name="org.springframework.core" rev="latest.integration" conf="compile->compile"/>
|
||||
<dependency org="org.springframework" name="org.springframework.transaction" rev="latest.integration" conf="optional, jca->compile"/>
|
||||
</dependencies>
|
||||
|
||||
</ivy-module>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>org.springframework.parent</artifactId>
|
||||
<version>3.0-M1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>org.springframework.jdbc</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Framework: JDBC</name>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>org.springframework.core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>org.springframework.beans</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>org.springframework.context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>org.springframework.transaction</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.transaction</groupId>
|
||||
<artifactId>com.springsource.javax.transaction</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mchange.c3p0</groupId>
|
||||
<artifactId>com.springsource.com.mchange.v2.c3p0</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.experlog.xapool</groupId>
|
||||
<artifactId>com.springsource.org.enhydra.jdbc</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms;
|
||||
|
||||
import javax.jms.JMSException;
|
||||
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
|
||||
/**
|
||||
* Base class for exception thrown by the framework whenever it
|
||||
* encounters a problem related to JMS.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
*/
|
||||
public abstract class JmsException extends NestedRuntimeException {
|
||||
|
||||
/**
|
||||
* Constructor that takes a message.
|
||||
* @param msg the detail message
|
||||
*/
|
||||
public JmsException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that takes a message and a root cause.
|
||||
* @param msg the detail message
|
||||
* @param cause the cause of the exception. This argument is generally
|
||||
* expected to be a proper subclass of {@link javax.jms.JMSException},
|
||||
* but can also be a JNDI NamingException or the like.
|
||||
*/
|
||||
public JmsException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that takes a plain root cause, intended for
|
||||
* subclasses mirroring corresponding <code>javax.jms</code> exceptions.
|
||||
* @param cause the cause of the exception. This argument is generally
|
||||
* expected to be a proper subclass of {@link javax.jms.JMSException}.
|
||||
*/
|
||||
public JmsException(Throwable cause) {
|
||||
super(cause != null ? cause.getMessage() : null, cause);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method to get the vendor specific error code if
|
||||
* the root cause was an instance of JMSException.
|
||||
* @return a string specifying the vendor-specific error code if the
|
||||
* root cause is an instance of JMSException, or <code>null</code>
|
||||
*/
|
||||
public String getErrorCode() {
|
||||
Throwable cause = getCause();
|
||||
if (cause instanceof JMSException) {
|
||||
return ((JMSException) cause).getErrorCode();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the detail message, including the message from the linked exception
|
||||
* if there is one.
|
||||
* @see javax.jms.JMSException#getLinkedException()
|
||||
*/
|
||||
public String getMessage() {
|
||||
String message = super.getMessage();
|
||||
Throwable cause = getCause();
|
||||
if (cause instanceof JMSException) {
|
||||
Exception linkedEx = ((JMSException) cause).getLinkedException();
|
||||
if (linkedEx != null && cause.getMessage().indexOf(linkedEx.getMessage()) == -1) {
|
||||
message = message + "; nested exception is " + linkedEx;
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.config;
|
||||
|
||||
import javax.jms.Session;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Abstract parser for JMS listener container elements, providing support for
|
||||
* common properties that are identical for all listener container variants.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
*/
|
||||
abstract class AbstractListenerContainerParser implements BeanDefinitionParser {
|
||||
|
||||
protected static final String LISTENER_ELEMENT = "listener";
|
||||
|
||||
protected static final String ID_ATTRIBUTE = "id";
|
||||
|
||||
protected static final String DESTINATION_ATTRIBUTE = "destination";
|
||||
|
||||
protected static final String SUBSCRIPTION_ATTRIBUTE = "subscription";
|
||||
|
||||
protected static final String SELECTOR_ATTRIBUTE = "selector";
|
||||
|
||||
protected static final String REF_ATTRIBUTE = "ref";
|
||||
|
||||
protected static final String METHOD_ATTRIBUTE = "method";
|
||||
|
||||
protected static final String DESTINATION_RESOLVER_ATTRIBUTE = "destination-resolver";
|
||||
|
||||
protected static final String MESSAGE_CONVERTER_ATTRIBUTE = "message-converter";
|
||||
|
||||
protected static final String RESPONSE_DESTINATION_ATTRIBUTE = "response-destination";
|
||||
|
||||
protected static final String DESTINATION_TYPE_ATTRIBUTE = "destination-type";
|
||||
|
||||
protected static final String DESTINATION_TYPE_QUEUE = "queue";
|
||||
|
||||
protected static final String DESTINATION_TYPE_TOPIC = "topic";
|
||||
|
||||
protected static final String DESTINATION_TYPE_DURABLE_TOPIC = "durableTopic";
|
||||
|
||||
protected static final String CLIENT_ID_ATTRIBUTE = "client-id";
|
||||
|
||||
protected static final String ACKNOWLEDGE_ATTRIBUTE = "acknowledge";
|
||||
|
||||
protected static final String ACKNOWLEDGE_AUTO = "auto";
|
||||
|
||||
protected static final String ACKNOWLEDGE_CLIENT = "client";
|
||||
|
||||
protected static final String ACKNOWLEDGE_DUPS_OK = "dups-ok";
|
||||
|
||||
protected static final String ACKNOWLEDGE_TRANSACTED = "transacted";
|
||||
|
||||
protected static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";
|
||||
|
||||
protected static final String CONCURRENCY_ATTRIBUTE = "concurrency";
|
||||
|
||||
protected static final String PREFETCH_ATTRIBUTE = "prefetch";
|
||||
|
||||
|
||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
CompositeComponentDefinition compositeDef =
|
||||
new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
|
||||
parserContext.pushContainingComponent(compositeDef);
|
||||
|
||||
NodeList childNodes = element.getChildNodes();
|
||||
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||
Node child = childNodes.item(i);
|
||||
if (child.getNodeType() == Node.ELEMENT_NODE) {
|
||||
String localName = child.getLocalName();
|
||||
if (LISTENER_ELEMENT.equals(localName)) {
|
||||
parseListener((Element) child, element, parserContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parserContext.popAndRegisterContainingComponent();
|
||||
return null;
|
||||
}
|
||||
|
||||
private void parseListener(Element listenerEle, Element containerEle, ParserContext parserContext) {
|
||||
RootBeanDefinition listenerDef = new RootBeanDefinition();
|
||||
listenerDef.setSource(parserContext.extractSource(listenerEle));
|
||||
|
||||
String ref = listenerEle.getAttribute(REF_ATTRIBUTE);
|
||||
if (!StringUtils.hasText(ref)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Listener 'ref' attribute contains empty value.", listenerEle);
|
||||
}
|
||||
listenerDef.getPropertyValues().addPropertyValue("delegate", new RuntimeBeanReference(ref));
|
||||
|
||||
String method = null;
|
||||
if (listenerEle.hasAttribute(METHOD_ATTRIBUTE)) {
|
||||
method = listenerEle.getAttribute(METHOD_ATTRIBUTE);
|
||||
if (!StringUtils.hasText(method)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Listener 'method' attribute contains empty value.", listenerEle);
|
||||
}
|
||||
}
|
||||
listenerDef.getPropertyValues().addPropertyValue("defaultListenerMethod", method);
|
||||
|
||||
if (containerEle.hasAttribute(MESSAGE_CONVERTER_ATTRIBUTE)) {
|
||||
String messageConverter = containerEle.getAttribute(MESSAGE_CONVERTER_ATTRIBUTE);
|
||||
listenerDef.getPropertyValues().addPropertyValue("messageConverter",
|
||||
new RuntimeBeanReference(messageConverter));
|
||||
}
|
||||
|
||||
BeanDefinition containerDef = parseContainer(listenerEle, containerEle, parserContext);
|
||||
|
||||
if (listenerEle.hasAttribute(RESPONSE_DESTINATION_ATTRIBUTE)) {
|
||||
String responseDestination = listenerEle.getAttribute(RESPONSE_DESTINATION_ATTRIBUTE);
|
||||
boolean pubSubDomain = indicatesPubSub(containerDef);
|
||||
listenerDef.getPropertyValues().addPropertyValue(
|
||||
pubSubDomain ? "defaultResponseTopicName" : "defaultResponseQueueName", responseDestination);
|
||||
if (containerDef.getPropertyValues().contains("destinationResolver")) {
|
||||
listenerDef.getPropertyValues().addPropertyValue("destinationResolver",
|
||||
containerDef.getPropertyValues().getPropertyValue("destinationResolver").getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Remain JMS 1.0.2 compatible for the adapter if the container class indicates this.
|
||||
boolean jms102 = indicatesJms102(containerDef);
|
||||
listenerDef.setBeanClassName(
|
||||
"org.springframework.jms.listener.adapter.MessageListenerAdapter" + (jms102 ? "102" : ""));
|
||||
|
||||
containerDef.getPropertyValues().addPropertyValue("messageListener", listenerDef);
|
||||
|
||||
String containerBeanName = listenerEle.getAttribute(ID_ATTRIBUTE);
|
||||
// If no bean id is given auto generate one using the ReaderContext's BeanNameGenerator
|
||||
if (!StringUtils.hasText(containerBeanName)) {
|
||||
containerBeanName = parserContext.getReaderContext().generateBeanName(containerDef);
|
||||
}
|
||||
|
||||
// Register the listener and fire event
|
||||
parserContext.registerBeanComponent(new BeanComponentDefinition(containerDef, containerBeanName));
|
||||
}
|
||||
|
||||
protected abstract BeanDefinition parseContainer(
|
||||
Element listenerEle, Element containerEle, ParserContext parserContext);
|
||||
|
||||
protected boolean indicatesPubSub(BeanDefinition containerDef) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean indicatesJms102(BeanDefinition containerDef) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void parseListenerConfiguration(Element ele, ParserContext parserContext, BeanDefinition configDef) {
|
||||
String destination = ele.getAttribute(DESTINATION_ATTRIBUTE);
|
||||
if (!StringUtils.hasText(destination)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Listener 'destination' attribute contains empty value.", ele);
|
||||
}
|
||||
configDef.getPropertyValues().addPropertyValue("destinationName", destination);
|
||||
|
||||
if (ele.hasAttribute(SUBSCRIPTION_ATTRIBUTE)) {
|
||||
String subscription = ele.getAttribute(SUBSCRIPTION_ATTRIBUTE);
|
||||
if (!StringUtils.hasText(subscription)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Listener 'subscription' attribute contains empty value.", ele);
|
||||
}
|
||||
configDef.getPropertyValues().addPropertyValue("durableSubscriptionName", subscription);
|
||||
}
|
||||
|
||||
if (ele.hasAttribute(SELECTOR_ATTRIBUTE)) {
|
||||
String selector = ele.getAttribute(SELECTOR_ATTRIBUTE);
|
||||
if (!StringUtils.hasText(selector)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Listener 'selector' attribute contains empty value.", ele);
|
||||
}
|
||||
configDef.getPropertyValues().addPropertyValue("messageSelector", selector);
|
||||
}
|
||||
}
|
||||
|
||||
protected void parseContainerConfiguration(Element ele, ParserContext parserContext, BeanDefinition configDef) {
|
||||
String destinationType = ele.getAttribute(DESTINATION_TYPE_ATTRIBUTE);
|
||||
boolean pubSubDomain = false;
|
||||
boolean subscriptionDurable = false;
|
||||
if (DESTINATION_TYPE_DURABLE_TOPIC.equals(destinationType)) {
|
||||
pubSubDomain = true;
|
||||
subscriptionDurable = true;
|
||||
}
|
||||
else if (DESTINATION_TYPE_TOPIC.equals(destinationType)) {
|
||||
pubSubDomain = true;
|
||||
}
|
||||
else if ("".equals(destinationType) || DESTINATION_TYPE_QUEUE.equals(destinationType)) {
|
||||
// the default: queue
|
||||
}
|
||||
else {
|
||||
parserContext.getReaderContext().error("Invalid listener container 'destination-type': " +
|
||||
"only \"queue\", \"topic\" and \"durableTopic\" supported.", ele);
|
||||
}
|
||||
configDef.getPropertyValues().addPropertyValue("pubSubDomain", Boolean.valueOf(pubSubDomain));
|
||||
configDef.getPropertyValues().addPropertyValue("subscriptionDurable", Boolean.valueOf(subscriptionDurable));
|
||||
|
||||
if (ele.hasAttribute(CLIENT_ID_ATTRIBUTE)) {
|
||||
String clientId = ele.getAttribute(CLIENT_ID_ATTRIBUTE);
|
||||
if (!StringUtils.hasText(clientId)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Listener 'client-id' attribute contains empty value.", ele);
|
||||
}
|
||||
configDef.getPropertyValues().addPropertyValue("clientId", clientId);
|
||||
}
|
||||
}
|
||||
|
||||
protected Integer parseAcknowledgeMode(Element ele, ParserContext parserContext) {
|
||||
String acknowledge = ele.getAttribute(ACKNOWLEDGE_ATTRIBUTE);
|
||||
if (StringUtils.hasText(acknowledge)) {
|
||||
int acknowledgeMode = Session.AUTO_ACKNOWLEDGE;
|
||||
if (ACKNOWLEDGE_TRANSACTED.equals(acknowledge)) {
|
||||
acknowledgeMode = Session.SESSION_TRANSACTED;
|
||||
}
|
||||
else if (ACKNOWLEDGE_DUPS_OK.equals(acknowledge)) {
|
||||
acknowledgeMode = Session.DUPS_OK_ACKNOWLEDGE;
|
||||
}
|
||||
else if (ACKNOWLEDGE_CLIENT.equals(acknowledge)) {
|
||||
acknowledgeMode = Session.CLIENT_ACKNOWLEDGE;
|
||||
}
|
||||
else if (!ACKNOWLEDGE_AUTO.equals(acknowledge)) {
|
||||
parserContext.getReaderContext().error("Invalid listener container 'acknowledge' setting [" +
|
||||
acknowledge + "]: only \"auto\", \"client\", \"dups-ok\" and \"transacted\" supported.", ele);
|
||||
}
|
||||
return new Integer(acknowledgeMode);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean indicatesPubSubConfig(BeanDefinition configDef) {
|
||||
return ((Boolean) configDef.getPropertyValues().getPropertyValue("pubSubDomain").getValue()).booleanValue();
|
||||
}
|
||||
|
||||
protected int[] parseConcurrency(Element ele, ParserContext parserContext) {
|
||||
String concurrency = ele.getAttribute(CONCURRENCY_ATTRIBUTE);
|
||||
if (!StringUtils.hasText(concurrency)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
int separatorIndex = concurrency.indexOf('-');
|
||||
if (separatorIndex != -1) {
|
||||
int[] result = new int[2];
|
||||
result[0] = Integer.parseInt(concurrency.substring(0, separatorIndex));
|
||||
result[1] = Integer.parseInt(concurrency.substring(separatorIndex + 1, concurrency.length()));
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return new int[] {1, Integer.parseInt(concurrency)};
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
parserContext.getReaderContext().error("Invalid concurrency value [" + concurrency + "]: only " +
|
||||
"single maximum integer (e.g. \"5\") and minimum-maximum combo (e.g. \"3-5\") supported.", ele, ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Parser for the JMS <code><jca-listener-container></code> element.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
*/
|
||||
class JcaListenerContainerParser extends AbstractListenerContainerParser {
|
||||
|
||||
private static final String RESOURCE_ADAPTER_ATTRIBUTE = "resource-adapter";
|
||||
|
||||
private static final String ACTIVATION_SPEC_FACTORY_ATTRIBUTE = "activation-spec-factory";
|
||||
|
||||
|
||||
protected BeanDefinition parseContainer(Element listenerEle, Element containerEle, ParserContext parserContext) {
|
||||
RootBeanDefinition containerDef = new RootBeanDefinition();
|
||||
containerDef.setSource(parserContext.extractSource(containerEle));
|
||||
containerDef.setBeanClassName("org.springframework.jms.listener.endpoint.JmsMessageEndpointManager");
|
||||
|
||||
String resourceAdapterBeanName = "resourceAdapter";
|
||||
if (containerEle.hasAttribute(RESOURCE_ADAPTER_ATTRIBUTE)) {
|
||||
resourceAdapterBeanName = containerEle.getAttribute(RESOURCE_ADAPTER_ATTRIBUTE);
|
||||
if (!StringUtils.hasText(resourceAdapterBeanName)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Listener container 'resource-adapter' attribute contains empty value.", containerEle);
|
||||
}
|
||||
}
|
||||
containerDef.getPropertyValues().addPropertyValue("resourceAdapter",
|
||||
new RuntimeBeanReference(resourceAdapterBeanName));
|
||||
|
||||
String activationSpecFactoryBeanName = containerEle.getAttribute(ACTIVATION_SPEC_FACTORY_ATTRIBUTE);
|
||||
String destinationResolverBeanName = containerEle.getAttribute(DESTINATION_RESOLVER_ATTRIBUTE);
|
||||
if (StringUtils.hasText(activationSpecFactoryBeanName)) {
|
||||
if (StringUtils.hasText(destinationResolverBeanName)) {
|
||||
parserContext.getReaderContext().error("Specify either 'activation-spec-factory' or " +
|
||||
"'destination-resolver', not both. If you define a dedicated JmsActivationSpecFactory bean, " +
|
||||
"specify the custom DestinationResolver there (if possible).", containerEle);
|
||||
}
|
||||
containerDef.getPropertyValues().addPropertyValue("activationSpecFactory",
|
||||
new RuntimeBeanReference(activationSpecFactoryBeanName));
|
||||
}
|
||||
if (StringUtils.hasText(destinationResolverBeanName)) {
|
||||
containerDef.getPropertyValues().addPropertyValue("destinationResolver",
|
||||
new RuntimeBeanReference(destinationResolverBeanName));
|
||||
}
|
||||
|
||||
RootBeanDefinition configDef = new RootBeanDefinition();
|
||||
configDef.setSource(parserContext.extractSource(configDef));
|
||||
configDef.setBeanClassName("org.springframework.jms.listener.endpoint.JmsActivationSpecConfig");
|
||||
|
||||
parseListenerConfiguration(listenerEle, parserContext, configDef);
|
||||
parseContainerConfiguration(containerEle, parserContext, configDef);
|
||||
|
||||
Integer acknowledgeMode = parseAcknowledgeMode(containerEle, parserContext);
|
||||
if (acknowledgeMode != null) {
|
||||
configDef.getPropertyValues().addPropertyValue("acknowledgeMode", acknowledgeMode);
|
||||
}
|
||||
|
||||
String transactionManagerBeanName = containerEle.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE);
|
||||
if (StringUtils.hasText(transactionManagerBeanName)) {
|
||||
containerDef.getPropertyValues().addPropertyValue("transactionManager",
|
||||
new RuntimeBeanReference(transactionManagerBeanName));
|
||||
}
|
||||
|
||||
int[] concurrency = parseConcurrency(containerEle, parserContext);
|
||||
if (concurrency != null) {
|
||||
configDef.getPropertyValues().addPropertyValue("maxConcurrency", new Integer(concurrency[1]));
|
||||
}
|
||||
|
||||
String prefetch = containerEle.getAttribute(PREFETCH_ATTRIBUTE);
|
||||
if (StringUtils.hasText(prefetch)) {
|
||||
configDef.getPropertyValues().addPropertyValue("prefetchSize", new Integer(prefetch));
|
||||
}
|
||||
|
||||
containerDef.getPropertyValues().addPropertyValue("activationSpecConfig", configDef);
|
||||
|
||||
return containerDef;
|
||||
}
|
||||
|
||||
protected boolean indicatesPubSub(BeanDefinition containerDef) {
|
||||
BeanDefinition configDef =
|
||||
(BeanDefinition) containerDef.getPropertyValues().getPropertyValue("activationSpecConfig").getValue();
|
||||
return indicatesPubSubConfig(configDef);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.config;
|
||||
|
||||
import javax.jms.Session;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Parser for the JMS <code><listener-container></code> element.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
*/
|
||||
class JmsListenerContainerParser extends AbstractListenerContainerParser {
|
||||
|
||||
private static final String CONTAINER_TYPE_ATTRIBUTE = "container-type";
|
||||
|
||||
private static final String CONTAINER_CLASS_ATTRIBUTE = "container-class";
|
||||
|
||||
private static final String CONNECTION_FACTORY_ATTRIBUTE = "connection-factory";
|
||||
|
||||
private static final String TASK_EXECUTOR_ATTRIBUTE = "task-executor";
|
||||
|
||||
private static final String CACHE_ATTRIBUTE = "cache";
|
||||
|
||||
|
||||
protected BeanDefinition parseContainer(Element listenerEle, Element containerEle, ParserContext parserContext) {
|
||||
RootBeanDefinition containerDef = new RootBeanDefinition();
|
||||
containerDef.setSource(parserContext.extractSource(containerEle));
|
||||
|
||||
parseListenerConfiguration(listenerEle, parserContext, containerDef);
|
||||
parseContainerConfiguration(containerEle, parserContext, containerDef);
|
||||
|
||||
String containerType = containerEle.getAttribute(CONTAINER_TYPE_ATTRIBUTE);
|
||||
String containerClass = containerEle.getAttribute(CONTAINER_CLASS_ATTRIBUTE);
|
||||
if (!"".equals(containerClass)) {
|
||||
containerDef.setBeanClassName(containerClass);
|
||||
}
|
||||
else if ("".equals(containerType) || "default".equals(containerType)) {
|
||||
containerDef.setBeanClassName("org.springframework.jms.listener.DefaultMessageListenerContainer");
|
||||
}
|
||||
else if ("default102".equals(containerType)) {
|
||||
containerDef.setBeanClassName("org.springframework.jms.listener.DefaultMessageListenerContainer102");
|
||||
}
|
||||
else if ("simple".equals(containerType)) {
|
||||
containerDef.setBeanClassName("org.springframework.jms.listener.SimpleMessageListenerContainer");
|
||||
}
|
||||
else if ("simple102".equals(containerType)) {
|
||||
containerDef.setBeanClassName("org.springframework.jms.listener.SimpleMessageListenerContainer102");
|
||||
}
|
||||
else {
|
||||
parserContext.getReaderContext().error(
|
||||
"Invalid 'container-type' attribute: only \"default(102)\" and \"simple(102)\" supported.", containerEle);
|
||||
}
|
||||
|
||||
String connectionFactoryBeanName = "connectionFactory";
|
||||
if (containerEle.hasAttribute(CONNECTION_FACTORY_ATTRIBUTE)) {
|
||||
connectionFactoryBeanName = containerEle.getAttribute(CONNECTION_FACTORY_ATTRIBUTE);
|
||||
if (!StringUtils.hasText(connectionFactoryBeanName)) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Listener container 'connection-factory' attribute contains empty value.", containerEle);
|
||||
}
|
||||
}
|
||||
containerDef.getPropertyValues().addPropertyValue("connectionFactory",
|
||||
new RuntimeBeanReference(connectionFactoryBeanName));
|
||||
|
||||
String taskExecutorBeanName = containerEle.getAttribute(TASK_EXECUTOR_ATTRIBUTE);
|
||||
if (StringUtils.hasText(taskExecutorBeanName)) {
|
||||
containerDef.getPropertyValues().addPropertyValue("taskExecutor",
|
||||
new RuntimeBeanReference(taskExecutorBeanName));
|
||||
}
|
||||
|
||||
String destinationResolverBeanName = containerEle.getAttribute(DESTINATION_RESOLVER_ATTRIBUTE);
|
||||
if (StringUtils.hasText(destinationResolverBeanName)) {
|
||||
containerDef.getPropertyValues().addPropertyValue("destinationResolver",
|
||||
new RuntimeBeanReference(destinationResolverBeanName));
|
||||
}
|
||||
|
||||
String cache = containerEle.getAttribute(CACHE_ATTRIBUTE);
|
||||
if (StringUtils.hasText(cache)) {
|
||||
if (containerType.startsWith("simple")) {
|
||||
if (!("auto".equals(cache) || "consumer".equals(cache))) {
|
||||
parserContext.getReaderContext().warning(
|
||||
"'cache' attribute not actively supported for listener container of type \"simple\". " +
|
||||
"Effective runtime behavior will be equivalent to \"consumer\" / \"auto\".", containerEle);
|
||||
}
|
||||
}
|
||||
else {
|
||||
containerDef.getPropertyValues().addPropertyValue("cacheLevelName", "CACHE_" + cache.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
Integer acknowledgeMode = parseAcknowledgeMode(containerEle, parserContext);
|
||||
if (acknowledgeMode != null) {
|
||||
if (acknowledgeMode.intValue() == Session.SESSION_TRANSACTED) {
|
||||
containerDef.getPropertyValues().addPropertyValue("sessionTransacted", Boolean.TRUE);
|
||||
}
|
||||
else {
|
||||
containerDef.getPropertyValues().addPropertyValue("sessionAcknowledgeMode", acknowledgeMode);
|
||||
}
|
||||
}
|
||||
|
||||
String transactionManagerBeanName = containerEle.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE);
|
||||
if (StringUtils.hasText(transactionManagerBeanName)) {
|
||||
if (containerType.startsWith("simple")) {
|
||||
parserContext.getReaderContext().error(
|
||||
"'transaction-manager' attribute not supported for listener container of type \"simple\".", containerEle);
|
||||
}
|
||||
else {
|
||||
containerDef.getPropertyValues().addPropertyValue("transactionManager",
|
||||
new RuntimeBeanReference(transactionManagerBeanName));
|
||||
}
|
||||
}
|
||||
|
||||
int[] concurrency = parseConcurrency(containerEle, parserContext);
|
||||
if (concurrency != null) {
|
||||
if (containerType.startsWith("default")) {
|
||||
containerDef.getPropertyValues().addPropertyValue("concurrentConsumers", new Integer(concurrency[0]));
|
||||
containerDef.getPropertyValues().addPropertyValue("maxConcurrentConsumers", new Integer(concurrency[1]));
|
||||
}
|
||||
else {
|
||||
containerDef.getPropertyValues().addPropertyValue("concurrentConsumers", new Integer(concurrency[1]));
|
||||
}
|
||||
}
|
||||
|
||||
String prefetch = containerEle.getAttribute(PREFETCH_ATTRIBUTE);
|
||||
if (StringUtils.hasText(prefetch)) {
|
||||
if (containerType.startsWith("default")) {
|
||||
containerDef.getPropertyValues().addPropertyValue("maxMessagesPerTask", new Integer(prefetch));
|
||||
}
|
||||
}
|
||||
|
||||
return containerDef;
|
||||
}
|
||||
|
||||
protected boolean indicatesPubSub(BeanDefinition containerDef) {
|
||||
return indicatesPubSubConfig(containerDef);
|
||||
}
|
||||
|
||||
protected boolean indicatesJms102(BeanDefinition containerDef) {
|
||||
return containerDef.getBeanClassName().endsWith("102");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
Support package for declarative messaging configuration,
|
||||
with XML schema being the primary configuration format.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,439 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<xsd:schema xmlns="http://www.springframework.org/schema/jms"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:tool="http://www.springframework.org/schema/tool"
|
||||
targetNamespace="http://www.springframework.org/schema/jms"
|
||||
elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
|
||||
<xsd:import namespace="http://www.springframework.org/schema/tool"/>
|
||||
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Defines the configuration elements for the Spring Framework's JMS support.
|
||||
Allows for configuring JMS listener containers in XML 'shortcut' style.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
|
||||
<xsd:element name="listener-container">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Each listener child element will be hosted by a container whose configuration
|
||||
is determined by this parent element. This variant builds standard JMS
|
||||
listener containers, operating against a specified JMS ConnectionFactory.
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation>
|
||||
<tool:exports type="org.springframework.jms.listener.AbstractMessageListenerContainer"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="listener" type="listenerType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="container-type" default="default">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The type of this listener container: "default" or "simple", choosing
|
||||
between DefaultMessageListenerContainer and SimpleMessageListenerContainer.
|
||||
The "102" suffix adapts to a JMS provider that implements JMS 1.0.2 only.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:NMTOKEN">
|
||||
<xsd:enumeration value="default"/>
|
||||
<xsd:enumeration value="default102"/>
|
||||
<xsd:enumeration value="simple"/>
|
||||
<xsd:enumeration value="simple102"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="container-class" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A custom listener container implementation class as fully qualified class name.
|
||||
Default is Spring's standard DefaultMessageListenerContainer or
|
||||
SimpleMessageListenerContainer, according to the "container-type" attribute.
|
||||
Note that a custom container class will typically be a subclass of either of
|
||||
those two Spring-provided standard container classes: Nake sure that the
|
||||
"container-type" attribute matches the actual base type that the custom class
|
||||
derives from ("default" will usually be fine anyway, since most custom classes
|
||||
will derive from DefaultMessageListenerContainer).
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation>
|
||||
<tool:expected-type type="java.lang.Class"/>
|
||||
<tool:assignable-to type="org.springframework.jms.listener.AbstractMessageListenerContainer"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="connection-factory" type="xsd:string" default="connectionFactory">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A reference to the JMS ConnectionFactory bean.
|
||||
Default is "connectionFactory".
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="javax.jms.ConnectionFactory"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="task-executor" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A reference to the Spring TaskExecutor for the JMS listener invokers.
|
||||
Default is a SimpleAsyncTaskExecutor, using internally managed threads.
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.core.task.TaskExecutor"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="destination-resolver" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A reference to the DestinationResolver strategy for resolving destination names.
|
||||
Default is a DynamicDestinationResolver, using the JMS provider's queue/topic
|
||||
name resolution. Alternatively, specify a reference to a JndiDestinationResolver
|
||||
(typically in a J2EE environment).
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.jms.support.destination.DestinationResolver"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="message-converter" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A reference to the MessageConverter strategy for converting JMS Messages to
|
||||
listener method arguments. Default is a SimpleMessageConverter.
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.jms.support.converter.MessageConverter"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="destination-type" default="queue">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The JMS destination type for this listener: "queue", "topic" or "durableTopic".
|
||||
The default is "queue".
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:NMTOKEN">
|
||||
<xsd:enumeration value="queue"/>
|
||||
<xsd:enumeration value="topic"/>
|
||||
<xsd:enumeration value="durableTopic"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="client-id" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The JMS client id for this listener container.
|
||||
Needs to be specified when using durable subscriptions.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="cache" default="auto">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The cache level for JMS resources: "none", "connection", "session", "consumer"
|
||||
or "auto". By default ("auto"), the cache level will effectively be "consumer",
|
||||
unless an external transaction manager has been specified - in which case the
|
||||
effective default will be "none" (assuming J2EE-style transaction management
|
||||
where the given ConnectionFactory is an XA-aware pool).
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:NMTOKEN">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="connection"/>
|
||||
<xsd:enumeration value="session"/>
|
||||
<xsd:enumeration value="consumer"/>
|
||||
<xsd:enumeration value="auto"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="acknowledge" default="auto">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The native JMS acknowledge mode: "auto", "client", "dups-ok" or "transacted".
|
||||
A value of "transacted" effectively activates a locally transacted Session;
|
||||
as alternative, specify an external "transaction-manager" via the corresponding
|
||||
attribute. Default is "auto".
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:NMTOKEN">
|
||||
<xsd:enumeration value="auto"/>
|
||||
<xsd:enumeration value="client"/>
|
||||
<xsd:enumeration value="dups-ok"/>
|
||||
<xsd:enumeration value="transacted"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="transaction-manager" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A reference to an external PlatformTransactionManager (typically an
|
||||
XA-based transaction coordinator, e.g. Spring's JtaTransactionManager).
|
||||
If not specified, native acknowledging will be used (see "acknowledge" attribute).
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.transaction.PlatformTransactionManager"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="concurrency" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The number of concurrent sessions/consumers to start for each listener.
|
||||
Can either be a simple number indicating the maximum number (e.g. "5")
|
||||
or a range indicating the lower as well as the upper limit (e.g. "3-5").
|
||||
Note that a specified minimum is just a hint and might be ignored at runtime.
|
||||
Default is 1; keep concurrency limited to 1 in case of a topic listener
|
||||
or if message ordering is important; consider raising it for general queues.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="prefetch" type="xsd:int">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The maximum number of messages to load into a single session.
|
||||
Note that raising this number might lead to starvation of concurrent consumers!
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="jca-listener-container">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Each listener child element will be hosted by a container whose configuration
|
||||
is determined by this parent element. This variant builds standard JCA-based
|
||||
listener containers, operating against a specified JCA ResourceAdapter
|
||||
(which needs to be provided by the JMS message broker, e.g. ActiveMQ).
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation>
|
||||
<tool:exports type="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="listener" type="listenerType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="resource-adapter" type="xsd:string" default="resourceAdapter">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A reference to the JCA ResourceAdapter bean for the JMS provider.
|
||||
Default is "resourceAdapter".
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="javax.resource.spi.ResourceAdapter"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="activation-spec-factory" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A reference to the JmsActivationSpecFactory.
|
||||
Default is to autodetect the JMS provider and its ActivationSpec class
|
||||
(see DefaultJmsActivationSpecFactory).
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.jms.listener.endpoint.JmsActivationSpecFactory"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="destination-resolver" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A reference to the DestinationResolver strategy for resolving destination names.
|
||||
Default is to pass in the destination name Strings into the JCA ActivationSpec as-is.
|
||||
Alternatively, specify a reference to a JndiDestinationResolver (typically in a J2EE
|
||||
environment, in particular if the server insists on receiving Destination objects).
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.jms.support.destination.DestinationResolver"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="message-converter" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A reference to the MessageConverter strategy for converting JMS Messages to
|
||||
listener method arguments. Default is a SimpleMessageConverter.
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.jms.support.converter.MessageConverter"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="destination-type" default="queue">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The JMS destination type for this listener: "queue", "topic" or "durableTopic".
|
||||
Default is "queue".
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:NMTOKEN">
|
||||
<xsd:enumeration value="queue"/>
|
||||
<xsd:enumeration value="topic"/>
|
||||
<xsd:enumeration value="durableTopic"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="client-id" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The JMS client id for this listener container.
|
||||
Needs to be specified when using durable subscriptions.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="acknowledge" default="auto">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The native JMS acknowledge mode: "auto", "client", "dups-ok" or "transacted".
|
||||
A value of "transacted" effectively activates a locally transacted Session;
|
||||
as alternative, specify an external "transaction-manager" via the corresponding
|
||||
attribute. Default is "auto".
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:restriction base="xsd:NMTOKEN">
|
||||
<xsd:enumeration value="auto"/>
|
||||
<xsd:enumeration value="client"/>
|
||||
<xsd:enumeration value="dups-ok"/>
|
||||
<xsd:enumeration value="transacted"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="transaction-manager" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
A reference to the Spring JtaTransactionManager or [javax.transaction.TransactionManager],
|
||||
for kicking off an XA transaction for each incoming message.
|
||||
If not specified, native acknowledging will be used (see "acknowledge" attribute).
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref"/>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="concurrency" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The number of concurrent sessions/consumers to start for each listener.
|
||||
Can either be a simple number indicating the maximum number (e.g. "5")
|
||||
or a range indicating the lower as well as the upper limit (e.g. "3-5").
|
||||
Note that a specified minimum is just a hint and will typically be ignored
|
||||
at runtime when using a JCA listener container. Default is 1.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="prefetch" type="xsd:int">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The maximum number of messages to load into a single session.
|
||||
Note that raising this number might lead to starvation of concurrent consumers!
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:complexType name="listenerType">
|
||||
<xsd:attribute name="id" type="xsd:ID">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The unique identifier for a listener.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="destination" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The destination name for this listener, resolved through the
|
||||
container-wide DestinationResolver strategy (if any). Required.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="subscription" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The name for the durable subscription, if any.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="selector" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The JMS message selector for this listener.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="ref" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The bean name of the listener object, implementing
|
||||
the MessageListener/SessionAwareMessageListener interface
|
||||
or defining the specified listener method. Required.
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref"/>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="method" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The name of the listener method to invoke. If not specified,
|
||||
the target bean is supposed to implement the MessageListener
|
||||
or SessionAwareMessageListener interface.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="response-destination" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The name of the default response destination to send response messages to.
|
||||
This will be applied in case of a request message that does not carry
|
||||
a "JMSReplyTo" field. The type of this destination will be determined
|
||||
by the listener-container's "destination-type" attribute.
|
||||
Note: This only applies to a listener method with a return value,
|
||||
for which each result object will be converted into a response message.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
|
||||
</xsd:schema>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,468 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.connection;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.MessageConsumer;
|
||||
import javax.jms.MessageProducer;
|
||||
import javax.jms.QueueSession;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.Topic;
|
||||
import javax.jms.TopicSession;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* {@link SingleConnectionFactory} subclass that adds {@link javax.jms.Session}
|
||||
* caching as well {@link javax.jms.MessageProducer} caching. This ConnectionFactory
|
||||
* also switches the {@link #setReconnectOnException "reconnectOnException" property}
|
||||
* to "true" by default, allowing for automatic recovery of the underlying Connection.
|
||||
*
|
||||
* <p>By default, only one single Session will be cached, with further requested
|
||||
* Sessions being created and disposed on demand. Consider raising the
|
||||
* {@link #setSessionCacheSize "sessionCacheSize" value} in case of a
|
||||
* high-concurrency environment.
|
||||
*
|
||||
* <p><b>NOTE: This ConnectionFactory decorator requires JMS 1.1 or higher.</b>
|
||||
* You may use it through the JMS 1.0.2 API; however, the target JMS driver
|
||||
* needs to be compliant with JMS 1.1.
|
||||
*
|
||||
* <p>When using the JMS 1.0.2 API, this ConnectionFactory will switch
|
||||
* into queue/topic mode according to the JMS API methods used at runtime:
|
||||
* <code>createQueueConnection</code> and <code>createTopicConnection</code> will
|
||||
* lead to queue/topic mode, respectively; generic <code>createConnection</code>
|
||||
* calls will lead to a JMS 1.1 connection which is able to serve both modes.
|
||||
*
|
||||
* <p><b>NOTE: This ConnectionFactory requires explicit closing of all Sessions
|
||||
* obtained from its shared Connection.</b> This is the usual recommendation for
|
||||
* native JMS access code anyway. However, with this ConnectionFactory, its use
|
||||
* is mandatory in order to actually allow for Session reuse.
|
||||
*
|
||||
* <p>Note also that MessageConsumers obtained from a cached Session won't get
|
||||
* closed until the Session will eventually be removed from the pool. This may
|
||||
* lead to semantic side effects in some cases. For a durable subscriber, the
|
||||
* logical <code>Session.close()</code> call will also close the subscription.
|
||||
* Re-registering a durable consumer for the same subscription on the same
|
||||
* Session handle is not supported; close and reobtain a cached Session first.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5.3
|
||||
*/
|
||||
public class CachingConnectionFactory extends SingleConnectionFactory {
|
||||
|
||||
private int sessionCacheSize = 1;
|
||||
|
||||
private boolean cacheProducers = true;
|
||||
|
||||
private boolean cacheConsumers = true;
|
||||
|
||||
private volatile boolean active = true;
|
||||
|
||||
private final Map cachedSessions = new HashMap();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new CachingConnectionFactory for bean-style usage.
|
||||
* @see #setTargetConnectionFactory
|
||||
*/
|
||||
public CachingConnectionFactory() {
|
||||
super();
|
||||
setReconnectOnException(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CachingConnectionFactory for the given target
|
||||
* ConnectionFactory.
|
||||
* @param targetConnectionFactory the target ConnectionFactory
|
||||
*/
|
||||
public CachingConnectionFactory(ConnectionFactory targetConnectionFactory) {
|
||||
super(targetConnectionFactory);
|
||||
setReconnectOnException(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specify the desired size for the JMS Session cache (per JMS Session type).
|
||||
* <p>This cache size is the maximum limit for the number of cached Sessions
|
||||
* per session acknowledgement type (auto, client, dups_ok, transacted).
|
||||
* As a consequence, the actual number of cached Sessions may be up to
|
||||
* four times as high as the specified value - in the unlikely case
|
||||
* of mixing and matching different acknowledgement types.
|
||||
* <p>Default is 1: caching a single Session, (re-)creating further ones on
|
||||
* demand. Specify a number like 10 if you'd like to raise the number of cached
|
||||
* Sessions; that said, 1 may be sufficient for low-concurrency scenarios.
|
||||
* @see #setCacheProducers
|
||||
*/
|
||||
public void setSessionCacheSize(int sessionCacheSize) {
|
||||
Assert.isTrue(sessionCacheSize >= 1, "Session cache size must be 1 or higher");
|
||||
this.sessionCacheSize = sessionCacheSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the desired size for the JMS Session cache (per JMS Session type).
|
||||
*/
|
||||
public int getSessionCacheSize() {
|
||||
return this.sessionCacheSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether to cache JMS MessageProducers per JMS Session instance
|
||||
* (more specifically: one MessageProducer per Destination and Session).
|
||||
* <p>Default is "true". Switch this to "false" in order to always
|
||||
* recreate MessageProducers on demand.
|
||||
*/
|
||||
public void setCacheProducers(boolean cacheProducers) {
|
||||
this.cacheProducers = cacheProducers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to cache JMS MessageProducers per JMS Session instance.
|
||||
*/
|
||||
public boolean isCacheProducers() {
|
||||
return this.cacheProducers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether to cache JMS MessageConsumers per JMS Session instance
|
||||
* (more specifically: one MessageConsumer per Destination, selector String
|
||||
* and Session). Note that durable subscribers will only be cached until
|
||||
* logical closing of the Session handle.
|
||||
* <p>Default is "true". Switch this to "false" in order to always
|
||||
* recreate MessageConsumers on demand.
|
||||
*/
|
||||
public void setCacheConsumers(boolean cacheConsumers) {
|
||||
this.cacheConsumers = cacheConsumers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to cache JMS MessageConsumers per JMS Session instance.
|
||||
*/
|
||||
public boolean isCacheConsumers() {
|
||||
return this.cacheConsumers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resets the Session cache as well.
|
||||
*/
|
||||
public void resetConnection() {
|
||||
this.active = false;
|
||||
synchronized (this.cachedSessions) {
|
||||
for (Iterator it = this.cachedSessions.values().iterator(); it.hasNext();) {
|
||||
LinkedList sessionList = (LinkedList) it.next();
|
||||
synchronized (sessionList) {
|
||||
for (Iterator it2 = sessionList.iterator(); it2.hasNext();) {
|
||||
Session session = (Session) it2.next();
|
||||
try {
|
||||
session.close();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.trace("Could not close cached JMS Session", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.cachedSessions.clear();
|
||||
}
|
||||
this.active = true;
|
||||
|
||||
// Now proceed with actual closing of the shared Connection...
|
||||
super.resetConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a cached Session for the given mode.
|
||||
*/
|
||||
protected Session getSession(Connection con, Integer mode) throws JMSException {
|
||||
LinkedList sessionList = null;
|
||||
synchronized (this.cachedSessions) {
|
||||
sessionList = (LinkedList) this.cachedSessions.get(mode);
|
||||
if (sessionList == null) {
|
||||
sessionList = new LinkedList();
|
||||
this.cachedSessions.put(mode, sessionList);
|
||||
}
|
||||
}
|
||||
Session session = null;
|
||||
synchronized (sessionList) {
|
||||
if (!sessionList.isEmpty()) {
|
||||
session = (Session) sessionList.removeFirst();
|
||||
}
|
||||
}
|
||||
if (session != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Found cached JMS Session for mode " + mode + ": " +
|
||||
(session instanceof SessionProxy ? ((SessionProxy) session).getTargetSession() : session));
|
||||
}
|
||||
}
|
||||
else {
|
||||
Session targetSession = createSession(con, mode);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Creating cached JMS Session for mode " + mode + ": " + targetSession);
|
||||
}
|
||||
session = getCachedSessionProxy(targetSession, sessionList);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the given Session with a proxy that delegates every method call to it
|
||||
* but adapts close calls. This is useful for allowing application code to
|
||||
* handle a special framework Session just like an ordinary Session.
|
||||
* @param target the original Session to wrap
|
||||
* @param sessionList the List of cached Sessions that the given Session belongs to
|
||||
* @return the wrapped Session
|
||||
*/
|
||||
protected Session getCachedSessionProxy(Session target, LinkedList sessionList) {
|
||||
List classes = new ArrayList(3);
|
||||
classes.add(SessionProxy.class);
|
||||
if (target instanceof QueueSession) {
|
||||
classes.add(QueueSession.class);
|
||||
}
|
||||
if (target instanceof TopicSession) {
|
||||
classes.add(TopicSession.class);
|
||||
}
|
||||
return (Session) Proxy.newProxyInstance(
|
||||
SessionProxy.class.getClassLoader(),
|
||||
(Class[]) classes.toArray(new Class[classes.size()]),
|
||||
new CachedSessionInvocationHandler(target, sessionList));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invocation handler for a cached JMS Session proxy.
|
||||
*/
|
||||
private class CachedSessionInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final Session target;
|
||||
|
||||
private final LinkedList sessionList;
|
||||
|
||||
private final Map cachedProducers = new HashMap();
|
||||
|
||||
private final Map cachedConsumers = new HashMap();
|
||||
|
||||
private boolean transactionOpen = false;
|
||||
|
||||
public CachedSessionInvocationHandler(Session target, LinkedList sessionList) {
|
||||
this.target = target;
|
||||
this.sessionList = sessionList;
|
||||
}
|
||||
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
String methodName = method.getName();
|
||||
if (methodName.equals("equals")) {
|
||||
// Only consider equal when proxies are identical.
|
||||
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
|
||||
}
|
||||
else if (methodName.equals("hashCode")) {
|
||||
// Use hashCode of Session proxy.
|
||||
return new Integer(System.identityHashCode(proxy));
|
||||
}
|
||||
else if (methodName.equals("toString")) {
|
||||
return "Cached JMS Session: " + this.target;
|
||||
}
|
||||
else if (methodName.equals("close")) {
|
||||
// Handle close method: don't pass the call on.
|
||||
if (active) {
|
||||
synchronized (this.sessionList) {
|
||||
if (this.sessionList.size() < getSessionCacheSize()) {
|
||||
logicalClose(proxy);
|
||||
// Remain open in the session list.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we get here, we're supposed to shut down.
|
||||
physicalClose();
|
||||
return null;
|
||||
}
|
||||
else if (methodName.equals("getTargetSession")) {
|
||||
// Handle getTargetSession method: return underlying Session.
|
||||
return this.target;
|
||||
}
|
||||
else if (methodName.equals("commit") || methodName.equals("rollback")) {
|
||||
this.transactionOpen = false;
|
||||
}
|
||||
else {
|
||||
this.transactionOpen = true;
|
||||
if ((methodName.equals("createProducer") || methodName.equals("createSender") ||
|
||||
methodName.equals("createPublisher")) && isCacheProducers()) {
|
||||
return getCachedProducer((Destination) args[0]);
|
||||
}
|
||||
else if ((methodName.equals("createConsumer") || methodName.equals("createReceiver") ||
|
||||
methodName.equals("createSubscriber")) && isCacheConsumers()) {
|
||||
return getCachedConsumer((Destination) args[0], (args.length > 1 ? (String) args[1] : null),
|
||||
(args.length > 2 && ((Boolean) args[2]).booleanValue()), null);
|
||||
}
|
||||
else if (methodName.equals("createDurableSubscriber") && isCacheConsumers()) {
|
||||
return getCachedConsumer((Destination) args[0], (args.length > 2 ? (String) args[2] : null),
|
||||
(args.length > 3 && ((Boolean) args[3]).booleanValue()), (String) args[1]);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return method.invoke(this.target, args);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw ex.getTargetException();
|
||||
}
|
||||
}
|
||||
|
||||
private MessageProducer getCachedProducer(Destination dest) throws JMSException {
|
||||
MessageProducer producer = (MessageProducer) this.cachedProducers.get(dest);
|
||||
if (producer != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Found cached JMS MessageProducer for destination [" + dest + "]: " + producer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
producer = this.target.createProducer(dest);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Creating cached JMS MessageProducer for destination [" + dest + "]: " + producer);
|
||||
}
|
||||
this.cachedProducers.put(dest, producer);
|
||||
}
|
||||
return new CachedMessageProducer(producer);
|
||||
}
|
||||
|
||||
private MessageConsumer getCachedConsumer(
|
||||
Destination dest, String selector, boolean noLocal, String subscription) throws JMSException {
|
||||
|
||||
Object cacheKey = new ConsumerCacheKey(dest, selector, noLocal, subscription);
|
||||
MessageConsumer consumer = (MessageConsumer) this.cachedConsumers.get(cacheKey);
|
||||
if (consumer != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Found cached JMS MessageConsumer for destination [" + dest + "]: " + consumer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (dest instanceof Topic) {
|
||||
consumer = (subscription != null ?
|
||||
this.target.createDurableSubscriber((Topic) dest, subscription, selector, noLocal) :
|
||||
this.target.createConsumer(dest, selector, noLocal));
|
||||
}
|
||||
else {
|
||||
consumer = this.target.createConsumer(dest, selector);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Creating cached JMS MessageConsumer for destination [" + dest + "]: " + consumer);
|
||||
}
|
||||
this.cachedConsumers.put(cacheKey, consumer);
|
||||
}
|
||||
return new CachedMessageConsumer(consumer);
|
||||
}
|
||||
|
||||
private void logicalClose(Object proxy) throws JMSException {
|
||||
// Preserve rollback-on-close semantics.
|
||||
if (this.transactionOpen && this.target.getTransacted()) {
|
||||
this.transactionOpen = false;
|
||||
this.target.rollback();
|
||||
}
|
||||
// Physically close durable subscribers at time of Session close call.
|
||||
for (Iterator it = this.cachedConsumers.entrySet().iterator(); it.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry) it.next();
|
||||
ConsumerCacheKey key = (ConsumerCacheKey) entry.getKey();
|
||||
if (key.subscription != null) {
|
||||
((MessageConsumer) entry.getValue()).close();
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
// Allow for multiple close calls...
|
||||
if (!this.sessionList.contains(proxy)) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Returning cached Session: " + this.target);
|
||||
}
|
||||
this.sessionList.addLast(proxy);
|
||||
}
|
||||
}
|
||||
|
||||
private void physicalClose() throws JMSException {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Closing cached Session: " + this.target);
|
||||
}
|
||||
// Explicitly close all MessageProducers and MessageConsumers that
|
||||
// this Session happens to cache...
|
||||
try {
|
||||
for (Iterator it = this.cachedProducers.values().iterator(); it.hasNext();) {
|
||||
((MessageProducer) it.next()).close();
|
||||
}
|
||||
for (Iterator it = this.cachedConsumers.values().iterator(); it.hasNext();) {
|
||||
((MessageConsumer) it.next()).close();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.cachedProducers.clear();
|
||||
this.cachedConsumers.clear();
|
||||
// Now actually close the Session.
|
||||
this.target.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple wrapper class around a Destination and other consumer attributes.
|
||||
* Used as the key when caching consumers.
|
||||
*/
|
||||
private static class ConsumerCacheKey {
|
||||
|
||||
private final Destination destination;
|
||||
|
||||
private final String selector;
|
||||
|
||||
private final boolean noLocal;
|
||||
|
||||
private final String subscription;
|
||||
|
||||
private ConsumerCacheKey(Destination destination, String selector, boolean noLocal, String subscription) {
|
||||
this.destination = destination;
|
||||
this.selector = selector;
|
||||
this.noLocal = noLocal;
|
||||
this.subscription = subscription;
|
||||
}
|
||||
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) {
|
||||
return true;
|
||||
}
|
||||
ConsumerCacheKey otherKey = (ConsumerCacheKey) other;
|
||||
return (this.destination.equals(otherKey.destination) &&
|
||||
ObjectUtils.nullSafeEquals(this.selector, otherKey.selector) &&
|
||||
this.noLocal == otherKey.noLocal &&
|
||||
ObjectUtils.nullSafeEquals(this.subscription, otherKey.subscription));
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return this.destination.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2002-2006 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.connection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.jms.ExceptionListener;
|
||||
import javax.jms.JMSException;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of the JMS ExceptionListener interface that supports chaining,
|
||||
* allowing the addition of multiple ExceptionListener instances in order.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
*/
|
||||
public class ChainedExceptionListener implements ExceptionListener {
|
||||
|
||||
/** List of ExceptionListeners */
|
||||
private final List delegates = new ArrayList(2);
|
||||
|
||||
|
||||
/**
|
||||
* Add an ExceptionListener to the chained delegate list.
|
||||
*/
|
||||
public final void addDelegate(ExceptionListener listener) {
|
||||
Assert.notNull(listener, "ExceptionListener must not be null");
|
||||
this.delegates.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all registered ExceptionListener delegates (as array).
|
||||
*/
|
||||
public final ExceptionListener[] getDelegates() {
|
||||
return (ExceptionListener[]) this.delegates.toArray(new ExceptionListener[this.delegates.size()]);
|
||||
}
|
||||
|
||||
|
||||
public void onException(JMSException ex) {
|
||||
for (Iterator it = this.delegates.iterator(); it.hasNext();) {
|
||||
ExceptionListener listener = (ExceptionListener) it.next();
|
||||
listener.onException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,417 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.connection;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.QueueConnection;
|
||||
import javax.jms.QueueConnectionFactory;
|
||||
import javax.jms.QueueSession;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.TopicConnection;
|
||||
import javax.jms.TopicConnectionFactory;
|
||||
import javax.jms.TopicSession;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.transaction.support.ResourceHolder;
|
||||
import org.springframework.transaction.support.ResourceHolderSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Helper class for managing a JMS {@link javax.jms.ConnectionFactory}, in particular
|
||||
* for obtaining transactional JMS resources for a given ConnectionFactory.
|
||||
*
|
||||
* <p>Mainly for internal use within the framework. Used by
|
||||
* {@link org.springframework.jms.core.JmsTemplate} as well as
|
||||
* {@link org.springframework.jms.listener.DefaultMessageListenerContainer}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @see SmartConnectionFactory
|
||||
*/
|
||||
public abstract class ConnectionFactoryUtils {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ConnectionFactoryUtils.class);
|
||||
|
||||
|
||||
/**
|
||||
* Release the given Connection, stopping it (if necessary) and eventually closing it.
|
||||
* <p>Checks {@link SmartConnectionFactory#shouldStop}, if available.
|
||||
* This is essentially a more sophisticated version of
|
||||
* {@link org.springframework.jms.support.JmsUtils#closeConnection}.
|
||||
* @param con the Connection to release
|
||||
* (if this is <code>null</code>, the call will be ignored)
|
||||
* @param cf the ConnectionFactory that the Connection was obtained from
|
||||
* (may be <code>null</code>)
|
||||
* @param started whether the Connection might have been started by the application
|
||||
* @see SmartConnectionFactory#shouldStop
|
||||
* @see org.springframework.jms.support.JmsUtils#closeConnection
|
||||
*/
|
||||
public static void releaseConnection(Connection con, ConnectionFactory cf, boolean started) {
|
||||
if (con == null) {
|
||||
return;
|
||||
}
|
||||
if (started && cf instanceof SmartConnectionFactory && ((SmartConnectionFactory) cf).shouldStop(con)) {
|
||||
try {
|
||||
con.stop();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.debug("Could not stop JMS Connection before closing it", ex);
|
||||
}
|
||||
}
|
||||
try {
|
||||
con.close();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.debug("Could not close JMS Connection", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the innermost target Session of the given Session. If the given
|
||||
* Session is a proxy, it will be unwrapped until a non-proxy Session is
|
||||
* found. Otherwise, the passed-in Session will be returned as-is.
|
||||
* @param session the Session proxy to unwrap
|
||||
* @return the innermost target Session, or the passed-in one if no proxy
|
||||
* @see SessionProxy#getTargetSession()
|
||||
*/
|
||||
public static Session getTargetSession(Session session) {
|
||||
Session sessionToUse = session;
|
||||
while (sessionToUse instanceof SessionProxy) {
|
||||
sessionToUse = ((SessionProxy) sessionToUse).getTargetSession();
|
||||
}
|
||||
return sessionToUse;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether the given JMS Session is transactional, that is,
|
||||
* bound to the current thread by Spring's transaction facilities.
|
||||
* @param session the JMS Session to check
|
||||
* @param cf the JMS ConnectionFactory that the Session originated from
|
||||
* @return whether the Session is transactional
|
||||
*/
|
||||
public static boolean isSessionTransactional(Session session, ConnectionFactory cf) {
|
||||
if (session == null || cf == null) {
|
||||
return false;
|
||||
}
|
||||
JmsResourceHolder resourceHolder = (JmsResourceHolder) TransactionSynchronizationManager.getResource(cf);
|
||||
return (resourceHolder != null && resourceHolder.containsSession(session));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtain a JMS Session that is synchronized with the current transaction, if any.
|
||||
* @param cf the ConnectionFactory to obtain a Session for
|
||||
* @param existingCon the existing JMS Connection to obtain a Session for
|
||||
* (may be <code>null</code>)
|
||||
* @param synchedLocalTransactionAllowed whether to allow for a local JMS transaction
|
||||
* that is synchronized with a Spring-managed transaction (where the main transaction
|
||||
* might be a JDBC-based one for a specific DataSource, for example), with the JMS
|
||||
* transaction committing right after the main transaction. If not allowed, the given
|
||||
* ConnectionFactory needs to handle transaction enlistment underneath the covers.
|
||||
* @return the transactional Session, or <code>null</code> if none found
|
||||
* @throws JMSException in case of JMS failure
|
||||
*/
|
||||
public static Session getTransactionalSession(
|
||||
final ConnectionFactory cf, final Connection existingCon, final boolean synchedLocalTransactionAllowed)
|
||||
throws JMSException {
|
||||
|
||||
return doGetTransactionalSession(cf, new ResourceFactory() {
|
||||
public Session getSession(JmsResourceHolder holder) {
|
||||
return holder.getSession(Session.class, existingCon);
|
||||
}
|
||||
public Connection getConnection(JmsResourceHolder holder) {
|
||||
return (existingCon != null ? existingCon : holder.getConnection());
|
||||
}
|
||||
public Connection createConnection() throws JMSException {
|
||||
return cf.createConnection();
|
||||
}
|
||||
public Session createSession(Connection con) throws JMSException {
|
||||
return con.createSession(synchedLocalTransactionAllowed, Session.AUTO_ACKNOWLEDGE);
|
||||
}
|
||||
public boolean isSynchedLocalTransactionAllowed() {
|
||||
return synchedLocalTransactionAllowed;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a JMS QueueSession that is synchronized with the current transaction, if any.
|
||||
* <p>Mainly intended for use with the JMS 1.0.2 API.
|
||||
* @param cf the ConnectionFactory to obtain a Session for
|
||||
* @param existingCon the existing JMS Connection to obtain a Session for
|
||||
* (may be <code>null</code>)
|
||||
* @param synchedLocalTransactionAllowed whether to allow for a local JMS transaction
|
||||
* that is synchronized with a Spring-managed transaction (where the main transaction
|
||||
* might be a JDBC-based one for a specific DataSource, for example), with the JMS
|
||||
* transaction committing right after the main transaction. If not allowed, the given
|
||||
* ConnectionFactory needs to handle transaction enlistment underneath the covers.
|
||||
* @return the transactional Session, or <code>null</code> if none found
|
||||
* @throws JMSException in case of JMS failure
|
||||
*/
|
||||
public static QueueSession getTransactionalQueueSession(
|
||||
final QueueConnectionFactory cf, final QueueConnection existingCon, final boolean synchedLocalTransactionAllowed)
|
||||
throws JMSException {
|
||||
|
||||
return (QueueSession) doGetTransactionalSession(cf, new ResourceFactory() {
|
||||
public Session getSession(JmsResourceHolder holder) {
|
||||
return holder.getSession(QueueSession.class, existingCon);
|
||||
}
|
||||
public Connection getConnection(JmsResourceHolder holder) {
|
||||
return (existingCon != null ? existingCon : holder.getConnection(QueueConnection.class));
|
||||
}
|
||||
public Connection createConnection() throws JMSException {
|
||||
return cf.createQueueConnection();
|
||||
}
|
||||
public Session createSession(Connection con) throws JMSException {
|
||||
return ((QueueConnection) con).createQueueSession(synchedLocalTransactionAllowed, Session.AUTO_ACKNOWLEDGE);
|
||||
}
|
||||
public boolean isSynchedLocalTransactionAllowed() {
|
||||
return synchedLocalTransactionAllowed;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a JMS TopicSession that is synchronized with the current transaction, if any.
|
||||
* <p>Mainly intended for use with the JMS 1.0.2 API.
|
||||
* @param cf the ConnectionFactory to obtain a Session for
|
||||
* @param existingCon the existing JMS Connection to obtain a Session for
|
||||
* (may be <code>null</code>)
|
||||
* @param synchedLocalTransactionAllowed whether to allow for a local JMS transaction
|
||||
* that is synchronized with a Spring-managed transaction (where the main transaction
|
||||
* might be a JDBC-based one for a specific DataSource, for example), with the JMS
|
||||
* transaction committing right after the main transaction. If not allowed, the given
|
||||
* ConnectionFactory needs to handle transaction enlistment underneath the covers.
|
||||
* @return the transactional Session, or <code>null</code> if none found
|
||||
* @throws JMSException in case of JMS failure
|
||||
*/
|
||||
public static TopicSession getTransactionalTopicSession(
|
||||
final TopicConnectionFactory cf, final TopicConnection existingCon, final boolean synchedLocalTransactionAllowed)
|
||||
throws JMSException {
|
||||
|
||||
return (TopicSession) doGetTransactionalSession(cf, new ResourceFactory() {
|
||||
public Session getSession(JmsResourceHolder holder) {
|
||||
return holder.getSession(TopicSession.class, existingCon);
|
||||
}
|
||||
public Connection getConnection(JmsResourceHolder holder) {
|
||||
return (existingCon != null ? existingCon : holder.getConnection(TopicConnection.class));
|
||||
}
|
||||
public Connection createConnection() throws JMSException {
|
||||
return cf.createTopicConnection();
|
||||
}
|
||||
public Session createSession(Connection con) throws JMSException {
|
||||
return ((TopicConnection) con).createTopicSession(synchedLocalTransactionAllowed, Session.AUTO_ACKNOWLEDGE);
|
||||
}
|
||||
public boolean isSynchedLocalTransactionAllowed() {
|
||||
return synchedLocalTransactionAllowed;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a JMS Session that is synchronized with the current transaction, if any.
|
||||
* <p>This <code>doGetTransactionalSession</code> variant always starts the underlying
|
||||
* JMS Connection, assuming that the Session will be used for receiving messages.
|
||||
* @param connectionFactory the JMS ConnectionFactory to bind for
|
||||
* (used as TransactionSynchronizationManager key)
|
||||
* @param resourceFactory the ResourceFactory to use for extracting or creating
|
||||
* JMS resources
|
||||
* @return the transactional Session, or <code>null</code> if none found
|
||||
* @throws JMSException in case of JMS failure
|
||||
* @see #doGetTransactionalSession(javax.jms.ConnectionFactory, ResourceFactory, boolean)
|
||||
*/
|
||||
public static Session doGetTransactionalSession(
|
||||
ConnectionFactory connectionFactory, ResourceFactory resourceFactory) throws JMSException {
|
||||
|
||||
return doGetTransactionalSession(connectionFactory, resourceFactory, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a JMS Session that is synchronized with the current transaction, if any.
|
||||
* @param connectionFactory the JMS ConnectionFactory to bind for
|
||||
* (used as TransactionSynchronizationManager key)
|
||||
* @param resourceFactory the ResourceFactory to use for extracting or creating
|
||||
* JMS resources
|
||||
* @param startConnection whether the underlying JMS Connection approach should be
|
||||
* started in order to allow for receiving messages. Note that a reused Connection
|
||||
* may already have been started before, even if this flag is <code>false</code>.
|
||||
* @return the transactional Session, or <code>null</code> if none found
|
||||
* @throws JMSException in case of JMS failure
|
||||
*/
|
||||
public static Session doGetTransactionalSession(
|
||||
ConnectionFactory connectionFactory, ResourceFactory resourceFactory, boolean startConnection)
|
||||
throws JMSException {
|
||||
|
||||
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
|
||||
Assert.notNull(resourceFactory, "ResourceFactory must not be null");
|
||||
|
||||
JmsResourceHolder resourceHolder =
|
||||
(JmsResourceHolder) TransactionSynchronizationManager.getResource(connectionFactory);
|
||||
if (resourceHolder != null) {
|
||||
Session session = resourceFactory.getSession(resourceHolder);
|
||||
if (session != null) {
|
||||
if (startConnection) {
|
||||
Connection con = resourceFactory.getConnection(resourceHolder);
|
||||
if (con != null) {
|
||||
con.start();
|
||||
}
|
||||
}
|
||||
return session;
|
||||
}
|
||||
if (resourceHolder.isFrozen()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
return null;
|
||||
}
|
||||
JmsResourceHolder resourceHolderToUse = resourceHolder;
|
||||
if (resourceHolderToUse == null) {
|
||||
resourceHolderToUse = new JmsResourceHolder(connectionFactory);
|
||||
}
|
||||
Connection con = resourceFactory.getConnection(resourceHolderToUse);
|
||||
Session session = null;
|
||||
try {
|
||||
boolean isExistingCon = (con != null);
|
||||
if (!isExistingCon) {
|
||||
con = resourceFactory.createConnection();
|
||||
resourceHolderToUse.addConnection(con);
|
||||
}
|
||||
session = resourceFactory.createSession(con);
|
||||
resourceHolderToUse.addSession(session, con);
|
||||
if (startConnection) {
|
||||
con.start();
|
||||
}
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
if (session != null) {
|
||||
try {
|
||||
session.close();
|
||||
}
|
||||
catch (Throwable ex2) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (con != null) {
|
||||
try {
|
||||
con.close();
|
||||
}
|
||||
catch (Throwable ex2) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
if (resourceHolderToUse != resourceHolder) {
|
||||
TransactionSynchronizationManager.registerSynchronization(
|
||||
new JmsResourceSynchronization(
|
||||
resourceHolderToUse, connectionFactory, resourceFactory.isSynchedLocalTransactionAllowed()));
|
||||
resourceHolderToUse.setSynchronizedWithTransaction(true);
|
||||
TransactionSynchronizationManager.bindResource(connectionFactory, resourceHolderToUse);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback interface for resource creation.
|
||||
* Serving as argument for the <code>doGetTransactionalSession</code> method.
|
||||
*/
|
||||
public interface ResourceFactory {
|
||||
|
||||
/**
|
||||
* Fetch an appropriate Session from the given JmsResourceHolder.
|
||||
* @param holder the JmsResourceHolder
|
||||
* @return an appropriate Session fetched from the holder,
|
||||
* or <code>null</code> if none found
|
||||
*/
|
||||
Session getSession(JmsResourceHolder holder);
|
||||
|
||||
/**
|
||||
* Fetch an appropriate Connection from the given JmsResourceHolder.
|
||||
* @param holder the JmsResourceHolder
|
||||
* @return an appropriate Connection fetched from the holder,
|
||||
* or <code>null</code> if none found
|
||||
*/
|
||||
Connection getConnection(JmsResourceHolder holder);
|
||||
|
||||
/**
|
||||
* Create a new JMS Connection for registration with a JmsResourceHolder.
|
||||
* @return the new JMS Connection
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
*/
|
||||
Connection createConnection() throws JMSException;
|
||||
|
||||
/**
|
||||
* Create a new JMS Session for registration with a JmsResourceHolder.
|
||||
* @param con the JMS Connection to create a Session for
|
||||
* @return the new JMS Session
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
*/
|
||||
Session createSession(Connection con) throws JMSException;
|
||||
|
||||
/**
|
||||
* Return whether to allow for a local JMS transaction that is synchronized with
|
||||
* a Spring-managed transaction (where the main transaction might be a JDBC-based
|
||||
* one for a specific DataSource, for example), with the JMS transaction
|
||||
* committing right after the main transaction.
|
||||
* @return whether to allow for synchronizing a local JMS transaction
|
||||
*/
|
||||
boolean isSynchedLocalTransactionAllowed();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback for resource cleanup at the end of a non-native JMS transaction
|
||||
* (e.g. when participating in a JtaTransactionManager transaction).
|
||||
* @see org.springframework.transaction.jta.JtaTransactionManager
|
||||
*/
|
||||
private static class JmsResourceSynchronization extends ResourceHolderSynchronization {
|
||||
|
||||
private final boolean transacted;
|
||||
|
||||
public JmsResourceSynchronization(JmsResourceHolder resourceHolder, Object resourceKey, boolean transacted) {
|
||||
super(resourceHolder, resourceKey);
|
||||
this.transacted = transacted;
|
||||
}
|
||||
|
||||
protected boolean shouldReleaseBeforeCompletion() {
|
||||
return !this.transacted;
|
||||
}
|
||||
|
||||
protected void processResourceAfterCommit(ResourceHolder resourceHolder) {
|
||||
try {
|
||||
((JmsResourceHolder) resourceHolder).commitAll();
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
throw new SynchedLocalTransactionFailedException("Local JMS transaction failed to commit", ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected void releaseResource(ResourceHolder resourceHolder, Object resourceKey) {
|
||||
((JmsResourceHolder) resourceHolder).closeAll();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.connection;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.TransactionInProgressException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.transaction.support.ResourceHolderSupport;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* JMS resource holder, wrapping a JMS Connection and a JMS Session.
|
||||
* JmsTransactionManager binds instances of this class to the thread,
|
||||
* for a given JMS ConnectionFactory.
|
||||
*
|
||||
* <p>Note: This is an SPI class, not intended to be used by applications.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see JmsTransactionManager
|
||||
* @see org.springframework.jms.core.JmsTemplate
|
||||
*/
|
||||
public class JmsResourceHolder extends ResourceHolderSupport {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(JmsResourceHolder.class);
|
||||
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
private boolean frozen = false;
|
||||
|
||||
private final List connections = new LinkedList();
|
||||
|
||||
private final List sessions = new LinkedList();
|
||||
|
||||
private final Map sessionsPerConnection = new HashMap();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new JmsResourceHolder that is open for resources to be added.
|
||||
* @see #addConnection
|
||||
* @see #addSession
|
||||
*/
|
||||
public JmsResourceHolder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JmsResourceHolder that is open for resources to be added.
|
||||
* @param connectionFactory the JMS ConnectionFactory that this
|
||||
* resource holder is associated with (may be <code>null</code>)
|
||||
*/
|
||||
public JmsResourceHolder(ConnectionFactory connectionFactory) {
|
||||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JmsResourceHolder for the given JMS Session.
|
||||
* @param session the JMS Session
|
||||
*/
|
||||
public JmsResourceHolder(Session session) {
|
||||
addSession(session);
|
||||
this.frozen = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JmsResourceHolder for the given JMS resources.
|
||||
* @param connection the JMS Connection
|
||||
* @param session the JMS Session
|
||||
*/
|
||||
public JmsResourceHolder(Connection connection, Session session) {
|
||||
addConnection(connection);
|
||||
addSession(session, connection);
|
||||
this.frozen = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JmsResourceHolder for the given JMS resources.
|
||||
* @param connectionFactory the JMS ConnectionFactory that this
|
||||
* resource holder is associated with (may be <code>null</code>)
|
||||
* @param connection the JMS Connection
|
||||
* @param session the JMS Session
|
||||
*/
|
||||
public JmsResourceHolder(ConnectionFactory connectionFactory, Connection connection, Session session) {
|
||||
this.connectionFactory = connectionFactory;
|
||||
addConnection(connection);
|
||||
addSession(session, connection);
|
||||
this.frozen = true;
|
||||
}
|
||||
|
||||
|
||||
public final boolean isFrozen() {
|
||||
return this.frozen;
|
||||
}
|
||||
|
||||
public final void addConnection(Connection connection) {
|
||||
Assert.isTrue(!this.frozen, "Cannot add Connection because JmsResourceHolder is frozen");
|
||||
Assert.notNull(connection, "Connection must not be null");
|
||||
if (!this.connections.contains(connection)) {
|
||||
this.connections.add(connection);
|
||||
}
|
||||
}
|
||||
|
||||
public final void addSession(Session session) {
|
||||
addSession(session, null);
|
||||
}
|
||||
|
||||
public final void addSession(Session session, Connection connection) {
|
||||
Assert.isTrue(!this.frozen, "Cannot add Session because JmsResourceHolder is frozen");
|
||||
Assert.notNull(session, "Session must not be null");
|
||||
if (!this.sessions.contains(session)) {
|
||||
this.sessions.add(session);
|
||||
if (connection != null) {
|
||||
List sessions = (List) this.sessionsPerConnection.get(connection);
|
||||
if (sessions == null) {
|
||||
sessions = new LinkedList();
|
||||
this.sessionsPerConnection.put(connection, sessions);
|
||||
}
|
||||
sessions.add(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containsSession(Session session) {
|
||||
return this.sessions.contains(session);
|
||||
}
|
||||
|
||||
|
||||
public Connection getConnection() {
|
||||
return (!this.connections.isEmpty() ? (Connection) this.connections.get(0) : null);
|
||||
}
|
||||
|
||||
public Connection getConnection(Class connectionType) {
|
||||
return (Connection) CollectionUtils.findValueOfType(this.connections, connectionType);
|
||||
}
|
||||
|
||||
public Session getSession() {
|
||||
return (!this.sessions.isEmpty() ? (Session) this.sessions.get(0) : null);
|
||||
}
|
||||
|
||||
public Session getSession(Class sessionType) {
|
||||
return getSession(sessionType, null);
|
||||
}
|
||||
|
||||
public Session getSession(Class sessionType, Connection connection) {
|
||||
List sessions = this.sessions;
|
||||
if (connection != null) {
|
||||
sessions = (List) this.sessionsPerConnection.get(connection);
|
||||
}
|
||||
return (Session) CollectionUtils.findValueOfType(sessions, sessionType);
|
||||
}
|
||||
|
||||
|
||||
public void commitAll() throws JMSException {
|
||||
for (Iterator it = this.sessions.iterator(); it.hasNext();) {
|
||||
try {
|
||||
((Session) it.next()).commit();
|
||||
}
|
||||
catch (TransactionInProgressException ex) {
|
||||
// Ignore -> can only happen in case of a JTA transaction.
|
||||
}
|
||||
catch (javax.jms.IllegalStateException ex) {
|
||||
// Ignore -> can only happen in case of a JTA transaction.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void closeAll() {
|
||||
for (Iterator it = this.sessions.iterator(); it.hasNext();) {
|
||||
try {
|
||||
((Session) it.next()).close();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.debug("Could not close synchronized JMS Session after transaction", ex);
|
||||
}
|
||||
}
|
||||
for (Iterator it = this.connections.iterator(); it.hasNext();) {
|
||||
Connection con = (Connection) it.next();
|
||||
ConnectionFactoryUtils.releaseConnection(con, this.connectionFactory, true);
|
||||
}
|
||||
this.connections.clear();
|
||||
this.sessions.clear();
|
||||
this.sessionsPerConnection.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.connection;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.TransactionRolledBackException;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.transaction.CannotCreateTransactionException;
|
||||
import org.springframework.transaction.InvalidIsolationLevelException;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionSystemException;
|
||||
import org.springframework.transaction.UnexpectedRollbackException;
|
||||
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
|
||||
import org.springframework.transaction.support.DefaultTransactionStatus;
|
||||
import org.springframework.transaction.support.ResourceTransactionManager;
|
||||
import org.springframework.transaction.support.SmartTransactionObject;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.transaction.PlatformTransactionManager} implementation
|
||||
* for a single JMS {@link javax.jms.ConnectionFactory}. Binds a JMS
|
||||
* Connection/Session pair from the specified ConnectionFactory to the thread,
|
||||
* potentially allowing for one thread-bound Session per ConnectionFactory.
|
||||
*
|
||||
* <p><b>NOTE:</b> This class requires a JMS 1.1+ provider because it builds on
|
||||
* the domain-independent API. <b>Use the {@link JmsTransactionManager102} subclass
|
||||
* for a JMS 1.0.2 provider, e.g. when running on a J2EE 1.3 server.</b>
|
||||
*
|
||||
* <p>This local strategy is an alternative to executing JMS operations within
|
||||
* JTA transactions. Its advantage is that it is able to work in any environment,
|
||||
* for example a standalone application or a test suite, with any message broker
|
||||
* as target. However, this strategy is <i>not</i> able to provide XA transactions,
|
||||
* for example in order to share transactions between messaging and database access.
|
||||
* A full JTA/XA setup is required for XA transactions, typically using Spring's
|
||||
* {@link org.springframework.transaction.jta.JtaTransactionManager} as strategy.
|
||||
*
|
||||
* <p>Application code is required to retrieve the transactional JMS Session via
|
||||
* {@link ConnectionFactoryUtils#getTransactionalSession} instead of a standard
|
||||
* J2EE-style {@link ConnectionFactory#createConnection()} call with subsequent
|
||||
* Session creation. Spring's {@link org.springframework.jms.core.JmsTemplate}
|
||||
* will autodetect a thread-bound Session and automatically participate in it.
|
||||
*
|
||||
* <p>Alternatively, you can allow application code to work with the standard
|
||||
* J2EE-style lookup pattern on a ConnectionFactory, for example for legacy code
|
||||
* that is not aware of Spring at all. In that case, define a
|
||||
* {@link TransactionAwareConnectionFactoryProxy} for your target ConnectionFactory,
|
||||
* which will automatically participate in Spring-managed transactions.
|
||||
*
|
||||
* <p><b>The use of {@link CachingConnectionFactory} as a target for this
|
||||
* transaction manager is strongly recommended.</b> CachingConnectionFactory
|
||||
* uses a single JMS Connection for all JMS access in order to avoid the overhead
|
||||
* of repeated Connection creation, as well as maintaining a cache of Sessions.
|
||||
* Each transaction will then share the same JMS Connection, while still using
|
||||
* its own individual JMS Session.
|
||||
*
|
||||
* <p>The use of a <i>raw</i> target ConnectionFactory would not only be inefficient
|
||||
* because of the lack of resource reuse. It might also lead to strange effects
|
||||
* when your JMS driver doesn't accept <code>MessageProducer.close()</code> calls
|
||||
* and/or <code>MessageConsumer.close()</code> calls before <code>Session.commit()</code>,
|
||||
* with the latter supposed to commit all the messages that have been sent through the
|
||||
* producer handle and received through the consumer handle. As a safe general solution,
|
||||
* always pass in a {@link CachingConnectionFactory} into this transaction manager's
|
||||
* {@link #setConnectionFactory "connectionFactory"} property.
|
||||
*
|
||||
* <p>Transaction synchronization is turned off by default, as this manager might
|
||||
* be used alongside a datastore-based Spring transaction manager such as the
|
||||
* JDBC {@link org.springframework.jdbc.datasource.DataSourceTransactionManager},
|
||||
* which has stronger needs for synchronization.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see ConnectionFactoryUtils#getTransactionalSession
|
||||
* @see TransactionAwareConnectionFactoryProxy
|
||||
* @see org.springframework.jms.core.JmsTemplate
|
||||
*/
|
||||
public class JmsTransactionManager extends AbstractPlatformTransactionManager
|
||||
implements ResourceTransactionManager, InitializingBean {
|
||||
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new JmsTransactionManager for bean-style usage.
|
||||
* <p>Note: The ConnectionFactory has to be set before using the instance.
|
||||
* This constructor can be used to prepare a JmsTemplate via a BeanFactory,
|
||||
* typically setting the ConnectionFactory via setConnectionFactory.
|
||||
* <p>Turns off transaction synchronization by default, as this manager might
|
||||
* be used alongside a datastore-based Spring transaction manager like
|
||||
* DataSourceTransactionManager, which has stronger needs for synchronization.
|
||||
* Only one manager is allowed to drive synchronization at any point of time.
|
||||
* @see #setConnectionFactory
|
||||
* @see #setTransactionSynchronization
|
||||
*/
|
||||
public JmsTransactionManager() {
|
||||
setTransactionSynchronization(SYNCHRONIZATION_NEVER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JmsTransactionManager, given a ConnectionFactory.
|
||||
* @param connectionFactory the ConnectionFactory to obtain connections from
|
||||
*/
|
||||
public JmsTransactionManager(ConnectionFactory connectionFactory) {
|
||||
this();
|
||||
setConnectionFactory(connectionFactory);
|
||||
afterPropertiesSet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the JMS ConnectionFactory that this instance should manage transactions for.
|
||||
*/
|
||||
public void setConnectionFactory(ConnectionFactory cf) {
|
||||
if (cf instanceof TransactionAwareConnectionFactoryProxy) {
|
||||
// If we got a TransactionAwareConnectionFactoryProxy, we need to perform transactions
|
||||
// for its underlying target ConnectionFactory, else JMS access code won't see
|
||||
// properly exposed transactions (i.e. transactions for the target ConnectionFactory).
|
||||
this.connectionFactory = ((TransactionAwareConnectionFactoryProxy) cf).getTargetConnectionFactory();
|
||||
}
|
||||
else {
|
||||
this.connectionFactory = cf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JMS ConnectionFactory that this instance should manage transactions for.
|
||||
*/
|
||||
public ConnectionFactory getConnectionFactory() {
|
||||
return this.connectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the ConnectionFactory has been set.
|
||||
*/
|
||||
public void afterPropertiesSet() {
|
||||
if (getConnectionFactory() == null) {
|
||||
throw new IllegalArgumentException("Property 'connectionFactory' is required");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Object getResourceFactory() {
|
||||
return getConnectionFactory();
|
||||
}
|
||||
|
||||
protected Object doGetTransaction() {
|
||||
JmsTransactionObject txObject = new JmsTransactionObject();
|
||||
txObject.setResourceHolder(
|
||||
(JmsResourceHolder) TransactionSynchronizationManager.getResource(getConnectionFactory()));
|
||||
return txObject;
|
||||
}
|
||||
|
||||
protected boolean isExistingTransaction(Object transaction) {
|
||||
JmsTransactionObject txObject = (JmsTransactionObject) transaction;
|
||||
return (txObject.getResourceHolder() != null);
|
||||
}
|
||||
|
||||
protected void doBegin(Object transaction, TransactionDefinition definition) {
|
||||
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
|
||||
throw new InvalidIsolationLevelException("JMS does not support an isolation level concept");
|
||||
}
|
||||
JmsTransactionObject txObject = (JmsTransactionObject) transaction;
|
||||
Connection con = null;
|
||||
Session session = null;
|
||||
try {
|
||||
con = createConnection();
|
||||
session = createSession(con);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Created JMS transaction on Session [" + session + "] from Connection [" + con + "]");
|
||||
}
|
||||
txObject.setResourceHolder(new JmsResourceHolder(getConnectionFactory(), con, session));
|
||||
txObject.getResourceHolder().setSynchronizedWithTransaction(true);
|
||||
int timeout = determineTimeout(definition);
|
||||
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
|
||||
txObject.getResourceHolder().setTimeoutInSeconds(timeout);
|
||||
}
|
||||
TransactionSynchronizationManager.bindResource(
|
||||
getConnectionFactory(), txObject.getResourceHolder());
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
if (session != null) {
|
||||
try {
|
||||
session.close();
|
||||
}
|
||||
catch (Throwable ex2) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (con != null) {
|
||||
try {
|
||||
con.close();
|
||||
}
|
||||
catch (Throwable ex2) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
throw new CannotCreateTransactionException("Could not create JMS transaction", ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected Object doSuspend(Object transaction) {
|
||||
JmsTransactionObject txObject = (JmsTransactionObject) transaction;
|
||||
txObject.setResourceHolder(null);
|
||||
return TransactionSynchronizationManager.unbindResource(getConnectionFactory());
|
||||
}
|
||||
|
||||
protected void doResume(Object transaction, Object suspendedResources) {
|
||||
JmsResourceHolder conHolder = (JmsResourceHolder) suspendedResources;
|
||||
TransactionSynchronizationManager.bindResource(getConnectionFactory(), conHolder);
|
||||
}
|
||||
|
||||
protected void doCommit(DefaultTransactionStatus status) {
|
||||
JmsTransactionObject txObject = (JmsTransactionObject) status.getTransaction();
|
||||
Session session = txObject.getResourceHolder().getSession();
|
||||
try {
|
||||
if (status.isDebug()) {
|
||||
logger.debug("Committing JMS transaction on Session [" + session + "]");
|
||||
}
|
||||
session.commit();
|
||||
}
|
||||
catch (TransactionRolledBackException ex) {
|
||||
throw new UnexpectedRollbackException("JMS transaction rolled back", ex);
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
throw new TransactionSystemException("Could not commit JMS transaction", ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected void doRollback(DefaultTransactionStatus status) {
|
||||
JmsTransactionObject txObject = (JmsTransactionObject) status.getTransaction();
|
||||
Session session = txObject.getResourceHolder().getSession();
|
||||
try {
|
||||
if (status.isDebug()) {
|
||||
logger.debug("Rolling back JMS transaction on Session [" + session + "]");
|
||||
}
|
||||
session.rollback();
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
throw new TransactionSystemException("Could not roll back JMS transaction", ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
|
||||
JmsTransactionObject txObject = (JmsTransactionObject) status.getTransaction();
|
||||
txObject.getResourceHolder().setRollbackOnly();
|
||||
}
|
||||
|
||||
protected void doCleanupAfterCompletion(Object transaction) {
|
||||
JmsTransactionObject txObject = (JmsTransactionObject) transaction;
|
||||
TransactionSynchronizationManager.unbindResource(getConnectionFactory());
|
||||
txObject.getResourceHolder().closeAll();
|
||||
txObject.getResourceHolder().clear();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// JMS 1.1 factory methods, potentially overridden for JMS 1.0.2
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create a JMS Connection via this template's ConnectionFactory.
|
||||
* <p>This implementation uses JMS 1.1 API.
|
||||
* @return the new JMS Connection
|
||||
* @throws javax.jms.JMSException if thrown by JMS API methods
|
||||
*/
|
||||
protected Connection createConnection() throws JMSException {
|
||||
return getConnectionFactory().createConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JMS Session for the given Connection.
|
||||
* <p>This implementation uses JMS 1.1 API.
|
||||
* @param con the JMS Connection to create a Session for
|
||||
* @return the new JMS Session
|
||||
* @throws javax.jms.JMSException if thrown by JMS API methods
|
||||
*/
|
||||
protected Session createSession(Connection con) throws JMSException {
|
||||
return con.createSession(true, Session.AUTO_ACKNOWLEDGE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* JMS transaction object, representing a JmsResourceHolder.
|
||||
* Used as transaction object by JmsTransactionManager.
|
||||
* @see JmsResourceHolder
|
||||
*/
|
||||
private static class JmsTransactionObject implements SmartTransactionObject {
|
||||
|
||||
private JmsResourceHolder resourceHolder;
|
||||
|
||||
public void setResourceHolder(JmsResourceHolder resourceHolder) {
|
||||
this.resourceHolder = resourceHolder;
|
||||
}
|
||||
|
||||
public JmsResourceHolder getResourceHolder() {
|
||||
return this.resourceHolder;
|
||||
}
|
||||
|
||||
public boolean isRollbackOnly() {
|
||||
return this.resourceHolder.isRollbackOnly();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.connection;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.QueueConnection;
|
||||
import javax.jms.QueueConnectionFactory;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.TopicConnection;
|
||||
import javax.jms.TopicConnectionFactory;
|
||||
|
||||
/**
|
||||
* A subclass of {@link JmsTransactionManager} for the JMS 1.0.2 specification,
|
||||
* not relying on JMS 1.1 methods like JmsTransactionManager itself.
|
||||
* This class can be used for JMS 1.0.2 providers, offering the same API as
|
||||
* JmsTransactionManager does for JMS 1.1 providers.
|
||||
*
|
||||
* <p>You need to set the {@link #setPubSubDomain "pubSubDomain" property},
|
||||
* since this class will always explicitly differentiate between a
|
||||
* {@link javax.jms.QueueConnection} and a {@link javax.jms.TopicConnection}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see #setConnectionFactory
|
||||
* @see #setPubSubDomain
|
||||
*/
|
||||
public class JmsTransactionManager102 extends JmsTransactionManager {
|
||||
|
||||
private boolean pubSubDomain = false;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new JmsTransactionManager102 for bean-style usage.
|
||||
* <p>Note: The ConnectionFactory has to be set before using the instance.
|
||||
* This constructor can be used to prepare a JmsTemplate via a BeanFactory,
|
||||
* typically setting the ConnectionFactory via setConnectionFactory.
|
||||
* @see #setConnectionFactory
|
||||
*/
|
||||
public JmsTransactionManager102() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JmsTransactionManager102, given a ConnectionFactory.
|
||||
* @param connectionFactory the ConnectionFactory to manage transactions for
|
||||
* @param pubSubDomain whether the Publish/Subscribe domain (Topics) or
|
||||
* Point-to-Point domain (Queues) should be used
|
||||
* @see #setPubSubDomain
|
||||
*/
|
||||
public JmsTransactionManager102(ConnectionFactory connectionFactory, boolean pubSubDomain) {
|
||||
setConnectionFactory(connectionFactory);
|
||||
this.pubSubDomain = pubSubDomain;
|
||||
afterPropertiesSet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configure the transaction manager with knowledge of the JMS domain used.
|
||||
* This tells the JMS 1.0.2 provider which class hierarchy to use for creating
|
||||
* Connections and Sessions.
|
||||
* <p>Default is Point-to-Point (Queues).
|
||||
* @param pubSubDomain <code>true</code> for Publish/Subscribe domain (Topics),
|
||||
* <code>false</code> for Point-to-Point domain (Queues)
|
||||
*/
|
||||
public void setPubSubDomain(boolean pubSubDomain) {
|
||||
this.pubSubDomain = pubSubDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the Publish/Subscribe domain (Topics) is used.
|
||||
* Otherwise, the Point-to-Point domain (Queues) is used.
|
||||
*/
|
||||
public boolean isPubSubDomain() {
|
||||
return this.pubSubDomain;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* In addition to checking if the connection factory is set, make sure
|
||||
* that the supplied connection factory is of the appropriate type for
|
||||
* the specified destination type: QueueConnectionFactory for queues,
|
||||
* and TopicConnectionFactory for topics.
|
||||
*/
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
|
||||
// Make sure that the ConnectionFactory passed is consistent.
|
||||
// Some provider implementations of the ConnectionFactory interface
|
||||
// implement both domain interfaces under the cover, so just check if
|
||||
// the selected domain is consistent with the type of connection factory.
|
||||
if (isPubSubDomain()) {
|
||||
if (!(getConnectionFactory() instanceof TopicConnectionFactory)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Specified a Spring JMS 1.0.2 transaction manager for topics " +
|
||||
"but did not supply an instance of TopicConnectionFactory");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!(getConnectionFactory() instanceof QueueConnectionFactory)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Specified a Spring JMS 1.0.2 transaction manager for queues " +
|
||||
"but did not supply an instance of QueueConnectionFactory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected Connection createConnection() throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicConnectionFactory) getConnectionFactory()).createTopicConnection();
|
||||
}
|
||||
else {
|
||||
return ((QueueConnectionFactory) getConnectionFactory()).createQueueConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected Session createSession(Connection con) throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicConnection) con).createTopicSession(true, Session.AUTO_ACKNOWLEDGE);
|
||||
}
|
||||
else {
|
||||
return ((QueueConnection) con).createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,587 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.connection;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.ExceptionListener;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.QueueConnection;
|
||||
import javax.jms.QueueConnectionFactory;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.TopicConnection;
|
||||
import javax.jms.TopicConnectionFactory;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A JMS ConnectionFactory adapter that returns the same Connection
|
||||
* from all {@link #createConnection()} calls, and ignores calls to
|
||||
* {@link javax.jms.Connection#close()}. According to the JMS Connection
|
||||
* model, this is perfectly thread-safe (in contrast to e.g. JDBC). The
|
||||
* shared Connection can be automatically recovered in case of an Exception.
|
||||
*
|
||||
* <p>You can either pass in a specific JMS Connection directly or let this
|
||||
* factory lazily create a Connection via a given target ConnectionFactory.
|
||||
* This factory generally works with JMS 1.1 as well as JMS 1.0.2; use
|
||||
* {@link SingleConnectionFactory102} for strict JMS 1.0.2 only usage.
|
||||
*
|
||||
* <p>Note that when using the JMS 1.0.2 API, this ConnectionFactory will switch
|
||||
* into queue/topic mode according to the JMS API methods used at runtime:
|
||||
* <code>createQueueConnection</code> and <code>createTopicConnection</code> will
|
||||
* lead to queue/topic mode, respectively; generic <code>createConnection</code>
|
||||
* calls will lead to a JMS 1.1 connection which is able to serve both modes.
|
||||
*
|
||||
* <p>Useful for testing and standalone environments in order to keep using the
|
||||
* same Connection for multiple {@link org.springframework.jms.core.JmsTemplate}
|
||||
* calls, without having a pooling ConnectionFactory underneath. This may span
|
||||
* any number of transactions, even concurrently executing transactions.
|
||||
*
|
||||
* <p>Note that Spring's message listener containers support the use of
|
||||
* a shared Connection within each listener container instance. Using
|
||||
* SingleConnectionFactory in combination only really makes sense for
|
||||
* sharing a single JMS Connection <i>across multiple listener containers</i>.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Mark Pollack
|
||||
* @since 1.1
|
||||
* @see org.springframework.jms.core.JmsTemplate
|
||||
* @see org.springframework.jms.listener.SimpleMessageListenerContainer
|
||||
* @see org.springframework.jms.listener.DefaultMessageListenerContainer#setCacheLevel
|
||||
*/
|
||||
public class SingleConnectionFactory
|
||||
implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory, ExceptionListener,
|
||||
InitializingBean, DisposableBean {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private ConnectionFactory targetConnectionFactory;
|
||||
|
||||
private String clientId;
|
||||
|
||||
private ExceptionListener exceptionListener;
|
||||
|
||||
private boolean reconnectOnException = false;
|
||||
|
||||
/** Wrapped Connection */
|
||||
private Connection target;
|
||||
|
||||
/** Proxy Connection */
|
||||
private Connection connection;
|
||||
|
||||
/** A hint whether to create a queue or topic connection */
|
||||
private Boolean pubSubMode;
|
||||
|
||||
/** Whether the shared Connection has been started */
|
||||
private boolean started = false;
|
||||
|
||||
/** Synchronization monitor for the shared Connection */
|
||||
private final Object connectionMonitor = new Object();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new SingleConnectionFactory for bean-style usage.
|
||||
* @see #setTargetConnectionFactory
|
||||
*/
|
||||
public SingleConnectionFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new SingleConnectionFactory that always returns the given Connection.
|
||||
* @param target the single Connection
|
||||
*/
|
||||
public SingleConnectionFactory(Connection target) {
|
||||
Assert.notNull(target, "Target Connection must not be null");
|
||||
this.target = target;
|
||||
this.connection = getSharedConnectionProxy(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new SingleConnectionFactory that always returns a single
|
||||
* Connection that it will lazily create via the given target
|
||||
* ConnectionFactory.
|
||||
* @param targetConnectionFactory the target ConnectionFactory
|
||||
*/
|
||||
public SingleConnectionFactory(ConnectionFactory targetConnectionFactory) {
|
||||
Assert.notNull(targetConnectionFactory, "Target ConnectionFactory must not be null");
|
||||
this.targetConnectionFactory = targetConnectionFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the target ConnectionFactory which will be used to lazily
|
||||
* create a single Connection.
|
||||
*/
|
||||
public void setTargetConnectionFactory(ConnectionFactory targetConnectionFactory) {
|
||||
this.targetConnectionFactory = targetConnectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the target ConnectionFactory which will be used to lazily
|
||||
* create a single Connection, if any.
|
||||
*/
|
||||
public ConnectionFactory getTargetConnectionFactory() {
|
||||
return this.targetConnectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a JMS client ID for the single Connection created and exposed
|
||||
* by this ConnectionFactory.
|
||||
* <p>Note that client IDs need to be unique among all active Connections
|
||||
* of the underlying JMS provider. Furthermore, a client ID can only be
|
||||
* assigned if the original ConnectionFactory hasn't already assigned one.
|
||||
* @see javax.jms.Connection#setClientID
|
||||
* @see #setTargetConnectionFactory
|
||||
*/
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a JMS client ID for the single Connection created and exposed
|
||||
* by this ConnectionFactory, if any.
|
||||
*/
|
||||
protected String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify an JMS ExceptionListener implementation that should be
|
||||
* registered with with the single Connection created by this factory.
|
||||
* @see #setReconnectOnException
|
||||
*/
|
||||
public void setExceptionListener(ExceptionListener exceptionListener) {
|
||||
this.exceptionListener = exceptionListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JMS ExceptionListener implementation that should be registered
|
||||
* with with the single Connection created by this factory, if any.
|
||||
*/
|
||||
protected ExceptionListener getExceptionListener() {
|
||||
return this.exceptionListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether the single Connection should be reset (to be subsequently renewed)
|
||||
* when a JMSException is reported by the underlying Connection.
|
||||
* <p>Default is "false". Switch this to "true" to automatically trigger
|
||||
* recovery based on your JMS provider's exception notifications.
|
||||
* <p>Internally, this will lead to a special JMS ExceptionListener
|
||||
* (this SingleConnectionFactory itself) being registered with the
|
||||
* underlying Connection. This can also be combined with a
|
||||
* user-specified ExceptionListener, if desired.
|
||||
* @see #setExceptionListener
|
||||
*/
|
||||
public void setReconnectOnException(boolean reconnectOnException) {
|
||||
this.reconnectOnException = reconnectOnException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the single Connection should be renewed when
|
||||
* a JMSException is reported by the underlying Connection.
|
||||
*/
|
||||
protected boolean isReconnectOnException() {
|
||||
return this.reconnectOnException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure a Connection or ConnectionFactory has been set.
|
||||
*/
|
||||
public void afterPropertiesSet() {
|
||||
if (this.connection == null && getTargetConnectionFactory() == null) {
|
||||
throw new IllegalArgumentException("Connection or 'targetConnectionFactory' is required");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Connection createConnection() throws JMSException {
|
||||
synchronized (this.connectionMonitor) {
|
||||
if (this.connection == null) {
|
||||
initConnection();
|
||||
}
|
||||
return this.connection;
|
||||
}
|
||||
}
|
||||
|
||||
public Connection createConnection(String username, String password) throws JMSException {
|
||||
throw new javax.jms.IllegalStateException(
|
||||
"SingleConnectionFactory does not support custom username and password");
|
||||
}
|
||||
|
||||
public QueueConnection createQueueConnection() throws JMSException {
|
||||
Connection con = null;
|
||||
synchronized (this.connectionMonitor) {
|
||||
this.pubSubMode = Boolean.FALSE;
|
||||
con = createConnection();
|
||||
}
|
||||
if (!(con instanceof QueueConnection)) {
|
||||
throw new javax.jms.IllegalStateException(
|
||||
"This SingleConnectionFactory does not hold a QueueConnection but rather: " + con);
|
||||
}
|
||||
return ((QueueConnection) con);
|
||||
}
|
||||
|
||||
public QueueConnection createQueueConnection(String username, String password) throws JMSException {
|
||||
throw new javax.jms.IllegalStateException(
|
||||
"SingleConnectionFactory does not support custom username and password");
|
||||
}
|
||||
|
||||
public TopicConnection createTopicConnection() throws JMSException {
|
||||
Connection con = null;
|
||||
synchronized (this.connectionMonitor) {
|
||||
this.pubSubMode = Boolean.TRUE;
|
||||
con = createConnection();
|
||||
}
|
||||
if (!(con instanceof TopicConnection)) {
|
||||
throw new javax.jms.IllegalStateException(
|
||||
"This SingleConnectionFactory does not hold a TopicConnection but rather: " + con);
|
||||
}
|
||||
return ((TopicConnection) con);
|
||||
}
|
||||
|
||||
public TopicConnection createTopicConnection(String username, String password) throws JMSException {
|
||||
throw new javax.jms.IllegalStateException(
|
||||
"SingleConnectionFactory does not support custom username and password");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the underlying shared Connection.
|
||||
* <p>Closes and reinitializes the Connection if an underlying
|
||||
* Connection is present already.
|
||||
* @throws javax.jms.JMSException if thrown by JMS API methods
|
||||
*/
|
||||
public void initConnection() throws JMSException {
|
||||
if (getTargetConnectionFactory() == null) {
|
||||
throw new IllegalStateException(
|
||||
"'targetConnectionFactory' is required for lazily initializing a Connection");
|
||||
}
|
||||
synchronized (this.connectionMonitor) {
|
||||
if (this.target != null) {
|
||||
closeConnection(this.target);
|
||||
}
|
||||
this.target = doCreateConnection();
|
||||
prepareConnection(this.target);
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Established shared JMS Connection: " + this.target);
|
||||
}
|
||||
this.connection = getSharedConnectionProxy(this.target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception listener callback that renews the underlying single Connection.
|
||||
*/
|
||||
public void onException(JMSException ex) {
|
||||
resetConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the underlying shared connection.
|
||||
* The provider of this ConnectionFactory needs to care for proper shutdown.
|
||||
* <p>As this bean implements DisposableBean, a bean factory will
|
||||
* automatically invoke this on destruction of its cached singletons.
|
||||
*/
|
||||
public void destroy() {
|
||||
resetConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the underlying shared Connection, to be reinitialized on next access.
|
||||
*/
|
||||
public void resetConnection() {
|
||||
synchronized (this.connectionMonitor) {
|
||||
if (this.target != null) {
|
||||
closeConnection(this.target);
|
||||
}
|
||||
this.target = null;
|
||||
this.connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JMS Connection via this template's ConnectionFactory.
|
||||
* @return the new JMS Connection
|
||||
* @throws javax.jms.JMSException if thrown by JMS API methods
|
||||
*/
|
||||
protected Connection doCreateConnection() throws JMSException {
|
||||
ConnectionFactory cf = getTargetConnectionFactory();
|
||||
if (Boolean.FALSE.equals(this.pubSubMode) && cf instanceof QueueConnectionFactory) {
|
||||
return ((QueueConnectionFactory) cf).createQueueConnection();
|
||||
}
|
||||
else if (Boolean.TRUE.equals(this.pubSubMode) && cf instanceof TopicConnectionFactory) {
|
||||
return ((TopicConnectionFactory) cf).createTopicConnection();
|
||||
}
|
||||
else {
|
||||
return getTargetConnectionFactory().createConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given Connection before it is exposed.
|
||||
* <p>The default implementation applies ExceptionListener and client id.
|
||||
* Can be overridden in subclasses.
|
||||
* @param con the Connection to prepare
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #setExceptionListener
|
||||
* @see #setReconnectOnException
|
||||
*/
|
||||
protected void prepareConnection(Connection con) throws JMSException {
|
||||
if (getClientId() != null) {
|
||||
con.setClientID(getClientId());
|
||||
}
|
||||
if (getExceptionListener() != null || isReconnectOnException()) {
|
||||
ExceptionListener listenerToUse = getExceptionListener();
|
||||
if (isReconnectOnException()) {
|
||||
listenerToUse = new InternalChainedExceptionListener(this, listenerToUse);
|
||||
}
|
||||
con.setExceptionListener(listenerToUse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method for obtaining a (potentially cached) Session.
|
||||
* <p>The default implementation always returns <code>null</code>.
|
||||
* Subclasses may override this for exposing specific Session handles,
|
||||
* possibly delegating to {@link #createSession} for the creation of raw
|
||||
* Session objects that will then get wrapped and returned from here.
|
||||
* @param con the JMS Connection to operate on
|
||||
* @param mode the Session acknowledgement mode
|
||||
* (<code>Session.TRANSACTED</code> or one of the common modes)
|
||||
* @return the Session to use, or <code>null</code> to indicate
|
||||
* creation of a raw standard Session
|
||||
* @throws JMSException if thrown by the JMS API
|
||||
*/
|
||||
protected Session getSession(Connection con, Integer mode) throws JMSException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default Session for this ConnectionFactory,
|
||||
* adaptign to JMS 1.0.2 style queue/topic mode if necessary.
|
||||
* @param con the JMS Connection to operate on
|
||||
* @param mode the Session acknowledgement mode
|
||||
* (<code>Session.TRANSACTED</code> or one of the common modes)
|
||||
* @return the newly created Session
|
||||
* @throws JMSException if thrown by the JMS API
|
||||
*/
|
||||
protected Session createSession(Connection con, Integer mode) throws JMSException {
|
||||
// Determine JMS API arguments...
|
||||
boolean transacted = (mode.intValue() == Session.SESSION_TRANSACTED);
|
||||
int ackMode = (transacted ? Session.AUTO_ACKNOWLEDGE : mode.intValue());
|
||||
// Now actually call the appropriate JMS factory method...
|
||||
if (Boolean.FALSE.equals(this.pubSubMode) && con instanceof QueueConnection) {
|
||||
return ((QueueConnection) con).createQueueSession(transacted, ackMode);
|
||||
}
|
||||
else if (Boolean.TRUE.equals(this.pubSubMode) && con instanceof TopicConnection) {
|
||||
return ((TopicConnection) con).createTopicSession(transacted, ackMode);
|
||||
}
|
||||
else {
|
||||
return con.createSession(transacted, ackMode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the given Connection.
|
||||
* @param con the Connection to close
|
||||
*/
|
||||
protected void closeConnection(Connection con) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Closing shared JMS Connection: " + this.target);
|
||||
}
|
||||
try {
|
||||
try {
|
||||
if (this.started) {
|
||||
this.started = false;
|
||||
con.stop();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
con.close();
|
||||
}
|
||||
}
|
||||
catch (javax.jms.IllegalStateException ex) {
|
||||
logger.debug("Ignoring Connection state exception - assuming already closed: " + ex);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.debug("Could not close shared JMS Connection", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the given Connection with a proxy that delegates every method call to it
|
||||
* but suppresses close calls. This is useful for allowing application code to
|
||||
* handle a special framework Connection just like an ordinary Connection from a
|
||||
* JMS ConnectionFactory.
|
||||
* @param target the original Connection to wrap
|
||||
* @return the wrapped Connection
|
||||
*/
|
||||
protected Connection getSharedConnectionProxy(Connection target) {
|
||||
List classes = new ArrayList(3);
|
||||
classes.add(Connection.class);
|
||||
if (target instanceof QueueConnection) {
|
||||
classes.add(QueueConnection.class);
|
||||
}
|
||||
if (target instanceof TopicConnection) {
|
||||
classes.add(TopicConnection.class);
|
||||
}
|
||||
return (Connection) Proxy.newProxyInstance(
|
||||
Connection.class.getClassLoader(),
|
||||
(Class[]) classes.toArray(new Class[classes.size()]),
|
||||
new SharedConnectionInvocationHandler(target));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invocation handler for a cached JMS Connection proxy.
|
||||
*/
|
||||
private class SharedConnectionInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final Connection target;
|
||||
|
||||
public SharedConnectionInvocationHandler(Connection target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
if (method.getName().equals("equals")) {
|
||||
// Only consider equal when proxies are identical.
|
||||
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
|
||||
}
|
||||
else if (method.getName().equals("hashCode")) {
|
||||
// Use hashCode of Connection proxy.
|
||||
return new Integer(System.identityHashCode(proxy));
|
||||
}
|
||||
else if (method.getName().equals("toString")) {
|
||||
return "Shared JMS Connection: " + this.target;
|
||||
}
|
||||
else if (method.getName().equals("setClientID")) {
|
||||
// Handle setClientID method: throw exception if not compatible.
|
||||
String currentClientId = this.target.getClientID();
|
||||
if (currentClientId != null && currentClientId.equals(args[0])) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
throw new javax.jms.IllegalStateException(
|
||||
"setClientID call not supported on proxy for shared Connection. " +
|
||||
"Set the 'clientId' property on the SingleConnectionFactory instead.");
|
||||
}
|
||||
}
|
||||
else if (method.getName().equals("setExceptionListener")) {
|
||||
// Handle setExceptionListener method: add to the chain.
|
||||
ExceptionListener currentExceptionListener = this.target.getExceptionListener();
|
||||
if (currentExceptionListener instanceof InternalChainedExceptionListener && args[0] != null) {
|
||||
((InternalChainedExceptionListener) currentExceptionListener).addDelegate((ExceptionListener) args[0]);
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
throw new javax.jms.IllegalStateException(
|
||||
"setExceptionListener call not supported on proxy for shared Connection. " +
|
||||
"Set the 'exceptionListener' property on the SingleConnectionFactory instead. " +
|
||||
"Alternatively, activate SingleConnectionFactory's 'reconnectOnException' feature, " +
|
||||
"which will allow for registering further ExceptionListeners to the recovery chain.");
|
||||
}
|
||||
}
|
||||
else if (method.getName().equals("start")) {
|
||||
// Handle start method: track started state.
|
||||
this.target.start();
|
||||
synchronized (connectionMonitor) {
|
||||
started = true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else if (method.getName().equals("stop")) {
|
||||
// Handle stop method: don't pass the call on.
|
||||
return null;
|
||||
}
|
||||
else if (method.getName().equals("close")) {
|
||||
// Handle close method: don't pass the call on.
|
||||
return null;
|
||||
}
|
||||
else if (method.getName().equals("createSession") || method.getName().equals("createQueueSession") ||
|
||||
method.getName().equals("createTopicSession")) {
|
||||
boolean transacted = ((Boolean) args[0]).booleanValue();
|
||||
Integer ackMode = (Integer) args[1];
|
||||
Integer mode = (transacted ? new Integer(Session.SESSION_TRANSACTED) : ackMode);
|
||||
Session session = getSession(this.target, mode);
|
||||
if (session != null) {
|
||||
if (!method.getReturnType().isInstance(session)) {
|
||||
String msg = "JMS Session does not implement specific domain: " + session;
|
||||
try {
|
||||
session.close();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.trace("Failed to close newly obtained JMS Session", ex);
|
||||
}
|
||||
throw new javax.jms.IllegalStateException(msg);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Object retVal = method.invoke(this.target, args);
|
||||
if (method.getName().equals("getExceptionListener") && retVal instanceof InternalChainedExceptionListener) {
|
||||
// Handle getExceptionListener method: hide internal chain.
|
||||
InternalChainedExceptionListener listener = (InternalChainedExceptionListener) retVal;
|
||||
return listener.getUserListener();
|
||||
}
|
||||
else {
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw ex.getTargetException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal chained ExceptionListener for handling the internal recovery listener
|
||||
* in combination with a user-specified listener.
|
||||
*/
|
||||
private static class InternalChainedExceptionListener extends ChainedExceptionListener {
|
||||
|
||||
private ExceptionListener userListener;
|
||||
|
||||
public InternalChainedExceptionListener(ExceptionListener internalListener, ExceptionListener userListener) {
|
||||
addDelegate(internalListener);
|
||||
if (userListener != null) {
|
||||
addDelegate(userListener);
|
||||
this.userListener = userListener;
|
||||
}
|
||||
}
|
||||
|
||||
public ExceptionListener getUserListener() {
|
||||
return this.userListener;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.connection;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.QueueConnectionFactory;
|
||||
import javax.jms.TopicConnectionFactory;
|
||||
|
||||
/**
|
||||
* A subclass of {@link SingleConnectionFactory} for the JMS 1.0.2 specification,
|
||||
* not relying on JMS 1.1 methods like SingleConnectionFactory itself.
|
||||
* This class can be used for JMS 1.0.2 providers, offering the same API as
|
||||
* SingleConnectionFactory does for JMS 1.1 providers.
|
||||
*
|
||||
* <p>You need to set the {@link #setPubSubDomain "pubSubDomain" property},
|
||||
* since this class will always explicitly differentiate between a
|
||||
* {@link javax.jms.QueueConnection} and a {@link javax.jms.TopicConnection}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see #setTargetConnectionFactory
|
||||
* @see #setPubSubDomain
|
||||
*/
|
||||
public class SingleConnectionFactory102 extends SingleConnectionFactory {
|
||||
|
||||
private boolean pubSubDomain = false;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new SingleConnectionFactory102 for bean-style usage.
|
||||
*/
|
||||
public SingleConnectionFactory102() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new SingleConnectionFactory102 that always returns a single
|
||||
* Connection that it will lazily create via the given target
|
||||
* ConnectionFactory.
|
||||
* @param connectionFactory the target ConnectionFactory
|
||||
* @param pubSubDomain whether the Publish/Subscribe domain (Topics) or
|
||||
* Point-to-Point domain (Queues) should be used
|
||||
*/
|
||||
public SingleConnectionFactory102(ConnectionFactory connectionFactory, boolean pubSubDomain) {
|
||||
setTargetConnectionFactory(connectionFactory);
|
||||
this.pubSubDomain = pubSubDomain;
|
||||
afterPropertiesSet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configure the factory with knowledge of the JMS domain used.
|
||||
* This tells the JMS 1.0.2 provider which class hierarchy to use for creating
|
||||
* Connections and Sessions.
|
||||
* <p>Default is Point-to-Point (Queues).
|
||||
* @param pubSubDomain <code>true</code> for Publish/Subscribe domain (Topics),
|
||||
* <code>false</code> for Point-to-Point domain (Queues)
|
||||
*/
|
||||
public void setPubSubDomain(boolean pubSubDomain) {
|
||||
this.pubSubDomain = pubSubDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the Publish/Subscribe domain (Topics) is used.
|
||||
* Otherwise, the Point-to-Point domain (Queues) is used.
|
||||
*/
|
||||
public boolean isPubSubDomain() {
|
||||
return this.pubSubDomain;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* In addition to checking whether the target ConnectionFactory is set,
|
||||
* make sure that the supplied factory is of the appropriate type for
|
||||
* the specified destination type: QueueConnectionFactory for queues,
|
||||
* TopicConnectionFactory for topics.
|
||||
*/
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
|
||||
// Make sure that the ConnectionFactory passed is consistent.
|
||||
// Some provider implementations of the ConnectionFactory interface
|
||||
// implement both domain interfaces under the cover, so just check if
|
||||
// the selected domain is consistent with the type of connection factory.
|
||||
if (isPubSubDomain()) {
|
||||
if (!(getTargetConnectionFactory() instanceof TopicConnectionFactory)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Specified a Spring JMS 1.0.2 SingleConnectionFactory for topics " +
|
||||
"but did not supply an instance of TopicConnectionFactory");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!(getTargetConnectionFactory() instanceof QueueConnectionFactory)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Specified a Spring JMS 1.0.2 SingleConnectionFactory for queues " +
|
||||
"but did not supply an instance of QueueConnectionFactory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected Connection doCreateConnection() throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicConnectionFactory) getTargetConnectionFactory()).createTopicConnection();
|
||||
}
|
||||
else {
|
||||
return ((QueueConnectionFactory) getTargetConnectionFactory()).createQueueConnection();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.connection;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.QueueConnection;
|
||||
import javax.jms.QueueConnectionFactory;
|
||||
import javax.jms.QueueSession;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.TopicConnection;
|
||||
import javax.jms.TopicConnectionFactory;
|
||||
import javax.jms.TopicSession;
|
||||
import javax.jms.TransactionInProgressException;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Proxy for a target JMS {@link javax.jms.ConnectionFactory}, adding awareness of
|
||||
* Spring-managed transactions. Similar to a transactional JNDI ConnectionFactory
|
||||
* as provided by a J2EE server.
|
||||
*
|
||||
* <p>Messaging code that should remain unaware of Spring's JMS support can work
|
||||
* with this proxy to seamlessly participate in Spring-managed transactions.
|
||||
* Note that the transaction manager, for example {@link JmsTransactionManager},
|
||||
* still needs to work with the underlying ConnectionFactory, <i>not</i> with
|
||||
* this proxy.
|
||||
*
|
||||
* <p><b>Make sure that TransactionAwareConnectionFactoryProxy is the outermost
|
||||
* ConnectionFactory of a chain of ConnectionFactory proxies/adapters.</b>
|
||||
* TransactionAwareConnectionFactoryProxy can delegate either directly to the
|
||||
* target factory or to some intermediary adapter like
|
||||
* {@link UserCredentialsConnectionFactoryAdapter}.
|
||||
*
|
||||
* <p>Delegates to {@link ConnectionFactoryUtils} for automatically participating
|
||||
* in thread-bound transactions, for example managed by {@link JmsTransactionManager}.
|
||||
* <code>createSession</code> calls and <code>close</code> calls on returned Sessions
|
||||
* will behave properly within a transaction, that is, always work on the transactional
|
||||
* Session. If not within a transaction, normal ConnectionFactory behavior applies.
|
||||
*
|
||||
* <p>Note that transactional JMS Sessions will be registered on a per-Connection
|
||||
* basis. To share the same JMS Session across a transaction, make sure that you
|
||||
* operate on the same JMS Connection handle - either through reusing the handle
|
||||
* or through configuring a {@link SingleConnectionFactory} underneath.
|
||||
*
|
||||
* <p>Returned transactional Session proxies will implement the {@link SessionProxy}
|
||||
* interface to allow for access to the underlying target Session. This is only
|
||||
* intended for accessing vendor-specific Session API or for testing purposes
|
||||
* (e.g. to perform manual transaction control). For typical application purposes,
|
||||
* simply use the standard JMS Session interface.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @see UserCredentialsConnectionFactoryAdapter
|
||||
* @see SingleConnectionFactory
|
||||
*/
|
||||
public class TransactionAwareConnectionFactoryProxy
|
||||
implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory {
|
||||
|
||||
private boolean synchedLocalTransactionAllowed = false;
|
||||
|
||||
private ConnectionFactory targetConnectionFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new TransactionAwareConnectionFactoryProxy.
|
||||
*/
|
||||
public TransactionAwareConnectionFactoryProxy() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new TransactionAwareConnectionFactoryProxy.
|
||||
* @param targetConnectionFactory the target ConnectionFactory
|
||||
*/
|
||||
public TransactionAwareConnectionFactoryProxy(ConnectionFactory targetConnectionFactory) {
|
||||
setTargetConnectionFactory(targetConnectionFactory);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the target ConnectionFactory that this ConnectionFactory should delegate to.
|
||||
*/
|
||||
public final void setTargetConnectionFactory(ConnectionFactory targetConnectionFactory) {
|
||||
Assert.notNull(targetConnectionFactory, "targetConnectionFactory must not be nul");
|
||||
this.targetConnectionFactory = targetConnectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the target ConnectionFactory that this ConnectionFactory should delegate to.
|
||||
*/
|
||||
protected ConnectionFactory getTargetConnectionFactory() {
|
||||
return this.targetConnectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to allow for a local JMS transaction that is synchronized with a
|
||||
* Spring-managed transaction (where the main transaction might be a JDBC-based
|
||||
* one for a specific DataSource, for example), with the JMS transaction committing
|
||||
* right after the main transaction. If not allowed, the given ConnectionFactory
|
||||
* needs to handle transaction enlistment underneath the covers.
|
||||
* <p>Default is "false": If not within a managed transaction that encompasses
|
||||
* the underlying JMS ConnectionFactory, standard Sessions will be returned.
|
||||
* Turn this flag on to allow participation in any Spring-managed transaction,
|
||||
* with a local JMS transaction synchronized with the main transaction.
|
||||
*/
|
||||
public void setSynchedLocalTransactionAllowed(boolean synchedLocalTransactionAllowed) {
|
||||
this.synchedLocalTransactionAllowed = synchedLocalTransactionAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to allow for a local JMS transaction that is synchronized
|
||||
* with a Spring-managed transaction.
|
||||
*/
|
||||
protected boolean isSynchedLocalTransactionAllowed() {
|
||||
return this.synchedLocalTransactionAllowed;
|
||||
}
|
||||
|
||||
|
||||
public Connection createConnection() throws JMSException {
|
||||
Connection targetConnection = this.targetConnectionFactory.createConnection();
|
||||
return getTransactionAwareConnectionProxy(targetConnection);
|
||||
}
|
||||
|
||||
public Connection createConnection(String username, String password) throws JMSException {
|
||||
Connection targetConnection = this.targetConnectionFactory.createConnection(username, password);
|
||||
return getTransactionAwareConnectionProxy(targetConnection);
|
||||
}
|
||||
|
||||
public QueueConnection createQueueConnection() throws JMSException {
|
||||
if (!(this.targetConnectionFactory instanceof QueueConnectionFactory)) {
|
||||
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is no QueueConnectionFactory");
|
||||
}
|
||||
QueueConnection targetConnection =
|
||||
((QueueConnectionFactory) this.targetConnectionFactory).createQueueConnection();
|
||||
return (QueueConnection) getTransactionAwareConnectionProxy(targetConnection);
|
||||
}
|
||||
|
||||
public QueueConnection createQueueConnection(String username, String password) throws JMSException {
|
||||
if (!(this.targetConnectionFactory instanceof QueueConnectionFactory)) {
|
||||
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is no QueueConnectionFactory");
|
||||
}
|
||||
QueueConnection targetConnection =
|
||||
((QueueConnectionFactory) this.targetConnectionFactory).createQueueConnection(username, password);
|
||||
return (QueueConnection) getTransactionAwareConnectionProxy(targetConnection);
|
||||
}
|
||||
|
||||
public TopicConnection createTopicConnection() throws JMSException {
|
||||
if (!(this.targetConnectionFactory instanceof TopicConnectionFactory)) {
|
||||
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is no TopicConnectionFactory");
|
||||
}
|
||||
TopicConnection targetConnection =
|
||||
((TopicConnectionFactory) this.targetConnectionFactory).createTopicConnection();
|
||||
return (TopicConnection) getTransactionAwareConnectionProxy(targetConnection);
|
||||
}
|
||||
|
||||
public TopicConnection createTopicConnection(String username, String password) throws JMSException {
|
||||
if (!(this.targetConnectionFactory instanceof TopicConnectionFactory)) {
|
||||
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is no TopicConnectionFactory");
|
||||
}
|
||||
TopicConnection targetConnection =
|
||||
((TopicConnectionFactory) this.targetConnectionFactory).createTopicConnection(username, password);
|
||||
return (TopicConnection) getTransactionAwareConnectionProxy(targetConnection);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrap the given Connection with a proxy that delegates every method call to it
|
||||
* but handles Session lookup in a transaction-aware fashion.
|
||||
* @param target the original Connection to wrap
|
||||
* @return the wrapped Connection
|
||||
*/
|
||||
private Connection getTransactionAwareConnectionProxy(Connection target) {
|
||||
List classes = new ArrayList(3);
|
||||
classes.add(Connection.class);
|
||||
if (target instanceof QueueConnection) {
|
||||
classes.add(QueueConnection.class);
|
||||
}
|
||||
if (target instanceof TopicConnection) {
|
||||
classes.add(TopicConnection.class);
|
||||
}
|
||||
return (Connection) Proxy.newProxyInstance(
|
||||
Connection.class.getClassLoader(),
|
||||
(Class[]) classes.toArray(new Class[classes.size()]),
|
||||
new TransactionAwareConnectionInvocationHandler(target));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invocation handler that exposes transactional Sessions for the underlying Connection.
|
||||
*/
|
||||
private class TransactionAwareConnectionInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final Connection target;
|
||||
|
||||
public TransactionAwareConnectionInvocationHandler(Connection target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
// Invocation on ConnectionProxy interface coming in...
|
||||
|
||||
if (method.getName().equals("equals")) {
|
||||
// Only consider equal when proxies are identical.
|
||||
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
|
||||
}
|
||||
else if (method.getName().equals("hashCode")) {
|
||||
// Use hashCode of Connection proxy.
|
||||
return new Integer(System.identityHashCode(proxy));
|
||||
}
|
||||
else if (Session.class.equals(method.getReturnType())) {
|
||||
Session session = ConnectionFactoryUtils.getTransactionalSession(
|
||||
getTargetConnectionFactory(), this.target, isSynchedLocalTransactionAllowed());
|
||||
if (session != null) {
|
||||
return getCloseSuppressingSessionProxy(session);
|
||||
}
|
||||
}
|
||||
else if (QueueSession.class.equals(method.getReturnType())) {
|
||||
QueueSession session = ConnectionFactoryUtils.getTransactionalQueueSession(
|
||||
(QueueConnectionFactory) getTargetConnectionFactory(), (QueueConnection) this.target,
|
||||
isSynchedLocalTransactionAllowed());
|
||||
if (session != null) {
|
||||
return getCloseSuppressingSessionProxy(session);
|
||||
}
|
||||
}
|
||||
else if (TopicSession.class.equals(method.getReturnType())) {
|
||||
TopicSession session = ConnectionFactoryUtils.getTransactionalTopicSession(
|
||||
(TopicConnectionFactory) getTargetConnectionFactory(), (TopicConnection) this.target,
|
||||
isSynchedLocalTransactionAllowed());
|
||||
if (session != null) {
|
||||
return getCloseSuppressingSessionProxy(session);
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke method on target Connection.
|
||||
try {
|
||||
return method.invoke(this.target, args);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw ex.getTargetException();
|
||||
}
|
||||
}
|
||||
|
||||
private Session getCloseSuppressingSessionProxy(Session target) {
|
||||
List classes = new ArrayList(3);
|
||||
classes.add(SessionProxy.class);
|
||||
if (target instanceof QueueSession) {
|
||||
classes.add(QueueSession.class);
|
||||
}
|
||||
if (target instanceof TopicSession) {
|
||||
classes.add(TopicSession.class);
|
||||
}
|
||||
return (Session) Proxy.newProxyInstance(
|
||||
SessionProxy.class.getClassLoader(),
|
||||
(Class[]) classes.toArray(new Class[classes.size()]),
|
||||
new CloseSuppressingSessionInvocationHandler(target));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invocation handler that suppresses close calls for a transactional JMS Session.
|
||||
*/
|
||||
private static class CloseSuppressingSessionInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final Session target;
|
||||
|
||||
public CloseSuppressingSessionInvocationHandler(Session target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
// Invocation on SessionProxy interface coming in...
|
||||
|
||||
if (method.getName().equals("equals")) {
|
||||
// Only consider equal when proxies are identical.
|
||||
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
|
||||
}
|
||||
else if (method.getName().equals("hashCode")) {
|
||||
// Use hashCode of Connection proxy.
|
||||
return new Integer(System.identityHashCode(proxy));
|
||||
}
|
||||
else if (method.getName().equals("commit")) {
|
||||
throw new TransactionInProgressException("Commit call not allowed within a managed transaction");
|
||||
}
|
||||
else if (method.getName().equals("rollback")) {
|
||||
throw new TransactionInProgressException("Rollback call not allowed within a managed transaction");
|
||||
}
|
||||
else if (method.getName().equals("close")) {
|
||||
// Handle close method: not to be closed within a transaction.
|
||||
return null;
|
||||
}
|
||||
else if (method.getName().equals("getTargetSession")) {
|
||||
// Handle getTargetSession method: return underlying Session.
|
||||
return this.target;
|
||||
}
|
||||
|
||||
// Invoke method on target Session.
|
||||
try {
|
||||
return method.invoke(this.target, args);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw ex.getTargetException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.connection;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.QueueConnection;
|
||||
import javax.jms.QueueConnectionFactory;
|
||||
import javax.jms.TopicConnection;
|
||||
import javax.jms.TopicConnectionFactory;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An adapter for a target JMS {@link javax.jms.ConnectionFactory}, applying the
|
||||
* given user credentials to every standard <code>createConnection()</code> call,
|
||||
* that is, implicitly invoking <code>createConnection(username, password)</code>
|
||||
* on the target. All other methods simply delegate to the corresponding methods
|
||||
* of the target ConnectionFactory.
|
||||
*
|
||||
* <p>Can be used to proxy a target JNDI ConnectionFactory that does not have user
|
||||
* credentials configured. Client code can work with the ConnectionFactory without
|
||||
* passing in username and password on every <code>createConnection()</code> call.
|
||||
*
|
||||
* <p>In the following example, client code can simply transparently work
|
||||
* with the preconfigured "myConnectionFactory", implicitly accessing
|
||||
* "myTargetConnectionFactory" with the specified user credentials.
|
||||
*
|
||||
* <pre class="code">
|
||||
* <bean id="myTargetConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
|
||||
* <property name="jndiName" value="java:comp/env/jms/mycf"/>
|
||||
* </bean>
|
||||
*
|
||||
* <bean id="myConnectionFactory" class="org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter">
|
||||
* <property name="targetConnectionFactory" ref="myTargetConnectionFactory"/>
|
||||
* <property name="username" value="myusername"/>
|
||||
* <property name="password" value="mypassword"/>
|
||||
* </bean></pre>
|
||||
*
|
||||
* <p>If the "username" is empty, this proxy will simply delegate to the standard
|
||||
* <code>createConnection()</code> method of the target ConnectionFactory.
|
||||
* This can be used to keep a UserCredentialsConnectionFactoryAdapter bean
|
||||
* definition just for the <i>option</i> of implicitly passing in user credentials
|
||||
* if the particular target ConnectionFactory requires it.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.2
|
||||
* @see #createConnection
|
||||
* @see #createQueueConnection
|
||||
* @see #createTopicConnection
|
||||
*/
|
||||
public class UserCredentialsConnectionFactoryAdapter
|
||||
implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory, InitializingBean {
|
||||
|
||||
private ConnectionFactory targetConnectionFactory;
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private final ThreadLocal threadBoundCredentials = new NamedThreadLocal("Current JMS user credentials");
|
||||
|
||||
|
||||
/**
|
||||
* Set the target ConnectionFactory that this ConnectionFactory should delegate to.
|
||||
*/
|
||||
public void setTargetConnectionFactory(ConnectionFactory targetConnectionFactory) {
|
||||
Assert.notNull(targetConnectionFactory, "'targetConnectionFactory' must not be null");
|
||||
this.targetConnectionFactory = targetConnectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the username that this adapter should use for retrieving Connections.
|
||||
* Default is no specific user.
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the password that this adapter should use for retrieving Connections.
|
||||
* Default is no specific password.
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() {
|
||||
if (this.targetConnectionFactory == null) {
|
||||
throw new IllegalArgumentException("Property 'targetConnectionFactory' is required");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set user credententials for this proxy and the current thread.
|
||||
* The given username and password will be applied to all subsequent
|
||||
* <code>createConnection()</code> calls on this ConnectionFactory proxy.
|
||||
* <p>This will override any statically specified user credentials,
|
||||
* that is, values of the "username" and "password" bean properties.
|
||||
* @param username the username to apply
|
||||
* @param password the password to apply
|
||||
* @see #removeCredentialsFromCurrentThread
|
||||
*/
|
||||
public void setCredentialsForCurrentThread(String username, String password) {
|
||||
this.threadBoundCredentials.set(new JmsUserCredentials(username, password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any user credentials for this proxy from the current thread.
|
||||
* Statically specified user credentials apply again afterwards.
|
||||
* @see #setCredentialsForCurrentThread
|
||||
*/
|
||||
public void removeCredentialsFromCurrentThread() {
|
||||
this.threadBoundCredentials.set(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether there are currently thread-bound credentials,
|
||||
* using them if available, falling back to the statically specified
|
||||
* username and password (i.e. values of the bean properties) else.
|
||||
* @see #doCreateConnection
|
||||
*/
|
||||
public final Connection createConnection() throws JMSException {
|
||||
JmsUserCredentials threadCredentials = (JmsUserCredentials) this.threadBoundCredentials.get();
|
||||
if (threadCredentials != null) {
|
||||
return doCreateConnection(threadCredentials.username, threadCredentials.password);
|
||||
}
|
||||
else {
|
||||
return doCreateConnection(this.username, this.password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate the call straight to the target ConnectionFactory.
|
||||
*/
|
||||
public Connection createConnection(String username, String password) throws JMSException {
|
||||
return doCreateConnection(username, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation delegates to the <code>createConnection(username, password)</code>
|
||||
* method of the target ConnectionFactory, passing in the specified user credentials.
|
||||
* If the specified username is empty, it will simply delegate to the standard
|
||||
* <code>createConnection()</code> method of the target ConnectionFactory.
|
||||
* @param username the username to use
|
||||
* @param password the password to use
|
||||
* @return the Connection
|
||||
* @see javax.jms.ConnectionFactory#createConnection(String, String)
|
||||
* @see javax.jms.ConnectionFactory#createConnection()
|
||||
*/
|
||||
protected Connection doCreateConnection(String username, String password) throws JMSException {
|
||||
Assert.state(this.targetConnectionFactory != null, "'targetConnectionFactory' is required");
|
||||
if (StringUtils.hasLength(username)) {
|
||||
return this.targetConnectionFactory.createConnection(username, password);
|
||||
}
|
||||
else {
|
||||
return this.targetConnectionFactory.createConnection();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether there are currently thread-bound credentials,
|
||||
* using them if available, falling back to the statically specified
|
||||
* username and password (i.e. values of the bean properties) else.
|
||||
* @see #doCreateQueueConnection
|
||||
*/
|
||||
public final QueueConnection createQueueConnection() throws JMSException {
|
||||
JmsUserCredentials threadCredentials = (JmsUserCredentials) this.threadBoundCredentials.get();
|
||||
if (threadCredentials != null) {
|
||||
return doCreateQueueConnection(threadCredentials.username, threadCredentials.password);
|
||||
}
|
||||
else {
|
||||
return doCreateQueueConnection(this.username, this.password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate the call straight to the target QueueConnectionFactory.
|
||||
*/
|
||||
public QueueConnection createQueueConnection(String username, String password) throws JMSException {
|
||||
return doCreateQueueConnection(username, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation delegates to the <code>createQueueConnection(username, password)</code>
|
||||
* method of the target QueueConnectionFactory, passing in the specified user credentials.
|
||||
* If the specified username is empty, it will simply delegate to the standard
|
||||
* <code>createQueueConnection()</code> method of the target ConnectionFactory.
|
||||
* @param username the username to use
|
||||
* @param password the password to use
|
||||
* @return the Connection
|
||||
* @see javax.jms.QueueConnectionFactory#createQueueConnection(String, String)
|
||||
* @see javax.jms.QueueConnectionFactory#createQueueConnection()
|
||||
*/
|
||||
protected QueueConnection doCreateQueueConnection(String username, String password) throws JMSException {
|
||||
Assert.state(this.targetConnectionFactory != null, "'targetConnectionFactory' is required");
|
||||
if (!(this.targetConnectionFactory instanceof QueueConnectionFactory)) {
|
||||
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is not a QueueConnectionFactory");
|
||||
}
|
||||
QueueConnectionFactory queueFactory = (QueueConnectionFactory) this.targetConnectionFactory;
|
||||
if (StringUtils.hasLength(username)) {
|
||||
return queueFactory.createQueueConnection(username, password);
|
||||
}
|
||||
else {
|
||||
return queueFactory.createQueueConnection();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether there are currently thread-bound credentials,
|
||||
* using them if available, falling back to the statically specified
|
||||
* username and password (i.e. values of the bean properties) else.
|
||||
* @see #doCreateTopicConnection
|
||||
*/
|
||||
public final TopicConnection createTopicConnection() throws JMSException {
|
||||
JmsUserCredentials threadCredentials = (JmsUserCredentials) this.threadBoundCredentials.get();
|
||||
if (threadCredentials != null) {
|
||||
return doCreateTopicConnection(threadCredentials.username, threadCredentials.password);
|
||||
}
|
||||
else {
|
||||
return doCreateTopicConnection(this.username, this.password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate the call straight to the target TopicConnectionFactory.
|
||||
*/
|
||||
public TopicConnection createTopicConnection(String username, String password) throws JMSException {
|
||||
return doCreateTopicConnection(username, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation delegates to the <code>createTopicConnection(username, password)</code>
|
||||
* method of the target TopicConnectionFactory, passing in the specified user credentials.
|
||||
* If the specified username is empty, it will simply delegate to the standard
|
||||
* <code>createTopicConnection()</code> method of the target ConnectionFactory.
|
||||
* @param username the username to use
|
||||
* @param password the password to use
|
||||
* @return the Connection
|
||||
* @see javax.jms.TopicConnectionFactory#createTopicConnection(String, String)
|
||||
* @see javax.jms.TopicConnectionFactory#createTopicConnection()
|
||||
*/
|
||||
protected TopicConnection doCreateTopicConnection(String username, String password) throws JMSException {
|
||||
Assert.state(this.targetConnectionFactory != null, "'targetConnectionFactory' is required");
|
||||
if (!(this.targetConnectionFactory instanceof TopicConnectionFactory)) {
|
||||
throw new javax.jms.IllegalStateException("'targetConnectionFactory' is not a TopicConnectionFactory");
|
||||
}
|
||||
TopicConnectionFactory queueFactory = (TopicConnectionFactory) this.targetConnectionFactory;
|
||||
if (StringUtils.hasLength(username)) {
|
||||
return queueFactory.createTopicConnection(username, password);
|
||||
}
|
||||
else {
|
||||
return queueFactory.createTopicConnection();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class used as ThreadLocal value.
|
||||
*/
|
||||
private static class JmsUserCredentials {
|
||||
|
||||
public final String username;
|
||||
|
||||
public final String password;
|
||||
|
||||
private JmsUserCredentials(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "JmsUserCredentials[username='" + this.username + "',password='" + this.password + "']";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
Provides a PlatformTransactionManager implementation for a single
|
||||
JMS ConnectionFactory, and a SingleConnectionFactory adapter.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.core;
|
||||
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.QueueBrowser;
|
||||
import javax.jms.Session;
|
||||
|
||||
/**
|
||||
* Callback for browsing the messages in a JMS queue.
|
||||
*
|
||||
* <p>To be used with JmsTemplate's callback methods that take a BrowserCallback
|
||||
* argument, often implemented as an anonymous inner class.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5.1
|
||||
* @see JmsTemplate#browse(BrowserCallback)
|
||||
* @see JmsTemplate#browseSelected(String, BrowserCallback)
|
||||
*/
|
||||
public interface BrowserCallback {
|
||||
|
||||
/**
|
||||
* Perform operations on the given {@link javax.jms.Session} and {@link javax.jms.QueueBrowser}.
|
||||
* <p>The message producer is not associated with any destination.
|
||||
* @param session the JMS <code>Session</code> object to use
|
||||
* @param browser the JMS <code>QueueBrowser</code> object to use
|
||||
* @return a result object from working with the <code>Session</code>, if any (can be <code>null</code>)
|
||||
* @throws javax.jms.JMSException if thrown by JMS API methods
|
||||
*/
|
||||
Object doInJms(Session session, QueueBrowser browser) throws JMSException;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,425 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.core;
|
||||
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.Queue;
|
||||
|
||||
import org.springframework.jms.JmsException;
|
||||
|
||||
/**
|
||||
* Specifies a basic set of JMS operations.
|
||||
*
|
||||
* <p>Implemented by {@link JmsTemplate}. Not often used but a useful option
|
||||
* to enhance testability, as it can easily be mocked or stubbed.
|
||||
*
|
||||
* <p>Provides <code>JmsTemplate's</code> <code>send(..)</code> and
|
||||
* <code>receive(..)</code> methods that mirror various JMS API methods.
|
||||
* See the JMS specification and javadocs for details on those methods.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see JmsTemplate
|
||||
* @see javax.jms.Destination
|
||||
* @see javax.jms.Session
|
||||
* @see javax.jms.MessageProducer
|
||||
* @see javax.jms.MessageConsumer
|
||||
*/
|
||||
public interface JmsOperations {
|
||||
|
||||
/**
|
||||
* Execute the action specified by the given action object within a JMS Session.
|
||||
* <p>When used with a 1.0.2 provider, you may need to downcast
|
||||
* to the appropriate domain implementation, either QueueSession or
|
||||
* TopicSession in the action objects doInJms callback method.
|
||||
* @param action callback object that exposes the session
|
||||
* @return the result object from working with the session
|
||||
* @throws JmsException if there is any problem
|
||||
*/
|
||||
Object execute(SessionCallback action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Send messages to the default JMS destination (or one specified
|
||||
* for each send operation). The callback gives access to the JMS Session
|
||||
* and MessageProducer in order to perform complex send operations.
|
||||
* @param action callback object that exposes the session/producer pair
|
||||
* @return the result object from working with the session
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object execute(ProducerCallback action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Send messages to a JMS destination. The callback gives access to the JMS Session
|
||||
* and MessageProducer in order to perform complex send operations.
|
||||
* @param destination the destination to send messages to
|
||||
* @param action callback object that exposes the session/producer pair
|
||||
* @return the result object from working with the session
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object execute(Destination destination, ProducerCallback action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Send messages to a JMS destination. The callback gives access to the JMS Session
|
||||
* and MessageProducer in order to perform complex send operations.
|
||||
* @param destinationName the name of the destination to send messages to
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
* @param action callback object that exposes the session/producer pair
|
||||
* @return the result object from working with the session
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object execute(String destinationName, ProducerCallback action) throws JmsException;
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Convenience methods for sending messages
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Send a message to the default destination.
|
||||
* <p>This will only work with a default destination specified!
|
||||
* @param messageCreator callback to create a message
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
void send(MessageCreator messageCreator) throws JmsException;
|
||||
|
||||
/**
|
||||
* Send a message to the specified destination.
|
||||
* The MessageCreator callback creates the message given a Session.
|
||||
* @param destination the destination to send this message to
|
||||
* @param messageCreator callback to create a message
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
void send(Destination destination, MessageCreator messageCreator) throws JmsException;
|
||||
|
||||
/**
|
||||
* Send a message to the specified destination.
|
||||
* The MessageCreator callback creates the message given a Session.
|
||||
* @param destinationName the name of the destination to send this message to
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
* @param messageCreator callback to create a message
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
void send(String destinationName, MessageCreator messageCreator) throws JmsException;
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Convenience methods for sending auto-converted messages
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Send the given object to the default destination, converting the object
|
||||
* to a JMS message with a configured MessageConverter.
|
||||
* <p>This will only work with a default destination specified!
|
||||
* @param message the object to convert to a message
|
||||
* @throws JmsException converted checked JMSException to unchecked
|
||||
*/
|
||||
void convertAndSend(Object message) throws JmsException;
|
||||
|
||||
/**
|
||||
* Send the given object to the specified destination, converting the object
|
||||
* to a JMS message with a configured MessageConverter.
|
||||
* @param destination the destination to send this message to
|
||||
* @param message the object to convert to a message
|
||||
* @throws JmsException converted checked JMSException to unchecked
|
||||
*/
|
||||
void convertAndSend(Destination destination, Object message) throws JmsException;
|
||||
|
||||
/**
|
||||
* Send the given object to the specified destination, converting the object
|
||||
* to a JMS message with a configured MessageConverter.
|
||||
* @param destinationName the name of the destination to send this message to
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
* @param message the object to convert to a message
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
void convertAndSend(String destinationName, Object message) throws JmsException;
|
||||
|
||||
/**
|
||||
* Send the given object to the default destination, converting the object
|
||||
* to a JMS message with a configured MessageConverter. The MessagePostProcessor
|
||||
* callback allows for modification of the message after conversion.
|
||||
* <p>This will only work with a default destination specified!
|
||||
* @param message the object to convert to a message
|
||||
* @param postProcessor the callback to modify the message
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
void convertAndSend(Object message, MessagePostProcessor postProcessor)
|
||||
throws JmsException;
|
||||
|
||||
/**
|
||||
* Send the given object to the specified destination, converting the object
|
||||
* to a JMS message with a configured MessageConverter. The MessagePostProcessor
|
||||
* callback allows for modification of the message after conversion.
|
||||
* @param destination the destination to send this message to
|
||||
* @param message the object to convert to a message
|
||||
* @param postProcessor the callback to modify the message
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
void convertAndSend(Destination destination, Object message, MessagePostProcessor postProcessor)
|
||||
throws JmsException;
|
||||
|
||||
/**
|
||||
* Send the given object to the specified destination, converting the object
|
||||
* to a JMS message with a configured MessageConverter. The MessagePostProcessor
|
||||
* callback allows for modification of the message after conversion.
|
||||
* @param destinationName the name of the destination to send this message to
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
* @param message the object to convert to a message.
|
||||
* @param postProcessor the callback to modify the message
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
void convertAndSend(String destinationName, Object message, MessagePostProcessor postProcessor)
|
||||
throws JmsException;
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Convenience methods for receiving messages
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Receive a message synchronously from the default destination, but only
|
||||
* wait up to a specified time for delivery.
|
||||
* <p>This method should be used carefully, since it will block the thread
|
||||
* until the message becomes available or until the timeout value is exceeded.
|
||||
* <p>This will only work with a default destination specified!
|
||||
* @return the message received by the consumer, or <code>null</code> if the timeout expires
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Message receive() throws JmsException;
|
||||
|
||||
/**
|
||||
* Receive a message synchronously from the specified destination, but only
|
||||
* wait up to a specified time for delivery.
|
||||
* <p>This method should be used carefully, since it will block the thread
|
||||
* until the message becomes available or until the timeout value is exceeded.
|
||||
* @param destination the destination to receive a message from
|
||||
* @return the message received by the consumer, or <code>null</code> if the timeout expires
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Message receive(Destination destination) throws JmsException;
|
||||
|
||||
/**
|
||||
* Receive a message synchronously from the specified destination, but only
|
||||
* wait up to a specified time for delivery.
|
||||
* <p>This method should be used carefully, since it will block the thread
|
||||
* until the message becomes available or until the timeout value is exceeded.
|
||||
* @param destinationName the name of the destination to send this message to
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
* @return the message received by the consumer, or <code>null</code> if the timeout expires
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Message receive(String destinationName) throws JmsException;
|
||||
|
||||
/**
|
||||
* Receive a message synchronously from the default destination, but only
|
||||
* wait up to a specified time for delivery.
|
||||
* <p>This method should be used carefully, since it will block the thread
|
||||
* until the message becomes available or until the timeout value is exceeded.
|
||||
* <p>This will only work with a default destination specified!
|
||||
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
|
||||
* See the JMS specification for a detailed definition of selector expressions.
|
||||
* @return the message received by the consumer, or <code>null</code> if the timeout expires
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Message receiveSelected(String messageSelector) throws JmsException;
|
||||
|
||||
/**
|
||||
* Receive a message synchronously from the specified destination, but only
|
||||
* wait up to a specified time for delivery.
|
||||
* <p>This method should be used carefully, since it will block the thread
|
||||
* until the message becomes available or until the timeout value is exceeded.
|
||||
* @param destination the destination to receive a message from
|
||||
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
|
||||
* See the JMS specification for a detailed definition of selector expressions.
|
||||
* @return the message received by the consumer, or <code>null</code> if the timeout expires
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Message receiveSelected(Destination destination, String messageSelector) throws JmsException;
|
||||
|
||||
/**
|
||||
* Receive a message synchronously from the specified destination, but only
|
||||
* wait up to a specified time for delivery.
|
||||
* <p>This method should be used carefully, since it will block the thread
|
||||
* until the message becomes available or until the timeout value is exceeded.
|
||||
* @param destinationName the name of the destination to send this message to
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
|
||||
* See the JMS specification for a detailed definition of selector expressions.
|
||||
* @return the message received by the consumer, or <code>null</code> if the timeout expires
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Message receiveSelected(String destinationName, String messageSelector) throws JmsException;
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Convenience methods for receiving auto-converted messages
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Receive a message synchronously from the default destination, but only
|
||||
* wait up to a specified time for delivery. Convert the message into an
|
||||
* object with a configured MessageConverter.
|
||||
* <p>This method should be used carefully, since it will block the thread
|
||||
* until the message becomes available or until the timeout value is exceeded.
|
||||
* <p>This will only work with a default destination specified!
|
||||
* @return the message produced for the consumer or <code>null</code> if the timeout expires.
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object receiveAndConvert() throws JmsException;
|
||||
|
||||
/**
|
||||
* Receive a message synchronously from the specified destination, but only
|
||||
* wait up to a specified time for delivery. Convert the message into an
|
||||
* object with a configured MessageConverter.
|
||||
* <p>This method should be used carefully, since it will block the thread
|
||||
* until the message becomes available or until the timeout value is exceeded.
|
||||
* @param destination the destination to receive a message from
|
||||
* @return the message produced for the consumer or <code>null</code> if the timeout expires.
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object receiveAndConvert(Destination destination) throws JmsException;
|
||||
|
||||
/**
|
||||
* Receive a message synchronously from the specified destination, but only
|
||||
* wait up to a specified time for delivery. Convert the message into an
|
||||
* object with a configured MessageConverter.
|
||||
* <p>This method should be used carefully, since it will block the thread
|
||||
* until the message becomes available or until the timeout value is exceeded.
|
||||
* @param destinationName the name of the destination to send this message to
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
* @return the message produced for the consumer or <code>null</code> if the timeout expires.
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object receiveAndConvert(String destinationName) throws JmsException;
|
||||
|
||||
/**
|
||||
* Receive a message synchronously from the default destination, but only
|
||||
* wait up to a specified time for delivery. Convert the message into an
|
||||
* object with a configured MessageConverter.
|
||||
* <p>This method should be used carefully, since it will block the thread
|
||||
* until the message becomes available or until the timeout value is exceeded.
|
||||
* <p>This will only work with a default destination specified!
|
||||
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
|
||||
* See the JMS specification for a detailed definition of selector expressions.
|
||||
* @return the message produced for the consumer or <code>null</code> if the timeout expires.
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object receiveSelectedAndConvert(String messageSelector) throws JmsException;
|
||||
|
||||
/**
|
||||
* Receive a message synchronously from the specified destination, but only
|
||||
* wait up to a specified time for delivery. Convert the message into an
|
||||
* object with a configured MessageConverter.
|
||||
* <p>This method should be used carefully, since it will block the thread
|
||||
* until the message becomes available or until the timeout value is exceeded.
|
||||
* @param destination the destination to receive a message from
|
||||
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
|
||||
* See the JMS specification for a detailed definition of selector expressions.
|
||||
* @return the message produced for the consumer or <code>null</code> if the timeout expires.
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object receiveSelectedAndConvert(Destination destination, String messageSelector) throws JmsException;
|
||||
|
||||
/**
|
||||
* Receive a message synchronously from the specified destination, but only
|
||||
* wait up to a specified time for delivery. Convert the message into an
|
||||
* object with a configured MessageConverter.
|
||||
* <p>This method should be used carefully, since it will block the thread
|
||||
* until the message becomes available or until the timeout value is exceeded.
|
||||
* @param destinationName the name of the destination to send this message to
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
|
||||
* See the JMS specification for a detailed definition of selector expressions.
|
||||
* @return the message produced for the consumer or <code>null</code> if the timeout expires.
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object receiveSelectedAndConvert(String destinationName, String messageSelector) throws JmsException;
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Convenience methods for browsing messages
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Browse messages in the default JMS queue. The callback gives access to the JMS
|
||||
* Session and QueueBrowser in order to browse the queue and react to the contents.
|
||||
* @param action callback object that exposes the session/browser pair
|
||||
* @return the result object from working with the session
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object browse(BrowserCallback action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Browse messages in a JMS queue. The callback gives access to the JMS Session
|
||||
* and QueueBrowser in order to browse the queue and react to the contents.
|
||||
* @param queue the queue to browse
|
||||
* @param action callback object that exposes the session/browser pair
|
||||
* @return the result object from working with the session
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object browse(Queue queue, BrowserCallback action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Browse messages in a JMS queue. The callback gives access to the JMS Session
|
||||
* and QueueBrowser in order to browse the queue and react to the contents.
|
||||
* @param queueName the name of the queue to browse
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
* @param action callback object that exposes the session/browser pair
|
||||
* @return the result object from working with the session
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object browse(String queueName, BrowserCallback action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Browse selected messages in a JMS queue. The callback gives access to the JMS
|
||||
* Session and QueueBrowser in order to browse the queue and react to the contents.
|
||||
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
|
||||
* See the JMS specification for a detailed definition of selector expressions.
|
||||
* @param action callback object that exposes the session/browser pair
|
||||
* @return the result object from working with the session
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object browseSelected(String messageSelector, BrowserCallback action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Browse selected messages in a JMS queue. The callback gives access to the JMS
|
||||
* Session and QueueBrowser in order to browse the queue and react to the contents.
|
||||
* @param queue the queue to browse
|
||||
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
|
||||
* See the JMS specification for a detailed definition of selector expressions.
|
||||
* @param action callback object that exposes the session/browser pair
|
||||
* @return the result object from working with the session
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object browseSelected(Queue queue, String messageSelector, BrowserCallback action) throws JmsException;
|
||||
|
||||
/**
|
||||
* Browse selected messages in a JMS queue. The callback gives access to the JMS
|
||||
* Session and QueueBrowser in order to browse the queue and react to the contents.
|
||||
* @param queueName the name of the queue to browse
|
||||
* (to be resolved to an actual destination by a DestinationResolver)
|
||||
* @param messageSelector the JMS message selector expression (or <code>null</code> if none).
|
||||
* See the JMS specification for a detailed definition of selector expressions.
|
||||
* @param action callback object that exposes the session/browser pair
|
||||
* @return the result object from working with the session
|
||||
* @throws JmsException checked JMSException converted to unchecked
|
||||
*/
|
||||
Object browseSelected(String queueName, String messageSelector, BrowserCallback action) throws JmsException;
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.core;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageConsumer;
|
||||
import javax.jms.MessageProducer;
|
||||
import javax.jms.Queue;
|
||||
import javax.jms.QueueBrowser;
|
||||
import javax.jms.QueueConnection;
|
||||
import javax.jms.QueueConnectionFactory;
|
||||
import javax.jms.QueueSender;
|
||||
import javax.jms.QueueSession;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.Topic;
|
||||
import javax.jms.TopicConnection;
|
||||
import javax.jms.TopicConnectionFactory;
|
||||
import javax.jms.TopicPublisher;
|
||||
import javax.jms.TopicSession;
|
||||
|
||||
import org.springframework.jms.connection.JmsResourceHolder;
|
||||
import org.springframework.jms.support.converter.SimpleMessageConverter102;
|
||||
|
||||
/**
|
||||
* A subclass of {@link JmsTemplate} for the JMS 1.0.2 specification, not relying
|
||||
* on JMS 1.1 methods like JmsTemplate itself. This class can be used for JMS
|
||||
* 1.0.2 providers, offering the same API as JmsTemplate does for JMS 1.1 providers.
|
||||
*
|
||||
* <p>You must specify the domain (or style) of messaging to be either
|
||||
* Point-to-Point (Queues) or Publish/Subscribe (Topics), using the
|
||||
* {@link #setPubSubDomain "pubSubDomain" property}.
|
||||
* Point-to-Point (Queues) is the default domain.
|
||||
*
|
||||
* <p>The "pubSubDomain" property is an important setting due to the use of similar
|
||||
* but separate class hierarchies in the JMS 1.0.2 API. JMS 1.1 provides a new
|
||||
* domain-independent API that allows for easy mix-and-match use of Point-to-Point
|
||||
* and Publish/Subscribe styles.
|
||||
*
|
||||
* <p>This template uses a
|
||||
* {@link org.springframework.jms.support.destination.DynamicDestinationResolver}
|
||||
* and a {@link org.springframework.jms.support.converter.SimpleMessageConverter102}
|
||||
* as default strategies for resolving a destination name and converting a message,
|
||||
* respectively.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see #setConnectionFactory
|
||||
* @see #setPubSubDomain
|
||||
* @see javax.jms.Queue
|
||||
* @see javax.jms.Topic
|
||||
* @see javax.jms.QueueSession
|
||||
* @see javax.jms.TopicSession
|
||||
* @see javax.jms.QueueSender
|
||||
* @see javax.jms.TopicPublisher
|
||||
* @see javax.jms.QueueReceiver
|
||||
* @see javax.jms.TopicSubscriber
|
||||
*/
|
||||
public class JmsTemplate102 extends JmsTemplate {
|
||||
|
||||
/**
|
||||
* Create a new JmsTemplate102 for bean-style usage.
|
||||
* <p>Note: The ConnectionFactory has to be set before using the instance.
|
||||
* This constructor can be used to prepare a JmsTemplate via a BeanFactory,
|
||||
* typically setting the ConnectionFactory via setConnectionFactory.
|
||||
* @see #setConnectionFactory
|
||||
*/
|
||||
public JmsTemplate102() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JmsTemplate102, given a ConnectionFactory.
|
||||
* @param connectionFactory the ConnectionFactory to obtain Connections from
|
||||
* @param pubSubDomain whether the Publish/Subscribe domain (Topics) or
|
||||
* Point-to-Point domain (Queues) should be used
|
||||
* @see #setPubSubDomain
|
||||
*/
|
||||
public JmsTemplate102(ConnectionFactory connectionFactory, boolean pubSubDomain) {
|
||||
this();
|
||||
setConnectionFactory(connectionFactory);
|
||||
setPubSubDomain(pubSubDomain);
|
||||
afterPropertiesSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the default implementations for the template's strategies:
|
||||
* DynamicDestinationResolver and SimpleMessageConverter102.
|
||||
* @see #setDestinationResolver
|
||||
* @see #setMessageConverter
|
||||
* @see org.springframework.jms.support.destination.DynamicDestinationResolver
|
||||
* @see org.springframework.jms.support.converter.SimpleMessageConverter102
|
||||
*/
|
||||
protected void initDefaultStrategies() {
|
||||
setMessageConverter(new SimpleMessageConverter102());
|
||||
}
|
||||
|
||||
/**
|
||||
* In addition to checking if the connection factory is set, make sure
|
||||
* that the supplied connection factory is of the appropriate type for
|
||||
* the specified destination type: QueueConnectionFactory for queues,
|
||||
* and TopicConnectionFactory for topics.
|
||||
*/
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
|
||||
// Make sure that the ConnectionFactory passed is consistent.
|
||||
// Some provider implementations of the ConnectionFactory interface
|
||||
// implement both domain interfaces under the cover, so just check if
|
||||
// the selected domain is consistent with the type of connection factory.
|
||||
if (isPubSubDomain()) {
|
||||
if (!(getConnectionFactory() instanceof TopicConnectionFactory)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Specified a Spring JMS 1.0.2 template for topics " +
|
||||
"but did not supply an instance of TopicConnectionFactory");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!(getConnectionFactory() instanceof QueueConnectionFactory)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Specified a Spring JMS 1.0.2 template for queues " +
|
||||
"but did not supply an instance of QueueConnectionFactory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to accept either
|
||||
* a QueueConnection or a TopicConnection, depending on the domain.
|
||||
*/
|
||||
protected Connection getConnection(JmsResourceHolder holder) {
|
||||
return holder.getConnection(isPubSubDomain() ? (Class) TopicConnection.class : QueueConnection.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to accept either
|
||||
* a QueueSession or a TopicSession, depending on the domain.
|
||||
*/
|
||||
protected Session getSession(JmsResourceHolder holder) {
|
||||
return holder.getSession(isPubSubDomain() ? (Class) TopicSession.class : QueueSession.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected Connection createConnection() throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicConnectionFactory) getConnectionFactory()).createTopicConnection();
|
||||
}
|
||||
else {
|
||||
return ((QueueConnectionFactory) getConnectionFactory()).createQueueConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected Session createSession(Connection con) throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicConnection) con).createTopicSession(isSessionTransacted(), getSessionAcknowledgeMode());
|
||||
}
|
||||
else {
|
||||
return ((QueueConnection) con).createQueueSession(isSessionTransacted(), getSessionAcknowledgeMode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected MessageProducer doCreateProducer(Session session, Destination destination) throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicSession) session).createPublisher((Topic) destination);
|
||||
}
|
||||
else {
|
||||
return ((QueueSession) session).createSender((Queue) destination);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected MessageConsumer createConsumer(Session session, Destination destination, String messageSelector)
|
||||
throws JMSException {
|
||||
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicSession) session).createSubscriber((Topic) destination, messageSelector, isPubSubNoLocal());
|
||||
}
|
||||
else {
|
||||
return ((QueueSession) session).createReceiver((Queue) destination, messageSelector);
|
||||
}
|
||||
}
|
||||
|
||||
protected QueueBrowser createBrowser(Session session, Queue queue, String messageSelector)
|
||||
throws JMSException {
|
||||
|
||||
if (isPubSubDomain()) {
|
||||
throw new javax.jms.IllegalStateException("Cannot create QueueBrowser for a TopicSession");
|
||||
}
|
||||
else {
|
||||
return ((QueueSession) session).createBrowser(queue, messageSelector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected void doSend(MessageProducer producer, Message message) throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
if (isExplicitQosEnabled()) {
|
||||
((TopicPublisher) producer).publish(message, getDeliveryMode(), getPriority(), getTimeToLive());
|
||||
}
|
||||
else {
|
||||
((TopicPublisher) producer).publish(message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isExplicitQosEnabled()) {
|
||||
((QueueSender) producer).send(message, getDeliveryMode(), getPriority(), getTimeToLive());
|
||||
}
|
||||
else {
|
||||
((QueueSender) producer).send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to avoid using
|
||||
* JMS 1.1's Session <code>getAcknowledgeMode()</code> method.
|
||||
* The best we can do here is to check the setting on the template.
|
||||
* @see #getSessionAcknowledgeMode()
|
||||
*/
|
||||
protected boolean isClientAcknowledge(Session session) throws JMSException {
|
||||
return (getSessionAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.core;
|
||||
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.MessageProducer;
|
||||
import javax.jms.Session;
|
||||
|
||||
/**
|
||||
* Callback for sending a message to a JMS destination.
|
||||
*
|
||||
* <p>To be used with JmsTemplate's callback methods that take a ProducerCallback
|
||||
* argument, often implemented as an anonymous inner class.
|
||||
*
|
||||
* <p>The typical implementation will perform multiple operations on the
|
||||
* supplied JMS {@link Session} and {@link MessageProducer}. When used with
|
||||
* a 1.0.2 provider, you need to downcast to the appropriate domain
|
||||
* implementation, either {@link javax.jms.QueueSender} or
|
||||
* {@link javax.jms.TopicPublisher}, to actually send a message.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @since 1.1
|
||||
* @see JmsTemplate#execute(ProducerCallback)
|
||||
* @see JmsTemplate#execute(javax.jms.Destination, ProducerCallback)
|
||||
* @see JmsTemplate#execute(String, ProducerCallback)
|
||||
*/
|
||||
public interface ProducerCallback {
|
||||
|
||||
/**
|
||||
* Perform operations on the given {@link Session} and {@link MessageProducer}.
|
||||
* <p>The message producer is not associated with any destination unless
|
||||
* when specified in the JmsTemplate call.
|
||||
* @param session the JMS <code>Session</code> object to use
|
||||
* @param producer the JMS <code>MessageProducer</code> object to use
|
||||
* @return a result object from working with the <code>Session</code>, if any (can be <code>null</code>)
|
||||
* @throws javax.jms.JMSException if thrown by JMS API methods
|
||||
*/
|
||||
Object doInJms(Session session, MessageProducer producer) throws JMSException;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2002-2005 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.core;
|
||||
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Session;
|
||||
|
||||
/**
|
||||
* Callback for executing any number of operations on a provided
|
||||
* {@link Session}.
|
||||
*
|
||||
* <p>To be used with the {@link JmsTemplate#execute(SessionCallback)}
|
||||
* method, often implemented as an anonymous inner class.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @since 1.1
|
||||
* @see JmsTemplate#execute(SessionCallback)
|
||||
*/
|
||||
public interface SessionCallback {
|
||||
|
||||
/**
|
||||
* Execute any number of operations against the supplied JMS
|
||||
* {@link Session}, possibly returning a result.
|
||||
* @param session the JMS <code>Session</code>
|
||||
* @return a result object from working with the <code>Session</code>, if any (so can be <code>null</code>)
|
||||
* @throws javax.jms.JMSException if thrown by JMS API methods
|
||||
*/
|
||||
Object doInJms(Session session) throws JMSException;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
Core package of the JMS support.
|
||||
Provides a JmsTemplate class and various callback interfaces.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright 2002-2005 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.core.support;
|
||||
|
||||
import javax.jms.ConnectionFactory;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.jms.core.JmsTemplate;
|
||||
|
||||
/**
|
||||
* Convenient super class for application classes that need JMS access.
|
||||
*
|
||||
* <p>Requires a ConnectionFactory or a JmsTemplate instance to be set.
|
||||
* It will create its own JmsTemplate if a ConnectionFactory is passed in.
|
||||
* A custom JmsTemplate instance can be created for a given ConnectionFactory
|
||||
* through overriding the <code>createJmsTemplate</code> method.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
* @since 1.1.1
|
||||
* @see #setConnectionFactory
|
||||
* @see #setJmsTemplate
|
||||
* @see #createJmsTemplate
|
||||
* @see org.springframework.jms.core.JmsTemplate
|
||||
*/
|
||||
public abstract class JmsGatewaySupport implements InitializingBean {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private JmsTemplate jmsTemplate;
|
||||
|
||||
|
||||
/**
|
||||
* Set the JMS connection factory to be used by the gateway.
|
||||
* Will automatically create a JmsTemplate for the given ConnectionFactory.
|
||||
* @see #createJmsTemplate
|
||||
* @see #setConnectionFactory(javax.jms.ConnectionFactory)
|
||||
* @param connectionFactory
|
||||
*/
|
||||
public final void setConnectionFactory(ConnectionFactory connectionFactory) {
|
||||
this.jmsTemplate = createJmsTemplate(connectionFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JmsTemplate for the given ConnectionFactory.
|
||||
* Only invoked if populating the gateway with a ConnectionFactory reference.
|
||||
* <p>Can be overridden in subclasses to provide a JmsTemplate instance with
|
||||
* a different configuration or the JMS 1.0.2 version, JmsTemplate102.
|
||||
* @param connectionFactory the JMS ConnectionFactory to create a JmsTemplate for
|
||||
* @return the new JmsTemplate instance
|
||||
* @see #setConnectionFactory
|
||||
* @see org.springframework.jms.core.JmsTemplate102
|
||||
*/
|
||||
protected JmsTemplate createJmsTemplate(ConnectionFactory connectionFactory) {
|
||||
return new JmsTemplate(connectionFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JMS ConnectionFactory used by the gateway.
|
||||
*/
|
||||
public final ConnectionFactory getConnectionFactory() {
|
||||
return (this.jmsTemplate != null ? this.jmsTemplate.getConnectionFactory() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JmsTemplate for the gateway.
|
||||
* @param jmsTemplate
|
||||
* @see #setConnectionFactory(javax.jms.ConnectionFactory)
|
||||
*/
|
||||
public final void setJmsTemplate(JmsTemplate jmsTemplate) {
|
||||
this.jmsTemplate = jmsTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JmsTemplate for the gateway.
|
||||
*/
|
||||
public final JmsTemplate getJmsTemplate() {
|
||||
return jmsTemplate;
|
||||
}
|
||||
|
||||
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
|
||||
if (this.jmsTemplate == null) {
|
||||
throw new IllegalArgumentException("connectionFactory or jmsTemplate is required");
|
||||
}
|
||||
try {
|
||||
initGateway();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new BeanInitializationException("Initialization of JMS gateway failed: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can override this for custom initialization behavior.
|
||||
* Gets called after population of this instance's bean properties.
|
||||
* @throws java.lang.Exception if initialization fails
|
||||
*/
|
||||
protected void initGateway() throws Exception {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
Classes supporting the org.springframework.jms.core package.
|
||||
Contains a base class for JmsTemplate usage.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,609 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.JMSException;
|
||||
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.jms.JmsException;
|
||||
import org.springframework.jms.connection.ConnectionFactoryUtils;
|
||||
import org.springframework.jms.support.JmsUtils;
|
||||
import org.springframework.jms.support.destination.JmsDestinationAccessor;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Common base class for all containers which need to implement listening
|
||||
* based on a JMS Connection (either shared or freshly obtained for each attempt).
|
||||
* Inherits basic Connection and Session configuration handling from the
|
||||
* {@link org.springframework.jms.support.JmsAccessor} base class.
|
||||
*
|
||||
* <p>This class provides basic lifecycle management, in particular management
|
||||
* of a shared JMS Connection. Subclasses are supposed to plug into this
|
||||
* lifecycle, implementing the {@link #sharedConnectionEnabled()} as well
|
||||
* as the {@link #doInitialize()} and {@link #doShutdown()} template methods.
|
||||
*
|
||||
* <p>This base class does not assume any specific listener programming model
|
||||
* or listener invoker mechanism. It just provides the general runtime
|
||||
* lifecycle management needed for any kind of JMS-based listening mechanism
|
||||
* that operates on a JMS Connection/Session.
|
||||
*
|
||||
* <p>For a concrete listener programming model, check out the
|
||||
* {@link AbstractMessageListenerContainer} subclass. For a concrete listener
|
||||
* invoker mechanism, check out the {@link DefaultMessageListenerContainer} class.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0.3
|
||||
* @see #sharedConnectionEnabled()
|
||||
* @see #doInitialize()
|
||||
* @see #doShutdown()
|
||||
*/
|
||||
public abstract class AbstractJmsListeningContainer extends JmsDestinationAccessor
|
||||
implements Lifecycle, BeanNameAware, DisposableBean {
|
||||
|
||||
private String clientId;
|
||||
|
||||
private boolean autoStartup = true;
|
||||
|
||||
private String beanName;
|
||||
|
||||
private Connection sharedConnection;
|
||||
|
||||
private boolean sharedConnectionStarted = false;
|
||||
|
||||
protected final Object sharedConnectionMonitor = new Object();
|
||||
|
||||
private boolean active = false;
|
||||
|
||||
private boolean running = false;
|
||||
|
||||
private final List pausedTasks = new LinkedList();
|
||||
|
||||
protected final Object lifecycleMonitor = new Object();
|
||||
|
||||
|
||||
/**
|
||||
* Specify the JMS client id for a shared Connection created and used
|
||||
* by this container.
|
||||
* <p>Note that client ids need to be unique among all active Connections
|
||||
* of the underlying JMS provider. Furthermore, a client id can only be
|
||||
* assigned if the original ConnectionFactory hasn't already assigned one.
|
||||
* @see javax.jms.Connection#setClientID
|
||||
* @see #setConnectionFactory
|
||||
*/
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JMS client ID for the shared Connection created and used
|
||||
* by this container, if any.
|
||||
*/
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to automatically start the container after initialization.
|
||||
* <p>Default is "true"; set this to "false" to allow for manual startup
|
||||
* through the {@link #start()} method.
|
||||
*/
|
||||
public void setAutoStartup(boolean autoStartup) {
|
||||
this.autoStartup = autoStartup;
|
||||
}
|
||||
|
||||
public void setBeanName(String beanName) {
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean name that this listener container has been assigned
|
||||
* in its containing bean factory, if any.
|
||||
*/
|
||||
protected final String getBeanName() {
|
||||
return this.beanName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delegates to {@link #validateConfiguration()} and {@link #initialize()}.
|
||||
*/
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
validateConfiguration();
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the configuration of this container.
|
||||
* <p>The default implementation is empty. To be overridden in subclasses.
|
||||
*/
|
||||
protected void validateConfiguration() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #shutdown()} when the BeanFactory destroys the container instance.
|
||||
* @see #shutdown()
|
||||
*/
|
||||
public void destroy() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Lifecycle methods for starting and stopping the container
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Initialize this container.
|
||||
* <p>Creates a JMS Connection, starts the {@link javax.jms.Connection}
|
||||
* (if {@link #setAutoStartup(boolean) "autoStartup"} hasn't been turned off),
|
||||
* and calls {@link #doInitialize()}.
|
||||
* @throws org.springframework.jms.JmsException if startup failed
|
||||
*/
|
||||
public void initialize() throws JmsException {
|
||||
try {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
this.active = true;
|
||||
this.lifecycleMonitor.notifyAll();
|
||||
}
|
||||
if (this.autoStartup) {
|
||||
doStart();
|
||||
}
|
||||
doInitialize();
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
synchronized (this.sharedConnectionMonitor) {
|
||||
ConnectionFactoryUtils.releaseConnection(this.sharedConnection, getConnectionFactory(), this.autoStartup);
|
||||
this.sharedConnection = null;
|
||||
}
|
||||
throw convertJmsAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the shared Connection, call {@link #doShutdown()},
|
||||
* and close this container.
|
||||
* @throws JmsException if shutdown failed
|
||||
*/
|
||||
public void shutdown() throws JmsException {
|
||||
logger.debug("Shutting down JMS listener container");
|
||||
boolean wasRunning = false;
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
wasRunning = this.running;
|
||||
this.running = false;
|
||||
this.active = false;
|
||||
this.lifecycleMonitor.notifyAll();
|
||||
}
|
||||
|
||||
// Stop shared Connection early, if necessary.
|
||||
if (wasRunning && sharedConnectionEnabled()) {
|
||||
try {
|
||||
stopSharedConnection();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.debug("Could not stop JMS Connection on shutdown", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Shut down the invokers.
|
||||
try {
|
||||
doShutdown();
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
throw convertJmsAccessException(ex);
|
||||
}
|
||||
finally {
|
||||
if (sharedConnectionEnabled()) {
|
||||
synchronized (this.sharedConnectionMonitor) {
|
||||
ConnectionFactoryUtils.releaseConnection(this.sharedConnection, getConnectionFactory(), false);
|
||||
this.sharedConnection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this container is currently active,
|
||||
* that is, whether it has been set up but not shut down yet.
|
||||
*/
|
||||
public final boolean isActive() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
return this.active;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start this container.
|
||||
* @throws JmsException if starting failed
|
||||
* @see #doStart
|
||||
*/
|
||||
public void start() throws JmsException {
|
||||
try {
|
||||
doStart();
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
throw convertJmsAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the shared Connection, if any, and notify all invoker tasks.
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #startSharedConnection
|
||||
*/
|
||||
protected void doStart() throws JMSException {
|
||||
// Lazily establish a shared Connection, if necessary.
|
||||
if (sharedConnectionEnabled()) {
|
||||
establishSharedConnection();
|
||||
}
|
||||
|
||||
// Reschedule paused tasks, if any.
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
this.running = true;
|
||||
this.lifecycleMonitor.notifyAll();
|
||||
resumePausedTasks();
|
||||
}
|
||||
|
||||
// Start the shared Connection, if any.
|
||||
if (sharedConnectionEnabled()) {
|
||||
startSharedConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop this container.
|
||||
* @throws JmsException if stopping failed
|
||||
* @see #doStop
|
||||
*/
|
||||
public void stop() throws JmsException {
|
||||
try {
|
||||
doStop();
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
throw convertJmsAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all invoker tasks and stop the shared Connection, if any.
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #stopSharedConnection
|
||||
*/
|
||||
protected void doStop() throws JMSException {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
this.running = false;
|
||||
this.lifecycleMonitor.notifyAll();
|
||||
}
|
||||
|
||||
if (sharedConnectionEnabled()) {
|
||||
stopSharedConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this container is currently running,
|
||||
* that is, whether it has been started and not stopped yet.
|
||||
* @see #start()
|
||||
* @see #stop()
|
||||
* @see #runningAllowed()
|
||||
*/
|
||||
public final boolean isRunning() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
return (this.running && runningAllowed());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this container's listeners are generally allowed to run.
|
||||
* <p>This implementation always returns <code>true</code>; the default 'running'
|
||||
* state is purely determined by {@link #start()} / {@link #stop()}.
|
||||
* <p>Subclasses may override this method to check against temporary
|
||||
* conditions that prevent listeners from actually running. In other words,
|
||||
* they may apply further restrictions to the 'running' state, returning
|
||||
* <code>false</code> if such a restriction prevents listeners from running.
|
||||
*/
|
||||
protected boolean runningAllowed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Management of a shared JMS Connection
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Establish a shared Connection for this container.
|
||||
* <p>The default implementation delegates to {@link #createSharedConnection()},
|
||||
* which does one immediate attempt and throws an exception if it fails.
|
||||
* Can be overridden to have a recovery process in place, retrying
|
||||
* until a Connection can be successfully established.
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
*/
|
||||
protected void establishSharedConnection() throws JMSException {
|
||||
synchronized (this.sharedConnectionMonitor) {
|
||||
if (this.sharedConnection == null) {
|
||||
this.sharedConnection = createSharedConnection();
|
||||
logger.debug("Established shared JMS Connection");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the shared Connection that this container holds.
|
||||
* <p>Called on startup and also after an infrastructure exception
|
||||
* that occurred during invoker setup and/or execution.
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
*/
|
||||
protected final void refreshSharedConnection() throws JMSException {
|
||||
synchronized (this.sharedConnectionMonitor) {
|
||||
ConnectionFactoryUtils.releaseConnection(
|
||||
this.sharedConnection, getConnectionFactory(), this.sharedConnectionStarted);
|
||||
this.sharedConnection = null;
|
||||
this.sharedConnection = createSharedConnection();
|
||||
if (this.sharedConnectionStarted) {
|
||||
this.sharedConnection.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a shared Connection for this container.
|
||||
* <p>The default implementation creates a standard Connection
|
||||
* and prepares it through {@link #prepareSharedConnection}.
|
||||
* @return the prepared Connection
|
||||
* @throws JMSException if the creation failed
|
||||
*/
|
||||
protected Connection createSharedConnection() throws JMSException {
|
||||
Connection con = createConnection();
|
||||
try {
|
||||
prepareSharedConnection(con);
|
||||
return con;
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
JmsUtils.closeConnection(con);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given Connection, which is about to be registered
|
||||
* as shared Connection for this container.
|
||||
* <p>The default implementation sets the specified client id, if any.
|
||||
* Subclasses can override this to apply further settings.
|
||||
* @param connection the Connection to prepare
|
||||
* @throws JMSException if the preparation efforts failed
|
||||
* @see #getClientId()
|
||||
*/
|
||||
protected void prepareSharedConnection(Connection connection) throws JMSException {
|
||||
String clientId = getClientId();
|
||||
if (clientId != null) {
|
||||
connection.setClientID(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the shared Connection.
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see javax.jms.Connection#start()
|
||||
*/
|
||||
protected void startSharedConnection() throws JMSException {
|
||||
synchronized (this.sharedConnectionMonitor) {
|
||||
this.sharedConnectionStarted = true;
|
||||
if (this.sharedConnection != null) {
|
||||
try {
|
||||
this.sharedConnection.start();
|
||||
}
|
||||
catch (javax.jms.IllegalStateException ex) {
|
||||
logger.debug("Ignoring Connection start exception - assuming already started: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the shared Connection.
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see javax.jms.Connection#start()
|
||||
*/
|
||||
protected void stopSharedConnection() throws JMSException {
|
||||
synchronized (this.sharedConnectionMonitor) {
|
||||
this.sharedConnectionStarted = false;
|
||||
if (this.sharedConnection != null) {
|
||||
try {
|
||||
this.sharedConnection.stop();
|
||||
}
|
||||
catch (javax.jms.IllegalStateException ex) {
|
||||
logger.debug("Ignoring Connection stop exception - assuming already stopped: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the shared JMS Connection maintained by this container.
|
||||
* Available after initialization.
|
||||
* @return the shared Connection (never <code>null</code>)
|
||||
* @throws IllegalStateException if this container does not maintain a
|
||||
* shared Connection, or if the Connection hasn't been initialized yet
|
||||
* @see #sharedConnectionEnabled()
|
||||
*/
|
||||
protected final Connection getSharedConnection() {
|
||||
if (!sharedConnectionEnabled()) {
|
||||
throw new IllegalStateException(
|
||||
"This listener container does not maintain a shared Connection");
|
||||
}
|
||||
synchronized (this.sharedConnectionMonitor) {
|
||||
if (this.sharedConnection == null) {
|
||||
throw new SharedConnectionNotInitializedException(
|
||||
"This listener container's shared Connection has not been initialized yet");
|
||||
}
|
||||
return this.sharedConnection;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Management of paused tasks
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Take the given task object and reschedule it, either immediately if
|
||||
* this container is currently running, or later once this container
|
||||
* has been restarted.
|
||||
* <p>If this container has already been shut down, the task will not
|
||||
* get rescheduled at all.
|
||||
* @param task the task object to reschedule
|
||||
* @return whether the task has been rescheduled
|
||||
* (either immediately or for a restart of this container)
|
||||
* @see #doRescheduleTask
|
||||
*/
|
||||
protected final boolean rescheduleTaskIfNecessary(Object task) {
|
||||
if (this.running) {
|
||||
try {
|
||||
doRescheduleTask(task);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
logRejectedTask(task, ex);
|
||||
this.pausedTasks.add(task);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (this.active) {
|
||||
this.pausedTasks.add(task);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to resume all paused tasks.
|
||||
* Tasks for which rescheduling failed simply remain in paused mode.
|
||||
*/
|
||||
protected void resumePausedTasks() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
if (!this.pausedTasks.isEmpty()) {
|
||||
for (Iterator it = this.pausedTasks.iterator(); it.hasNext();) {
|
||||
Object task = it.next();
|
||||
try {
|
||||
doRescheduleTask(task);
|
||||
it.remove();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Resumed paused task: " + task);
|
||||
}
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
logRejectedTask(task, ex);
|
||||
// Keep the task in paused mode...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getPausedTaskCount() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
return this.pausedTasks.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reschedule the given task object immediately.
|
||||
* <p>To be implemented by subclasses if they ever call
|
||||
* <code>rescheduleTaskIfNecessary</code>.
|
||||
* This implementation throws an UnsupportedOperationException.
|
||||
* @param task the task object to reschedule
|
||||
* @see #rescheduleTaskIfNecessary
|
||||
*/
|
||||
protected void doRescheduleTask(Object task) {
|
||||
throw new UnsupportedOperationException(
|
||||
ClassUtils.getShortName(getClass()) + " does not support rescheduling of tasks");
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a task that has been rejected by {@link #doRescheduleTask}.
|
||||
* <p>The default implementation simply logs a corresponding message
|
||||
* at debug level.
|
||||
* @param task the rejected task object
|
||||
* @param ex the exception thrown from {@link #doRescheduleTask}
|
||||
*/
|
||||
protected void logRejectedTask(Object task, RuntimeException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Listener container task [" + task + "] has been rejected and paused: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Template methods to be implemented by subclasses
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Return whether a shared JMS Connection should be maintained
|
||||
* by this container base class.
|
||||
* @see #getSharedConnection()
|
||||
*/
|
||||
protected abstract boolean sharedConnectionEnabled();
|
||||
|
||||
/**
|
||||
* Register any invokers within this container.
|
||||
* <p>Subclasses need to implement this method for their specific
|
||||
* invoker management process.
|
||||
* <p>A shared JMS Connection, if any, will already have been
|
||||
* started at this point.
|
||||
* @throws JMSException if registration failed
|
||||
* @see #getSharedConnection()
|
||||
*/
|
||||
protected abstract void doInitialize() throws JMSException;
|
||||
|
||||
/**
|
||||
* Close the registered invokers.
|
||||
* <p>Subclasses need to implement this method for their specific
|
||||
* invoker management process.
|
||||
* <p>A shared JMS Connection, if any, will automatically be closed
|
||||
* <i>afterwards</i>.
|
||||
* @throws JMSException if shutdown failed
|
||||
* @see #shutdown()
|
||||
*/
|
||||
protected abstract void doShutdown() throws JMSException;
|
||||
|
||||
|
||||
/**
|
||||
* Exception that indicates that the initial setup of this container's
|
||||
* shared JMS Connection failed. This is indicating to invokers that they need
|
||||
* to establish the shared Connection themselves on first access.
|
||||
*/
|
||||
public static class SharedConnectionNotInitializedException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Create a new SharedConnectionNotInitializedException.
|
||||
* @param msg the detail message
|
||||
*/
|
||||
protected SharedConnectionNotInitializedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,676 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.ExceptionListener;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageListener;
|
||||
import javax.jms.Queue;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.Topic;
|
||||
|
||||
import org.springframework.jms.support.JmsUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base class for message listener containers. Can either host
|
||||
* a standard JMS {@link javax.jms.MessageListener} or a Spring-specific
|
||||
* {@link SessionAwareMessageListener}.
|
||||
*
|
||||
* <p>Usually holds a single JMS {@link Connection} that all listeners are
|
||||
* supposed to be registered on, which is the standard JMS way of managing
|
||||
* listeners. Can alternatively also be used with a fresh Connection per
|
||||
* listener, for J2EE-style XA-aware JMS messaging. The actual registration
|
||||
* process is up to concrete subclasses.
|
||||
*
|
||||
* <p><b>NOTE:</b> The default behavior of this message listener container
|
||||
* is to <b>never</b> propagate an exception thrown by a message listener up to
|
||||
* the JMS provider. Instead, it will log any such exception at the error level.
|
||||
* This means that from the perspective of the attendant JMS provider no such
|
||||
* listener will ever fail.
|
||||
*
|
||||
* <p>The listener container offers the following message acknowledgment options:
|
||||
* <ul>
|
||||
* <li>"sessionAcknowledgeMode" set to "AUTO_ACKNOWLEDGE" (default):
|
||||
* Automatic message acknowledgment <i>before</i> listener execution;
|
||||
* no redelivery in case of exception thrown.
|
||||
* <li>"sessionAcknowledgeMode" set to "CLIENT_ACKNOWLEDGE":
|
||||
* Automatic message acknowledgment <i>after</i> successful listener execution;
|
||||
* no redelivery in case of exception thrown.
|
||||
* <li>"sessionAcknowledgeMode" set to "DUPS_OK_ACKNOWLEDGE":
|
||||
* <i>Lazy</i> message acknowledgment during or after listener execution;
|
||||
* <i>potential redelivery</i> in case of exception thrown.
|
||||
* <li>"sessionTransacted" set to "true":
|
||||
* Transactional acknowledgment after successful listener execution;
|
||||
* <i>guaranteed redelivery</i> in case of exception thrown.
|
||||
* </ul>
|
||||
* The exact behavior might vary according to the concrete listener container
|
||||
* and JMS provider used.
|
||||
*
|
||||
* <p>There are two solutions to the duplicate processing problem:
|
||||
* <ul>
|
||||
* <li>Either add <i>duplicate message detection</i> to your listener, in the
|
||||
* form of a business entity existence check or a protocol table check. This
|
||||
* usually just needs to be done in case of the JMSRedelivered flag being
|
||||
* set on the incoming message (else just process straightforwardly).
|
||||
* <li>Or wrap the <i>entire processing with an XA transaction</i>, covering the
|
||||
* reception of the message as well as the execution of the message listener.
|
||||
* This is only supported by {@link DefaultMessageListenerContainer}, through
|
||||
* specifying a "transactionManager" (typically a
|
||||
* {@link org.springframework.transaction.jta.JtaTransactionManager}, with
|
||||
* a corresponding XA-aware JMS {@link javax.jms.ConnectionFactory} passed in as
|
||||
* "connectionFactory").
|
||||
* </ul>
|
||||
* Note that XA transaction coordination adds significant runtime overhead,
|
||||
* so it might be feasible to avoid it unless absolutely necessary.
|
||||
*
|
||||
* <p><b>Recommendations:</b>
|
||||
* <ul>
|
||||
* <li>The general recommendation is to set "sessionTransacted" to "true",
|
||||
* typically in combination with local database transactions triggered by the
|
||||
* listener implementation, through Spring's standard transaction facilities.
|
||||
* This will work nicely in Tomcat or in a standalone environment, often
|
||||
* combined with custom duplicate message detection (if it is unacceptable
|
||||
* to ever process the same message twice).
|
||||
* <li>Alternatively, specify a
|
||||
* {@link org.springframework.transaction.jta.JtaTransactionManager} as
|
||||
* "transactionManager" for a fully XA-aware JMS provider - typically when
|
||||
* running on a J2EE server, but also for other environments with a JTA
|
||||
* transaction manager present. This will give full "exactly-once" guarantees
|
||||
* without custom duplicate message checks, at the price of additional
|
||||
* runtime processing overhead.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Note that it is also possible to specify a
|
||||
* {@link org.springframework.jms.connection.JmsTransactionManager} as external
|
||||
* "transactionManager", providing fully synchronized Spring transactions based
|
||||
* on local JMS transactions. The effect is similar to "sessionTransacted" set
|
||||
* to "true", the difference being that this external transaction management
|
||||
* will also affect independent JMS access code within the service layer
|
||||
* (e.g. based on {@link org.springframework.jms.core.JmsTemplate} or
|
||||
* {@link org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy}),
|
||||
* not just direct JMS Session usage in a {@link SessionAwareMessageListener}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @see #setMessageListener
|
||||
* @see javax.jms.MessageListener
|
||||
* @see SessionAwareMessageListener
|
||||
* @see #handleListenerException
|
||||
* @see DefaultMessageListenerContainer
|
||||
* @see SimpleMessageListenerContainer
|
||||
* @see org.springframework.jms.listener.endpoint.JmsMessageEndpointManager
|
||||
*/
|
||||
public abstract class AbstractMessageListenerContainer extends AbstractJmsListeningContainer {
|
||||
|
||||
private volatile Object destination;
|
||||
|
||||
private volatile String messageSelector;
|
||||
|
||||
private volatile Object messageListener;
|
||||
|
||||
private boolean subscriptionDurable = false;
|
||||
|
||||
private String durableSubscriptionName;
|
||||
|
||||
private ExceptionListener exceptionListener;
|
||||
|
||||
private boolean exposeListenerSession = true;
|
||||
|
||||
private boolean acceptMessagesWhileStopping = false;
|
||||
|
||||
|
||||
/**
|
||||
* Set the destination to receive messages from.
|
||||
* <p>Alternatively, specify a "destinationName", to be dynamically
|
||||
* resolved via the {@link org.springframework.jms.support.destination.DestinationResolver}.
|
||||
* <p>Note: The destination may be replaced at runtime, with the listener
|
||||
* container picking up the new destination immediately (works e.g. with
|
||||
* DefaultMessageListenerContainer, as long as the cache level is less than
|
||||
* CACHE_CONSUMER). However, this is considered advanced usage; use it with care!
|
||||
* @see #setDestinationName(String)
|
||||
*/
|
||||
public void setDestination(Destination destination) {
|
||||
Assert.notNull(destination, "'destination' must not be null");
|
||||
this.destination = destination;
|
||||
if (destination instanceof Topic && !(destination instanceof Queue)) {
|
||||
// Clearly a Topic: let's set the "pubSubDomain" flag accordingly.
|
||||
setPubSubDomain(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the destination to receive messages from. Will be <code>null</code>
|
||||
* if the configured destination is not an actual {@link Destination} type;
|
||||
* c.f. {@link #setDestinationName(String) when the destination is a String}.
|
||||
*/
|
||||
public Destination getDestination() {
|
||||
return (this.destination instanceof Destination ? (Destination) this.destination : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the destination to receive messages from.
|
||||
* <p>The specified name will be dynamically resolved via the configured
|
||||
* {@link #setDestinationResolver destination resolver}.
|
||||
* <p>Alternatively, specify a JMS {@link Destination} object as "destination".
|
||||
* <p>Note: The destination may be replaced at runtime, with the listener
|
||||
* container picking up the new destination immediately (works e.g. with
|
||||
* DefaultMessageListenerContainer, as long as the cache level is less than
|
||||
* CACHE_CONSUMER). However, this is considered advanced usage; use it with care!
|
||||
* @param destinationName the desired destination (can be <code>null</code>)
|
||||
* @see #setDestination(javax.jms.Destination)
|
||||
*/
|
||||
public void setDestinationName(String destinationName) {
|
||||
Assert.notNull(destinationName, "'destinationName' must not be null");
|
||||
this.destination = destinationName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the destination to receive messages from.
|
||||
* Will be <code>null</code> if the configured destination is not a
|
||||
* {@link String} type; c.f. {@link #setDestination(Destination) when
|
||||
* it is an actual Destination}.
|
||||
*/
|
||||
public String getDestinationName() {
|
||||
return (this.destination instanceof String ? (String) this.destination : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a descriptive String for this container's JMS destination
|
||||
* (never <code>null</code>).
|
||||
*/
|
||||
protected String getDestinationDescription() {
|
||||
return this.destination.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JMS message selector expression (or <code>null</code> if none).
|
||||
* Default is none.
|
||||
* <p>See the JMS specification for a detailed definition of selector expressions.
|
||||
* <p>Note: The message selector may be replaced at runtime, with the listener
|
||||
* container picking up the new selector value immediately (works e.g. with
|
||||
* DefaultMessageListenerContainer, as long as the cache level is less than
|
||||
* CACHE_CONSUMER). However, this is considered advanced usage; use it with care!
|
||||
*/
|
||||
public void setMessageSelector(String messageSelector) {
|
||||
this.messageSelector = messageSelector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JMS message selector expression (or <code>null</code> if none).
|
||||
*/
|
||||
public String getMessageSelector() {
|
||||
return this.messageSelector;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the message listener implementation to register.
|
||||
* This can be either a standard JMS {@link MessageListener} object
|
||||
* or a Spring {@link SessionAwareMessageListener} object.
|
||||
* <p>Note: The message listener may be replaced at runtime, with the listener
|
||||
* container picking up the new listener object immediately (works e.g. with
|
||||
* DefaultMessageListenerContainer, as long as the cache level is less than
|
||||
* CACHE_CONSUMER). However, this is considered advanced usage; use it with care!
|
||||
* @throws IllegalArgumentException if the supplied listener is not a
|
||||
* {@link MessageListener} or a {@link SessionAwareMessageListener}
|
||||
* @see javax.jms.MessageListener
|
||||
* @see SessionAwareMessageListener
|
||||
*/
|
||||
public void setMessageListener(Object messageListener) {
|
||||
checkMessageListener(messageListener);
|
||||
this.messageListener = messageListener;
|
||||
if (this.durableSubscriptionName == null) {
|
||||
this.durableSubscriptionName = getDefaultSubscriptionName(messageListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the message listener object to register.
|
||||
*/
|
||||
public Object getMessageListener() {
|
||||
return this.messageListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given message listener, throwing an exception
|
||||
* if it does not correspond to a supported listener type.
|
||||
* <p>By default, only a standard JMS {@link MessageListener} object or a
|
||||
* Spring {@link SessionAwareMessageListener} object will be accepted.
|
||||
* @param messageListener the message listener object to check
|
||||
* @throws IllegalArgumentException if the supplied listener is not a
|
||||
* {@link MessageListener} or a {@link SessionAwareMessageListener}
|
||||
* @see javax.jms.MessageListener
|
||||
* @see SessionAwareMessageListener
|
||||
*/
|
||||
protected void checkMessageListener(Object messageListener) {
|
||||
if (!(messageListener instanceof MessageListener ||
|
||||
messageListener instanceof SessionAwareMessageListener)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Message listener needs to be of type [" + MessageListener.class.getName() +
|
||||
"] or [" + SessionAwareMessageListener.class.getName() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the default subscription name for the given message listener.
|
||||
* @param messageListener the message listener object to check
|
||||
* @return the default subscription name
|
||||
* @see SubscriptionNameProvider
|
||||
*/
|
||||
protected String getDefaultSubscriptionName(Object messageListener) {
|
||||
if (messageListener instanceof SubscriptionNameProvider) {
|
||||
return ((SubscriptionNameProvider) messageListener).getSubscriptionName();
|
||||
}
|
||||
else {
|
||||
return messageListener.getClass().getName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to make the subscription durable. The durable subscription name
|
||||
* to be used can be specified through the "durableSubscriptionName" property.
|
||||
* <p>Default is "false". Set this to "true" to register a durable subscription,
|
||||
* typically in combination with a "durableSubscriptionName" value (unless
|
||||
* your message listener class name is good enough as subscription name).
|
||||
* <p>Only makes sense when listening to a topic (pub-sub domain).
|
||||
* @see #setDurableSubscriptionName
|
||||
*/
|
||||
public void setSubscriptionDurable(boolean subscriptionDurable) {
|
||||
this.subscriptionDurable = subscriptionDurable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to make the subscription durable.
|
||||
*/
|
||||
public boolean isSubscriptionDurable() {
|
||||
return this.subscriptionDurable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of a durable subscription to create. To be applied in case
|
||||
* of a topic (pub-sub domain) with subscription durability activated.
|
||||
* <p>The durable subscription name needs to be unique within this client's
|
||||
* JMS client id. Default is the class name of the specified message listener.
|
||||
* <p>Note: Only 1 concurrent consumer (which is the default of this
|
||||
* message listener container) is allowed for each durable subscription.
|
||||
* @see #setSubscriptionDurable
|
||||
* @see #setClientId
|
||||
* @see #setMessageListener
|
||||
*/
|
||||
public void setDurableSubscriptionName(String durableSubscriptionName) {
|
||||
this.durableSubscriptionName = durableSubscriptionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of a durable subscription to create, if any.
|
||||
*/
|
||||
public String getDurableSubscriptionName() {
|
||||
return this.durableSubscriptionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JMS ExceptionListener to notify in case of a JMSException thrown
|
||||
* by the registered message listener or the invocation infrastructure.
|
||||
*/
|
||||
public void setExceptionListener(ExceptionListener exceptionListener) {
|
||||
this.exceptionListener = exceptionListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JMS ExceptionListener to notify in case of a JMSException thrown
|
||||
* by the registered message listener or the invocation infrastructure, if any.
|
||||
*/
|
||||
public ExceptionListener getExceptionListener() {
|
||||
return this.exceptionListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to expose the listener JMS Session to a registered
|
||||
* {@link SessionAwareMessageListener} as well as to
|
||||
* {@link org.springframework.jms.core.JmsTemplate} calls.
|
||||
* <p>Default is "true", reusing the listener's {@link Session}.
|
||||
* Turn this off to expose a fresh JMS Session fetched from the same
|
||||
* underlying JMS {@link Connection} instead, which might be necessary
|
||||
* on some JMS providers.
|
||||
* <p>Note that Sessions managed by an external transaction manager will
|
||||
* always get exposed to {@link org.springframework.jms.core.JmsTemplate}
|
||||
* calls. So in terms of JmsTemplate exposure, this setting only affects
|
||||
* locally transacted Sessions.
|
||||
* @see SessionAwareMessageListener
|
||||
*/
|
||||
public void setExposeListenerSession(boolean exposeListenerSession) {
|
||||
this.exposeListenerSession = exposeListenerSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to expose the listener JMS {@link Session} to a
|
||||
* registered {@link SessionAwareMessageListener}.
|
||||
*/
|
||||
public boolean isExposeListenerSession() {
|
||||
return this.exposeListenerSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to accept received messages while the listener container
|
||||
* in the process of stopping.
|
||||
* <p>Default is "false", rejecting such messages through aborting the
|
||||
* receive attempt. Switch this flag on to fully process such messages
|
||||
* even in the stopping phase, with the drawback that even newly sent
|
||||
* messages might still get processed (if coming in before all receive
|
||||
* timeouts have expired).
|
||||
* <p><b>NOTE:</b> Aborting receive attempts for such incoming messages
|
||||
* might lead to the provider's retry count decreasing for the affected
|
||||
* messages. If you have a high number of concurrent consumers, make sure
|
||||
* that the number of retries is higher than the number of consumers,
|
||||
* to be on the safe side for all potential stopping scenarios.
|
||||
*/
|
||||
public void setAcceptMessagesWhileStopping(boolean acceptMessagesWhileStopping) {
|
||||
this.acceptMessagesWhileStopping = acceptMessagesWhileStopping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to accept received messages while the listener container
|
||||
* in the process of stopping.
|
||||
*/
|
||||
public boolean isAcceptMessagesWhileStopping() {
|
||||
return this.acceptMessagesWhileStopping;
|
||||
}
|
||||
|
||||
protected void validateConfiguration() {
|
||||
if (this.destination == null) {
|
||||
throw new IllegalArgumentException("Property 'destination' or 'destinationName' is required");
|
||||
}
|
||||
if (isSubscriptionDurable() && !isPubSubDomain()) {
|
||||
throw new IllegalArgumentException("A durable subscription requires a topic (pub-sub domain)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Template methods for listener execution
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute the specified listener,
|
||||
* committing or rolling back the transaction afterwards (if necessary).
|
||||
* @param session the JMS Session to operate on
|
||||
* @param message the received JMS Message
|
||||
* @see #invokeListener
|
||||
* @see #commitIfNecessary
|
||||
* @see #rollbackOnExceptionIfNecessary
|
||||
* @see #handleListenerException
|
||||
*/
|
||||
protected void executeListener(Session session, Message message) {
|
||||
try {
|
||||
doExecuteListener(session, message);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
handleListenerException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the specified listener,
|
||||
* committing or rolling back the transaction afterwards (if necessary).
|
||||
* @param session the JMS Session to operate on
|
||||
* @param message the received JMS Message
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #invokeListener
|
||||
* @see #commitIfNecessary
|
||||
* @see #rollbackOnExceptionIfNecessary
|
||||
* @see #convertJmsAccessException
|
||||
*/
|
||||
protected void doExecuteListener(Session session, Message message) throws JMSException {
|
||||
if (!isAcceptMessagesWhileStopping() && !isRunning()) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Rejecting received message because of the listener container " +
|
||||
"having been stopped in the meantime: " + message);
|
||||
}
|
||||
rollbackIfNecessary(session);
|
||||
throw new MessageRejectedWhileStoppingException();
|
||||
}
|
||||
try {
|
||||
invokeListener(session, message);
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
rollbackOnExceptionIfNecessary(session, ex);
|
||||
throw ex;
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
rollbackOnExceptionIfNecessary(session, ex);
|
||||
throw ex;
|
||||
}
|
||||
catch (Error err) {
|
||||
rollbackOnExceptionIfNecessary(session, err);
|
||||
throw err;
|
||||
}
|
||||
commitIfNecessary(session, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the specified listener: either as standard JMS MessageListener
|
||||
* or (preferably) as Spring SessionAwareMessageListener.
|
||||
* @param session the JMS Session to operate on
|
||||
* @param message the received JMS Message
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #setMessageListener
|
||||
*/
|
||||
protected void invokeListener(Session session, Message message) throws JMSException {
|
||||
Object listener = getMessageListener();
|
||||
if (listener instanceof SessionAwareMessageListener) {
|
||||
doInvokeListener((SessionAwareMessageListener) listener, session, message);
|
||||
}
|
||||
else if (listener instanceof MessageListener) {
|
||||
doInvokeListener((MessageListener) listener, message);
|
||||
}
|
||||
else if (listener != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Only MessageListener and SessionAwareMessageListener supported: " + listener);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("No message listener specified - see property 'messageListener'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the specified listener as Spring SessionAwareMessageListener,
|
||||
* exposing a new JMS Session (potentially with its own transaction)
|
||||
* to the listener if demanded.
|
||||
* @param listener the Spring SessionAwareMessageListener to invoke
|
||||
* @param session the JMS Session to operate on
|
||||
* @param message the received JMS Message
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see SessionAwareMessageListener
|
||||
* @see #setExposeListenerSession
|
||||
*/
|
||||
protected void doInvokeListener(SessionAwareMessageListener listener, Session session, Message message)
|
||||
throws JMSException {
|
||||
|
||||
Connection conToClose = null;
|
||||
Session sessionToClose = null;
|
||||
try {
|
||||
Session sessionToUse = session;
|
||||
if (!isExposeListenerSession()) {
|
||||
// We need to expose a separate Session.
|
||||
conToClose = createConnection();
|
||||
sessionToClose = createSession(conToClose);
|
||||
sessionToUse = sessionToClose;
|
||||
}
|
||||
// Actually invoke the message listener...
|
||||
listener.onMessage(message, sessionToUse);
|
||||
// Clean up specially exposed Session, if any.
|
||||
if (sessionToUse != session) {
|
||||
if (sessionToUse.getTransacted() && isSessionLocallyTransacted(sessionToUse)) {
|
||||
// Transacted session created by this container -> commit.
|
||||
JmsUtils.commitIfNecessary(sessionToUse);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
JmsUtils.closeSession(sessionToClose);
|
||||
JmsUtils.closeConnection(conToClose);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the specified listener as standard JMS MessageListener.
|
||||
* <p>Default implementation performs a plain invocation of the
|
||||
* <code>onMessage</code> method.
|
||||
* @param listener the JMS MessageListener to invoke
|
||||
* @param message the received JMS Message
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see javax.jms.MessageListener#onMessage
|
||||
*/
|
||||
protected void doInvokeListener(MessageListener listener, Message message) throws JMSException {
|
||||
listener.onMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a commit or message acknowledgement, as appropriate.
|
||||
* @param session the JMS Session to commit
|
||||
* @param message the Message to acknowledge
|
||||
* @throws javax.jms.JMSException in case of commit failure
|
||||
*/
|
||||
protected void commitIfNecessary(Session session, Message message) throws JMSException {
|
||||
// Commit session or acknowledge message.
|
||||
if (session.getTransacted()) {
|
||||
// Commit necessary - but avoid commit call within a JTA transaction.
|
||||
if (isSessionLocallyTransacted(session)) {
|
||||
// Transacted session created by this container -> commit.
|
||||
JmsUtils.commitIfNecessary(session);
|
||||
}
|
||||
}
|
||||
else if (isClientAcknowledge(session)) {
|
||||
message.acknowledge();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a rollback, if appropriate.
|
||||
* @param session the JMS Session to rollback
|
||||
* @throws javax.jms.JMSException in case of a rollback error
|
||||
*/
|
||||
protected void rollbackIfNecessary(Session session) throws JMSException {
|
||||
if (session.getTransacted() && isSessionLocallyTransacted(session)) {
|
||||
// Transacted session created by this container -> rollback.
|
||||
JmsUtils.rollbackIfNecessary(session);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a rollback, handling rollback exceptions properly.
|
||||
* @param session the JMS Session to rollback
|
||||
* @param ex the thrown application exception or error
|
||||
* @throws javax.jms.JMSException in case of a rollback error
|
||||
*/
|
||||
protected void rollbackOnExceptionIfNecessary(Session session, Throwable ex) throws JMSException {
|
||||
try {
|
||||
if (session.getTransacted() && isSessionLocallyTransacted(session)) {
|
||||
// Transacted session created by this container -> rollback.
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Initiating transaction rollback on application exception", ex);
|
||||
}
|
||||
JmsUtils.rollbackIfNecessary(session);
|
||||
}
|
||||
}
|
||||
catch (IllegalStateException ex2) {
|
||||
logger.debug("Could not roll back because Session already closed", ex2);
|
||||
}
|
||||
catch (JMSException ex2) {
|
||||
logger.error("Application exception overridden by rollback exception", ex);
|
||||
throw ex2;
|
||||
}
|
||||
catch (RuntimeException ex2) {
|
||||
logger.error("Application exception overridden by rollback exception", ex);
|
||||
throw ex2;
|
||||
}
|
||||
catch (Error err) {
|
||||
logger.error("Application exception overridden by rollback error", ex);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given Session is locally transacted, that is, whether
|
||||
* its transaction is managed by this listener container's Session handling
|
||||
* and not by an external transaction coordinator.
|
||||
* <p>Note: The Session's own transacted flag will already have been checked
|
||||
* before. This method is about finding out whether the Session's transaction
|
||||
* is local or externally coordinated.
|
||||
* @param session the Session to check
|
||||
* @return whether the given Session is locally transacted
|
||||
* @see #isSessionTransacted()
|
||||
* @see org.springframework.jms.connection.ConnectionFactoryUtils#isSessionTransactional
|
||||
*/
|
||||
protected boolean isSessionLocallyTransacted(Session session) {
|
||||
return isSessionTransacted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the given exception that arose during listener execution.
|
||||
* <p>The default implementation logs the exception at error level,
|
||||
* not propagating it to the JMS provider - assuming that all handling of
|
||||
* acknowledgement and/or transactions is done by this listener container.
|
||||
* This can be overridden in subclasses.
|
||||
* @param ex the exception to handle
|
||||
*/
|
||||
protected void handleListenerException(Throwable ex) {
|
||||
if (ex instanceof MessageRejectedWhileStoppingException) {
|
||||
// Internal exception - has been handled before.
|
||||
return;
|
||||
}
|
||||
if (ex instanceof JMSException) {
|
||||
invokeExceptionListener((JMSException) ex);
|
||||
}
|
||||
if (isActive()) {
|
||||
// Regular case: failed while active.
|
||||
// Log at error level.
|
||||
logger.warn("Execution of JMS message listener failed", ex);
|
||||
}
|
||||
else {
|
||||
// Rare case: listener thread failed after container shutdown.
|
||||
// Log at debug level, to avoid spamming the shutdown log.
|
||||
logger.debug("Listener exception after container shutdown", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the registered JMS ExceptionListener, if any.
|
||||
* @param ex the exception that arose during JMS processing
|
||||
* @see #setExceptionListener
|
||||
*/
|
||||
protected void invokeExceptionListener(JMSException ex) {
|
||||
ExceptionListener exceptionListener = getExceptionListener();
|
||||
if (exceptionListener != null) {
|
||||
exceptionListener.onException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal exception class that indicates a rejected message on shutdown.
|
||||
* Used to trigger a rollback for an external transaction manager in that case.
|
||||
*/
|
||||
private static class MessageRejectedWhileStoppingException extends RuntimeException {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,514 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageConsumer;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.Topic;
|
||||
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.jms.connection.ConnectionFactoryUtils;
|
||||
import org.springframework.jms.connection.JmsResourceHolder;
|
||||
import org.springframework.jms.connection.SingleConnectionFactory;
|
||||
import org.springframework.jms.support.JmsUtils;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.ResourceTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationUtils;
|
||||
|
||||
/**
|
||||
* Base class for listener container implementations which are based on polling.
|
||||
* Provides support for listener handling based on {@link javax.jms.MessageConsumer},
|
||||
* optionally participating in externally managed transactions.
|
||||
*
|
||||
* <p>This listener container variant is built for repeated polling attempts,
|
||||
* each invoking the {@link #receiveAndExecute} method. The MessageConsumer used
|
||||
* may be reobtained fo reach attempt or cached inbetween attempts; this is up
|
||||
* to the concrete implementation. The receive timeout for each attempt can be
|
||||
* configured through the {@link #setReceiveTimeout "receiveTimeout"} property.
|
||||
*
|
||||
* <p>The underlying mechanism is based on standard JMS MessageConsumer handling,
|
||||
* which is perfectly compatible with both native JMS and JMS in a J2EE environment.
|
||||
* Neither the JMS <code>MessageConsumer.setMessageListener</code> facility
|
||||
* nor the JMS ServerSessionPool facility is required. A further advantage
|
||||
* of this approach is full control over the listening process, allowing for
|
||||
* custom scaling and throttling and of concurrent message processing
|
||||
* (which is up to concrete subclasses).
|
||||
*
|
||||
* <p>Message reception and listener execution can automatically be wrapped
|
||||
* in transactions through passing a Spring
|
||||
* {@link org.springframework.transaction.PlatformTransactionManager} into the
|
||||
* {@link #setTransactionManager "transactionManager"} property. This will usually
|
||||
* be a {@link org.springframework.transaction.jta.JtaTransactionManager} in a
|
||||
* J2EE enviroment, in combination with a JTA-aware JMS ConnectionFactory obtained
|
||||
* from JNDI (check your J2EE server's documentation).
|
||||
*
|
||||
* <p>This base class does not assume any specific mechanism for asynchronous
|
||||
* execution of polling invokers. Check out {@link DefaultMessageListenerContainer}
|
||||
* for a concrete implementation which is based on Spring's
|
||||
* {@link org.springframework.core.task.TaskExecutor} abstraction,
|
||||
* including dynamic scaling of concurrent consumers and automatic self recovery.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0.3
|
||||
* @see #createListenerConsumer
|
||||
* @see #receiveAndExecute
|
||||
* @see #setTransactionManager
|
||||
*/
|
||||
public abstract class AbstractPollingMessageListenerContainer extends AbstractMessageListenerContainer
|
||||
implements BeanNameAware {
|
||||
|
||||
/**
|
||||
* The default receive timeout: 1000 ms = 1 second.
|
||||
*/
|
||||
public static final long DEFAULT_RECEIVE_TIMEOUT = 1000;
|
||||
|
||||
|
||||
private final MessageListenerContainerResourceFactory transactionalResourceFactory =
|
||||
new MessageListenerContainerResourceFactory();
|
||||
|
||||
private boolean sessionTransactedCalled = false;
|
||||
|
||||
private boolean pubSubNoLocal = false;
|
||||
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
private DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
|
||||
|
||||
private long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT;
|
||||
|
||||
|
||||
public void setSessionTransacted(boolean sessionTransacted) {
|
||||
super.setSessionTransacted(sessionTransacted);
|
||||
this.sessionTransactedCalled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to inhibit the delivery of messages published by its own connection.
|
||||
* Default is "false".
|
||||
* @see javax.jms.TopicSession#createSubscriber(javax.jms.Topic, String, boolean)
|
||||
*/
|
||||
public void setPubSubNoLocal(boolean pubSubNoLocal) {
|
||||
this.pubSubNoLocal = pubSubNoLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to inhibit the delivery of messages published by its own connection.
|
||||
*/
|
||||
protected boolean isPubSubNoLocal() {
|
||||
return this.pubSubNoLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the Spring {@link org.springframework.transaction.PlatformTransactionManager}
|
||||
* to use for transactional wrapping of message reception plus listener execution.
|
||||
* <p>Default is none, not performing any transactional wrapping.
|
||||
* If specified, this will usually be a Spring
|
||||
* {@link org.springframework.transaction.jta.JtaTransactionManager} or one
|
||||
* of its subclasses, in combination with a JTA-aware ConnectionFactory that
|
||||
* this message listener container obtains its Connections from.
|
||||
* <p><b>Note: Consider the use of local JMS transactions instead.</b>
|
||||
* Simply switch the {@link #setSessionTransacted "sessionTransacted"} flag
|
||||
* to "true" in order to use a locally transacted JMS Session for the entire
|
||||
* receive processing, including any Session operations performed by a
|
||||
* {@link SessionAwareMessageListener} (e.g. sending a response message).
|
||||
* Alternatively, a {@link org.springframework.jms.connection.JmsTransactionManager}
|
||||
* may be used for fully synchronized Spring transactions based on local JMS
|
||||
* transactions. Check {@link AbstractMessageListenerContainer}'s javadoc for
|
||||
* a discussion of transaction choices and message redelivery scenarios.
|
||||
* @see org.springframework.transaction.jta.JtaTransactionManager
|
||||
* @see org.springframework.jms.connection.JmsTransactionManager
|
||||
*/
|
||||
public void setTransactionManager(PlatformTransactionManager transactionManager) {
|
||||
this.transactionManager = transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Spring PlatformTransactionManager to use for transactional
|
||||
* wrapping of message reception plus listener execution.
|
||||
*/
|
||||
protected final PlatformTransactionManager getTransactionManager() {
|
||||
return this.transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the transaction name to use for transactional wrapping.
|
||||
* Default is the bean name of this listener container, if any.
|
||||
* @see org.springframework.transaction.TransactionDefinition#getName()
|
||||
*/
|
||||
public void setTransactionName(String transactionName) {
|
||||
this.transactionDefinition.setName(transactionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the transaction timeout to use for transactional wrapping, in <b>seconds</b>.
|
||||
* Default is none, using the transaction manager's default timeout.
|
||||
* @see org.springframework.transaction.TransactionDefinition#getTimeout()
|
||||
* @see #setReceiveTimeout
|
||||
*/
|
||||
public void setTransactionTimeout(int transactionTimeout) {
|
||||
this.transactionDefinition.setTimeout(transactionTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timeout to use for receive calls, in <b>milliseconds</b>.
|
||||
* The default is 1000 ms, that is, 1 second.
|
||||
* <p><b>NOTE:</b> This value needs to be smaller than the transaction
|
||||
* timeout used by the transaction manager (in the appropriate unit,
|
||||
* of course). -1 indicates no timeout at all; however, this is only
|
||||
* feasible if not running within a transaction manager.
|
||||
* @see javax.jms.MessageConsumer#receive(long)
|
||||
* @see javax.jms.MessageConsumer#receive()
|
||||
* @see #setTransactionTimeout
|
||||
*/
|
||||
public void setReceiveTimeout(long receiveTimeout) {
|
||||
this.receiveTimeout = receiveTimeout;
|
||||
}
|
||||
|
||||
|
||||
public void initialize() {
|
||||
// Set sessionTransacted=true in case of a non-JTA transaction manager.
|
||||
if (!this.sessionTransactedCalled &&
|
||||
this.transactionManager instanceof ResourceTransactionManager &&
|
||||
!TransactionSynchronizationUtils.sameResourceFactory(
|
||||
(ResourceTransactionManager) this.transactionManager, getConnectionFactory())) {
|
||||
super.setSessionTransacted(true);
|
||||
}
|
||||
|
||||
// Use bean name as default transaction name.
|
||||
if (this.transactionDefinition.getName() == null) {
|
||||
this.transactionDefinition.setName(getBeanName());
|
||||
}
|
||||
|
||||
// Proceed with superclass initialization.
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a MessageConsumer for the given JMS Session,
|
||||
* registering a MessageListener for the specified listener.
|
||||
* @param session the JMS Session to work on
|
||||
* @return the MessageConsumer
|
||||
* @throws javax.jms.JMSException if thrown by JMS methods
|
||||
* @see #receiveAndExecute
|
||||
*/
|
||||
protected MessageConsumer createListenerConsumer(Session session) throws JMSException {
|
||||
Destination destination = getDestination();
|
||||
if (destination == null) {
|
||||
destination = resolveDestinationName(session, getDestinationName());
|
||||
}
|
||||
return createConsumer(session, destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the listener for a message received from the given consumer,
|
||||
* wrapping the entire operation in an external transaction if demanded.
|
||||
* @param session the JMS Session to work on
|
||||
* @param consumer the MessageConsumer to work on
|
||||
* @return whether a message has been received
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
* @see #doReceiveAndExecute
|
||||
*/
|
||||
protected boolean receiveAndExecute(Object invoker, Session session, MessageConsumer consumer)
|
||||
throws JMSException {
|
||||
|
||||
if (this.transactionManager != null) {
|
||||
// Execute receive within transaction.
|
||||
TransactionStatus status = this.transactionManager.getTransaction(this.transactionDefinition);
|
||||
boolean messageReceived = true;
|
||||
try {
|
||||
messageReceived = doReceiveAndExecute(invoker, session, consumer, status);
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
rollbackOnException(status, ex);
|
||||
throw ex;
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
rollbackOnException(status, ex);
|
||||
throw ex;
|
||||
}
|
||||
catch (Error err) {
|
||||
rollbackOnException(status, err);
|
||||
throw err;
|
||||
}
|
||||
this.transactionManager.commit(status);
|
||||
return messageReceived;
|
||||
}
|
||||
|
||||
else {
|
||||
// Execute receive outside of transaction.
|
||||
return doReceiveAndExecute(invoker, session, consumer, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually execute the listener for a message received from the given consumer,
|
||||
* fetching all requires resources and invoking the listener.
|
||||
* @param session the JMS Session to work on
|
||||
* @param consumer the MessageConsumer to work on
|
||||
* @param status the TransactionStatus (may be <code>null</code>)
|
||||
* @return whether a message has been received
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
* @see #doExecuteListener(javax.jms.Session, javax.jms.Message)
|
||||
*/
|
||||
protected boolean doReceiveAndExecute(
|
||||
Object invoker, Session session, MessageConsumer consumer, TransactionStatus status)
|
||||
throws JMSException {
|
||||
|
||||
Connection conToClose = null;
|
||||
Session sessionToClose = null;
|
||||
MessageConsumer consumerToClose = null;
|
||||
try {
|
||||
Session sessionToUse = session;
|
||||
boolean transactional = false;
|
||||
if (sessionToUse == null) {
|
||||
sessionToUse = ConnectionFactoryUtils.doGetTransactionalSession(
|
||||
getConnectionFactory(), this.transactionalResourceFactory, true);
|
||||
transactional = (sessionToUse != null);
|
||||
}
|
||||
if (sessionToUse == null) {
|
||||
Connection conToUse = null;
|
||||
if (sharedConnectionEnabled()) {
|
||||
conToUse = getSharedConnection();
|
||||
}
|
||||
else {
|
||||
conToUse = createConnection();
|
||||
conToClose = conToUse;
|
||||
conToUse.start();
|
||||
}
|
||||
sessionToUse = createSession(conToUse);
|
||||
sessionToClose = sessionToUse;
|
||||
}
|
||||
MessageConsumer consumerToUse = consumer;
|
||||
if (consumerToUse == null) {
|
||||
consumerToUse = createListenerConsumer(sessionToUse);
|
||||
consumerToClose = consumerToUse;
|
||||
}
|
||||
Message message = receiveMessage(consumerToUse);
|
||||
if (message != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Received message of type [" + message.getClass() + "] from consumer [" +
|
||||
consumerToUse + "] of " + (transactional ? "transactional " : "") + "session [" +
|
||||
sessionToUse + "]");
|
||||
}
|
||||
messageReceived(invoker, sessionToUse);
|
||||
boolean exposeResource = (!transactional && isExposeListenerSession() &&
|
||||
!TransactionSynchronizationManager.hasResource(getConnectionFactory()));
|
||||
if (exposeResource) {
|
||||
TransactionSynchronizationManager.bindResource(
|
||||
getConnectionFactory(), new LocallyExposedJmsResourceHolder(sessionToUse));
|
||||
}
|
||||
try {
|
||||
doExecuteListener(sessionToUse, message);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
if (status != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Rolling back transaction because of listener exception thrown: " + ex);
|
||||
}
|
||||
status.setRollbackOnly();
|
||||
}
|
||||
handleListenerException(ex);
|
||||
// Rethrow JMSException to indicate an infrastructure problem
|
||||
// that may have to trigger recovery...
|
||||
if (ex instanceof JMSException) {
|
||||
throw (JMSException) ex;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (exposeResource) {
|
||||
TransactionSynchronizationManager.unbindResource(getConnectionFactory());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Consumer [" + consumerToUse + "] of " + (transactional ? "transactional " : "") +
|
||||
"session [" + sessionToUse + "] did not receive a message");
|
||||
}
|
||||
noMessageReceived(invoker, sessionToUse);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
JmsUtils.closeMessageConsumer(consumerToClose);
|
||||
JmsUtils.closeSession(sessionToClose);
|
||||
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation checks whether the Session is externally synchronized.
|
||||
* In this case, the Session is not locally transacted, despite the listener
|
||||
* container's "sessionTransacted" flag being set to "true".
|
||||
* @see org.springframework.jms.connection.JmsResourceHolder
|
||||
*/
|
||||
protected boolean isSessionLocallyTransacted(Session session) {
|
||||
if (!super.isSessionLocallyTransacted(session)) {
|
||||
return false;
|
||||
}
|
||||
JmsResourceHolder resourceHolder =
|
||||
(JmsResourceHolder) TransactionSynchronizationManager.getResource(getConnectionFactory());
|
||||
return (resourceHolder == null || resourceHolder instanceof LocallyExposedJmsResourceHolder ||
|
||||
!resourceHolder.containsSession(session));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a rollback, handling rollback exceptions properly.
|
||||
* @param status object representing the transaction
|
||||
* @param ex the thrown listener exception or error
|
||||
*/
|
||||
private void rollbackOnException(TransactionStatus status, Throwable ex) {
|
||||
logger.debug("Initiating transaction rollback on listener exception", ex);
|
||||
try {
|
||||
this.transactionManager.rollback(status);
|
||||
}
|
||||
catch (RuntimeException ex2) {
|
||||
logger.error("Listener exception overridden by rollback exception", ex);
|
||||
throw ex2;
|
||||
}
|
||||
catch (Error err) {
|
||||
logger.error("Listener exception overridden by rollback error", ex);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a message from the given consumer.
|
||||
* @param consumer the MessageConsumer to use
|
||||
* @return the Message, or <code>null</code> if none
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
*/
|
||||
protected Message receiveMessage(MessageConsumer consumer) throws JMSException {
|
||||
return (this.receiveTimeout < 0 ? consumer.receive() : consumer.receive(this.receiveTimeout));
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method that gets called right when a new message has been received,
|
||||
* before attempting to process it. Allows subclasses to react to the event
|
||||
* of an actual incoming message, for example adapting their consumer count.
|
||||
* @param invoker the invoker object (passed through)
|
||||
* @param session the receiving JMS Session
|
||||
*/
|
||||
protected void messageReceived(Object invoker, Session session) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method that gets called right <i>no</i> message has been received,
|
||||
* before attempting to process it. Allows subclasses to react to the event
|
||||
* of an actual incoming message, for example marking .
|
||||
* @param invoker the invoker object (passed through)
|
||||
* @param session the receiving JMS Session
|
||||
*/
|
||||
protected void noMessageReceived(Object invoker, Session session) {
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// JMS 1.1 factory methods, potentially overridden for JMS 1.0.2
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetch an appropriate Connection from the given JmsResourceHolder.
|
||||
* <p>This implementation accepts any JMS 1.1 Connection.
|
||||
* @param holder the JmsResourceHolder
|
||||
* @return an appropriate Connection fetched from the holder,
|
||||
* or <code>null</code> if none found
|
||||
*/
|
||||
protected Connection getConnection(JmsResourceHolder holder) {
|
||||
return holder.getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch an appropriate Session from the given JmsResourceHolder.
|
||||
* <p>This implementation accepts any JMS 1.1 Session.
|
||||
* @param holder the JmsResourceHolder
|
||||
* @return an appropriate Session fetched from the holder,
|
||||
* or <code>null</code> if none found
|
||||
*/
|
||||
protected Session getSession(JmsResourceHolder holder) {
|
||||
return holder.getSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JMS MessageConsumer for the given Session and Destination.
|
||||
* <p>This implementation uses JMS 1.1 API.
|
||||
* @param session the JMS Session to create a MessageConsumer for
|
||||
* @param destination the JMS Destination to create a MessageConsumer for
|
||||
* @return the new JMS MessageConsumer
|
||||
* @throws javax.jms.JMSException if thrown by JMS API methods
|
||||
*/
|
||||
protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException {
|
||||
// Only pass in the NoLocal flag in case of a Topic:
|
||||
// Some JMS providers, such as WebSphere MQ 6.0, throw IllegalStateException
|
||||
// in case of the NoLocal flag being specified for a Queue.
|
||||
if (isPubSubDomain()) {
|
||||
if (isSubscriptionDurable() && destination instanceof Topic) {
|
||||
return session.createDurableSubscriber(
|
||||
(Topic) destination, getDurableSubscriptionName(), getMessageSelector(), isPubSubNoLocal());
|
||||
}
|
||||
else {
|
||||
return session.createConsumer(destination, getMessageSelector(), isPubSubNoLocal());
|
||||
}
|
||||
}
|
||||
else {
|
||||
return session.createConsumer(destination, getMessageSelector());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ResourceFactory implementation that delegates to this listener container's protected callback methods.
|
||||
*/
|
||||
private class MessageListenerContainerResourceFactory implements ConnectionFactoryUtils.ResourceFactory {
|
||||
|
||||
public Connection getConnection(JmsResourceHolder holder) {
|
||||
return AbstractPollingMessageListenerContainer.this.getConnection(holder);
|
||||
}
|
||||
|
||||
public Session getSession(JmsResourceHolder holder) {
|
||||
return AbstractPollingMessageListenerContainer.this.getSession(holder);
|
||||
}
|
||||
|
||||
public Connection createConnection() throws JMSException {
|
||||
if (AbstractPollingMessageListenerContainer.this.sharedConnectionEnabled()) {
|
||||
Connection sharedCon = AbstractPollingMessageListenerContainer.this.getSharedConnection();
|
||||
return new SingleConnectionFactory(sharedCon).createConnection();
|
||||
}
|
||||
else {
|
||||
return AbstractPollingMessageListenerContainer.this.createConnection();
|
||||
}
|
||||
}
|
||||
|
||||
public Session createSession(Connection con) throws JMSException {
|
||||
return AbstractPollingMessageListenerContainer.this.createSession(con);
|
||||
}
|
||||
|
||||
public boolean isSynchedLocalTransactionAllowed() {
|
||||
return AbstractPollingMessageListenerContainer.this.isSessionTransacted();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.MessageConsumer;
|
||||
import javax.jms.Queue;
|
||||
import javax.jms.QueueConnection;
|
||||
import javax.jms.QueueConnectionFactory;
|
||||
import javax.jms.QueueSession;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.Topic;
|
||||
import javax.jms.TopicConnection;
|
||||
import javax.jms.TopicConnectionFactory;
|
||||
import javax.jms.TopicSession;
|
||||
|
||||
import org.springframework.jms.connection.JmsResourceHolder;
|
||||
|
||||
/**
|
||||
* A subclass of {@link DefaultMessageListenerContainer} for the JMS 1.0.2 specification,
|
||||
* not relying on JMS 1.1 methods like SimpleMessageListenerContainer itself.
|
||||
*
|
||||
* <p>This class can be used for JMS 1.0.2 providers, offering the same facility as
|
||||
* DefaultMessageListenerContainer does for JMS 1.1 providers.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
*/
|
||||
public class DefaultMessageListenerContainer102 extends DefaultMessageListenerContainer {
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to accept either
|
||||
* a QueueConnection or a TopicConnection, depending on the domain.
|
||||
*/
|
||||
protected Connection getConnection(JmsResourceHolder holder) {
|
||||
return holder.getConnection(isPubSubDomain() ? (Class) TopicConnection.class : QueueConnection.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to accept either
|
||||
* a QueueSession or a TopicSession, depending on the domain.
|
||||
*/
|
||||
protected Session getSession(JmsResourceHolder holder) {
|
||||
return holder.getSession(isPubSubDomain() ? (Class) TopicSession.class : QueueSession.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected Connection createConnection() throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicConnectionFactory) getConnectionFactory()).createTopicConnection();
|
||||
}
|
||||
else {
|
||||
return ((QueueConnectionFactory) getConnectionFactory()).createQueueConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected Session createSession(Connection con) throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicConnection) con).createTopicSession(isSessionTransacted(), getSessionAcknowledgeMode());
|
||||
}
|
||||
else {
|
||||
return ((QueueConnection) con).createQueueSession(isSessionTransacted(), getSessionAcknowledgeMode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
if (isSubscriptionDurable()) {
|
||||
return ((TopicSession) session).createDurableSubscriber(
|
||||
(Topic) destination, getDurableSubscriptionName(), getMessageSelector(), isPubSubNoLocal());
|
||||
}
|
||||
else {
|
||||
return ((TopicSession) session).createSubscriber(
|
||||
(Topic) destination, getMessageSelector(), isPubSubNoLocal());
|
||||
}
|
||||
}
|
||||
else {
|
||||
return ((QueueSession) session).createReceiver((Queue) destination, getMessageSelector());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to avoid using
|
||||
* JMS 1.1's Session <code>getAcknowledgeMode()</code> method.
|
||||
* The best we can do here is to check the setting on the listener container.
|
||||
*/
|
||||
protected boolean isClientAcknowledge(Session session) throws JMSException {
|
||||
return (getSessionAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener;
|
||||
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.Session;
|
||||
|
||||
/**
|
||||
* Variant of the standard JMS {@link javax.jms.MessageListener} interface,
|
||||
* offering not only the received Message but also the underlying
|
||||
* JMS Session object. The latter can be used to send reply messages,
|
||||
* without the need to access an external Connection/Session,
|
||||
* i.e. without the need to access the underlying ConnectionFactory.
|
||||
*
|
||||
* <p>Supported by Spring's {@link DefaultMessageListenerContainer}
|
||||
* and {@link SimpleMessageListenerContainer},
|
||||
* as direct alternative to the standard JMS MessageListener interface.
|
||||
* Typically <i>not</i> supported by JCA-based listener containers:
|
||||
* For maximum compatibility, implement a standard JMS MessageListener instead.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @see AbstractMessageListenerContainer#setMessageListener
|
||||
* @see DefaultMessageListenerContainer
|
||||
* @see SimpleMessageListenerContainer
|
||||
* @see org.springframework.jms.listener.endpoint.JmsMessageEndpointManager
|
||||
* @see javax.jms.MessageListener
|
||||
*/
|
||||
public interface SessionAwareMessageListener {
|
||||
|
||||
/**
|
||||
* Callback for processing a received JMS message.
|
||||
* <p>Implementors are supposed to process the given Message,
|
||||
* typically sending reply messages through the given Session.
|
||||
* @param message the received JMS message (never <code>null</code>)
|
||||
* @param session the underlying JMS Session (never <code>null</code>)
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
*/
|
||||
void onMessage(Message message, Session session) throws JMSException;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.ExceptionListener;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageConsumer;
|
||||
import javax.jms.MessageListener;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.Topic;
|
||||
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.jms.support.JmsUtils;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Message listener container that uses the plain JMS client API's
|
||||
* <code>MessageConsumer.setMessageListener()</code> method to
|
||||
* create concurrent MessageConsumers for the specified listeners.
|
||||
*
|
||||
* <p><b>NOTE:</b> This class requires a JMS 1.1+ provider, because it builds on
|
||||
* the domain-independent API. <b>Use the {@link SimpleMessageListenerContainer102}
|
||||
* subclass for a JMS 1.0.2 provider, e.g. when running on a J2EE 1.3 server.</b>
|
||||
*
|
||||
* <p>This is the simplest form of a message listener container.
|
||||
* It creates a fixed number of JMS Sessions to invoke the listener,
|
||||
* not allowing for dynamic adaptation to runtime demands. Its main
|
||||
* advantage is its low level of complexity and the minimum requirements
|
||||
* on the JMS provider: Not even the ServerSessionPool facility is required.
|
||||
*
|
||||
* <p>See the {@link AbstractMessageListenerContainer} javadoc for details
|
||||
* on acknowledge modes and transaction options.
|
||||
*
|
||||
* <p>For a different style of MessageListener handling, through looped
|
||||
* <code>MessageConsumer.receive()</code> calls that also allow for
|
||||
* transactional reception of messages (registering them with XA transactions),
|
||||
* see {@link DefaultMessageListenerContainer}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @see javax.jms.MessageConsumer#setMessageListener
|
||||
* @see DefaultMessageListenerContainer
|
||||
* @see org.springframework.jms.listener.endpoint.JmsMessageEndpointManager
|
||||
*/
|
||||
public class SimpleMessageListenerContainer extends AbstractMessageListenerContainer implements ExceptionListener {
|
||||
|
||||
private boolean pubSubNoLocal = false;
|
||||
|
||||
private int concurrentConsumers = 1;
|
||||
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
private Set sessions;
|
||||
|
||||
private Set consumers;
|
||||
|
||||
private final Object consumersMonitor = new Object();
|
||||
|
||||
|
||||
/**
|
||||
* Set whether to inhibit the delivery of messages published by its own connection.
|
||||
* Default is "false".
|
||||
* @see javax.jms.TopicSession#createSubscriber(javax.jms.Topic, String, boolean)
|
||||
*/
|
||||
public void setPubSubNoLocal(boolean pubSubNoLocal) {
|
||||
this.pubSubNoLocal = pubSubNoLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to inhibit the delivery of messages published by its own connection.
|
||||
*/
|
||||
protected boolean isPubSubNoLocal() {
|
||||
return this.pubSubNoLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the number of concurrent consumers to create. Default is 1.
|
||||
* <p>Raising the number of concurrent consumers is recommendable in order
|
||||
* to scale the consumption of messages coming in from a queue. However,
|
||||
* note that any ordering guarantees are lost once multiple consumers are
|
||||
* registered. In general, stick with 1 consumer for low-volume queues.
|
||||
* <p><b>Do not raise the number of concurrent consumers for a topic.</b>
|
||||
* This would lead to concurrent consumption of the same message,
|
||||
* which is hardly ever desirable.
|
||||
*/
|
||||
public void setConcurrentConsumers(int concurrentConsumers) {
|
||||
Assert.isTrue(concurrentConsumers > 0, "'concurrentConsumers' value must be at least 1 (one)");
|
||||
this.concurrentConsumers = concurrentConsumers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Spring TaskExecutor to use for executing the listener once
|
||||
* a message has been received by the provider.
|
||||
* <p>Default is none, that is, to run in the JMS provider's own receive thread,
|
||||
* blocking the provider's receive endpoint while executing the listener.
|
||||
* <p>Specify a TaskExecutor for executing the listener in a different thread,
|
||||
* rather than blocking the JMS provider, usually integrating with an existing
|
||||
* thread pool. This allows to keep the number of concurrent consumers low (1)
|
||||
* while still processing messages concurrently (decoupled from receiving!).
|
||||
* <p><b>NOTE: Specifying a TaskExecutor for listener execution affects
|
||||
* acknowledgement semantics.</b> Messages will then always get acknowledged
|
||||
* before listener execution, with the underlying Session immediately reused
|
||||
* for receiving the next message. Using this in combination with a transacted
|
||||
* session or with client acknowledgement will lead to unspecified results!
|
||||
* <p><b>NOTE: Concurrent listener execution via a TaskExecutor will lead
|
||||
* to concurrent processing of messages that have been received by the same
|
||||
* underlying Session.</b> As a consequence, it is not recommended to use
|
||||
* this setting with a {@link SessionAwareMessageListener}, at least not
|
||||
* if the latter performs actual work on the given Session. A standard
|
||||
* {@link javax.jms.MessageListener} will work fine, in general.
|
||||
* @see #setConcurrentConsumers
|
||||
* @see org.springframework.core.task.SimpleAsyncTaskExecutor
|
||||
* @see org.springframework.scheduling.commonj.WorkManagerTaskExecutor
|
||||
*/
|
||||
public void setTaskExecutor(TaskExecutor taskExecutor) {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
protected void validateConfiguration() {
|
||||
super.validateConfiguration();
|
||||
if (isSubscriptionDurable() && this.concurrentConsumers != 1) {
|
||||
throw new IllegalArgumentException("Only 1 concurrent consumer supported for durable subscription");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Implementation of AbstractMessageListenerContainer's template methods
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Always use a shared JMS Connection.
|
||||
*/
|
||||
protected final boolean sharedConnectionEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the specified number of concurrent consumers,
|
||||
* in the form of a JMS Session plus associated MessageConsumer.
|
||||
* @see #createListenerConsumer
|
||||
*/
|
||||
protected void doInitialize() throws JMSException {
|
||||
establishSharedConnection();
|
||||
initializeConsumers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-initializes this container's JMS message consumers,
|
||||
* if not initialized already.
|
||||
*/
|
||||
protected void doStart() throws JMSException {
|
||||
super.doStart();
|
||||
initializeConsumers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this listener container as JMS ExceptionListener on the shared connection.
|
||||
*/
|
||||
protected void prepareSharedConnection(Connection connection) throws JMSException {
|
||||
super.prepareSharedConnection(connection);
|
||||
connection.setExceptionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* JMS ExceptionListener implementation, invoked by the JMS provider in
|
||||
* case of connection failures. Re-initializes this listener container's
|
||||
* shared connection and its sessions and consumers.
|
||||
* @param ex the reported connection exception
|
||||
*/
|
||||
public void onException(JMSException ex) {
|
||||
// First invoke the user-specific ExceptionListener, if any.
|
||||
invokeExceptionListener(ex);
|
||||
|
||||
// Now try to recover the shared Connection and all consumers...
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Trying to recover from JMS Connection exception: " + ex);
|
||||
}
|
||||
try {
|
||||
synchronized (this.consumersMonitor) {
|
||||
this.sessions = null;
|
||||
this.consumers = null;
|
||||
}
|
||||
refreshSharedConnection();
|
||||
initializeConsumers();
|
||||
logger.info("Successfully refreshed JMS Connection");
|
||||
}
|
||||
catch (JMSException recoverEx) {
|
||||
logger.debug("Failed to recover JMS Connection", recoverEx);
|
||||
logger.error("Encountered non-recoverable JMSException", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the JMS Sessions and MessageConsumers for this container.
|
||||
* @throws JMSException in case of setup failure
|
||||
*/
|
||||
protected void initializeConsumers() throws JMSException {
|
||||
// Register Sessions and MessageConsumers.
|
||||
synchronized (this.consumersMonitor) {
|
||||
if (this.consumers == null) {
|
||||
this.sessions = new HashSet(this.concurrentConsumers);
|
||||
this.consumers = new HashSet(this.concurrentConsumers);
|
||||
Connection con = getSharedConnection();
|
||||
for (int i = 0; i < this.concurrentConsumers; i++) {
|
||||
Session session = createSession(con);
|
||||
MessageConsumer consumer = createListenerConsumer(session);
|
||||
this.sessions.add(session);
|
||||
this.consumers.add(consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MessageConsumer for the given JMS Session,
|
||||
* registering a MessageListener for the specified listener.
|
||||
* @param session the JMS Session to work on
|
||||
* @return the MessageConsumer
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
* @see #executeListener
|
||||
*/
|
||||
protected MessageConsumer createListenerConsumer(final Session session) throws JMSException {
|
||||
Destination destination = getDestination();
|
||||
if (destination == null) {
|
||||
destination = resolveDestinationName(session, getDestinationName());
|
||||
}
|
||||
MessageConsumer consumer = createConsumer(session, destination);
|
||||
|
||||
if (this.taskExecutor != null) {
|
||||
consumer.setMessageListener(new MessageListener() {
|
||||
public void onMessage(final Message message) {
|
||||
taskExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
processMessage(message, session);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
consumer.setMessageListener(new MessageListener() {
|
||||
public void onMessage(Message message) {
|
||||
processMessage(message, session);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return consumer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a message received from the provider.
|
||||
* <p>Executes the listener, exposing the current JMS Session as
|
||||
* thread-bound resource (if "exposeListenerSession" is "true").
|
||||
* @param message the received JMS Message
|
||||
* @param session the JMS Session to operate on
|
||||
* @see #executeListener
|
||||
* @see #setExposeListenerSession
|
||||
*/
|
||||
protected void processMessage(Message message, Session session) {
|
||||
boolean exposeResource = isExposeListenerSession();
|
||||
if (exposeResource) {
|
||||
TransactionSynchronizationManager.bindResource(
|
||||
getConnectionFactory(), new LocallyExposedJmsResourceHolder(session));
|
||||
}
|
||||
try {
|
||||
executeListener(session, message);
|
||||
}
|
||||
finally {
|
||||
if (exposeResource) {
|
||||
TransactionSynchronizationManager.unbindResource(getConnectionFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the registered JMS Sessions and associated MessageConsumers.
|
||||
*/
|
||||
protected void doShutdown() throws JMSException {
|
||||
logger.debug("Closing JMS MessageConsumers");
|
||||
for (Iterator it = this.consumers.iterator(); it.hasNext();) {
|
||||
MessageConsumer consumer = (MessageConsumer) it.next();
|
||||
JmsUtils.closeMessageConsumer(consumer);
|
||||
}
|
||||
logger.debug("Closing JMS Sessions");
|
||||
for (Iterator it = this.sessions.iterator(); it.hasNext();) {
|
||||
Session session = (Session) it.next();
|
||||
JmsUtils.closeSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// JMS 1.1 factory methods, potentially overridden for JMS 1.0.2
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create a JMS MessageConsumer for the given Session and Destination.
|
||||
* <p>This implementation uses JMS 1.1 API.
|
||||
* @param session the JMS Session to create a MessageConsumer for
|
||||
* @param destination the JMS Destination to create a MessageConsumer for
|
||||
* @return the new JMS MessageConsumer
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
*/
|
||||
protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException {
|
||||
// Only pass in the NoLocal flag in case of a Topic:
|
||||
// Some JMS providers, such as WebSphere MQ 6.0, throw IllegalStateException
|
||||
// in case of the NoLocal flag being specified for a Queue.
|
||||
if (isPubSubDomain()) {
|
||||
if (isSubscriptionDurable() && destination instanceof Topic) {
|
||||
return session.createDurableSubscriber(
|
||||
(Topic) destination, getDurableSubscriptionName(), getMessageSelector(), isPubSubNoLocal());
|
||||
}
|
||||
else {
|
||||
return session.createConsumer(destination, getMessageSelector(), isPubSubNoLocal());
|
||||
}
|
||||
}
|
||||
else {
|
||||
return session.createConsumer(destination, getMessageSelector());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.MessageConsumer;
|
||||
import javax.jms.Queue;
|
||||
import javax.jms.QueueConnection;
|
||||
import javax.jms.QueueConnectionFactory;
|
||||
import javax.jms.QueueSession;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.Topic;
|
||||
import javax.jms.TopicConnection;
|
||||
import javax.jms.TopicConnectionFactory;
|
||||
import javax.jms.TopicSession;
|
||||
|
||||
/**
|
||||
* A subclass of {@link SimpleMessageListenerContainer} for the JMS 1.0.2 specification,
|
||||
* not relying on JMS 1.1 methods like SimpleMessageListenerContainer itself.
|
||||
*
|
||||
* <p>This class can be used for JMS 1.0.2 providers, offering the same facility as
|
||||
* SimpleMessageListenerContainer does for JMS 1.1 providers.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SimpleMessageListenerContainer102 extends SimpleMessageListenerContainer {
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected Connection createConnection() throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicConnectionFactory) getConnectionFactory()).createTopicConnection();
|
||||
}
|
||||
else {
|
||||
return ((QueueConnectionFactory) getConnectionFactory()).createQueueConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected Session createSession(Connection con) throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicConnection) con).createTopicSession(isSessionTransacted(), getSessionAcknowledgeMode());
|
||||
}
|
||||
else {
|
||||
return ((QueueConnection) con).createQueueSession(isSessionTransacted(), getSessionAcknowledgeMode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected MessageConsumer createConsumer(Session session, Destination destination) throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
if (isSubscriptionDurable()) {
|
||||
return ((TopicSession) session).createDurableSubscriber(
|
||||
(Topic) destination, getDurableSubscriptionName(), getMessageSelector(), isPubSubNoLocal());
|
||||
}
|
||||
else {
|
||||
return ((TopicSession) session).createSubscriber(
|
||||
(Topic) destination, getMessageSelector(), isPubSubNoLocal());
|
||||
}
|
||||
}
|
||||
else {
|
||||
return ((QueueSession) session).createReceiver((Queue) destination, getMessageSelector());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to avoid using
|
||||
* JMS 1.1's Session <code>getAcknowledgeMode()</code> method.
|
||||
* The best we can do here is to check the setting on the listener container.
|
||||
*/
|
||||
protected boolean isClientAcknowledge(Session session) throws JMSException {
|
||||
return (getSessionAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,656 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener.adapter;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.InvalidDestinationException;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageListener;
|
||||
import javax.jms.MessageProducer;
|
||||
import javax.jms.Session;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.jms.listener.SessionAwareMessageListener;
|
||||
import org.springframework.jms.listener.SubscriptionNameProvider;
|
||||
import org.springframework.jms.support.JmsUtils;
|
||||
import org.springframework.jms.support.converter.MessageConversionException;
|
||||
import org.springframework.jms.support.converter.MessageConverter;
|
||||
import org.springframework.jms.support.converter.SimpleMessageConverter;
|
||||
import org.springframework.jms.support.destination.DestinationResolver;
|
||||
import org.springframework.jms.support.destination.DynamicDestinationResolver;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MethodInvoker;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Message listener adapter that delegates the handling of messages to target
|
||||
* listener methods via reflection, with flexible message type conversion.
|
||||
* Allows listener methods to operate on message content types, completely
|
||||
* independent from the JMS API.
|
||||
*
|
||||
* <p><b>NOTE:</b> This class requires a JMS 1.1+ provider, because it builds
|
||||
* on the domain-independent API. <b>Use the {@link MessageListenerAdapter102
|
||||
* MessageListenerAdapter102} subclass for JMS 1.0.2 providers.</b>
|
||||
*
|
||||
* <p>By default, the content of incoming JMS messages gets extracted before
|
||||
* being passed into the target listener method, to let the target method
|
||||
* operate on message content types such as String or byte array instead of
|
||||
* the raw {@link Message}. Message type conversion is delegated to a Spring
|
||||
* JMS {@link MessageConverter}. By default, a {@link SimpleMessageConverter}
|
||||
* {@link org.springframework.jms.support.converter.SimpleMessageConverter102 (102)}
|
||||
* will be used. (If you do not want such automatic message conversion taking
|
||||
* place, then be sure to set the {@link #setMessageConverter MessageConverter}
|
||||
* to <code>null</code>.)
|
||||
*
|
||||
* <p>If a target listener method returns a non-null object (typically of a
|
||||
* message content type such as <code>String</code> or byte array), it will get
|
||||
* wrapped in a JMS <code>Message</code> and sent to the response destination
|
||||
* (either the JMS "reply-to" destination or a
|
||||
* {@link #setDefaultResponseDestination(javax.jms.Destination) specified default
|
||||
* destination}).
|
||||
*
|
||||
* <p><b>Note:</b> The sending of response messages is only available when
|
||||
* using the {@link SessionAwareMessageListener} entry point (typically through a
|
||||
* Spring message listener container). Usage as standard JMS {@link MessageListener}
|
||||
* does <i>not</i> support the generation of response messages.
|
||||
*
|
||||
* <p>Find below some examples of method signatures compliant with this
|
||||
* adapter class. This first example handles all <code>Message</code> types
|
||||
* and gets passed the contents of each <code>Message</code> type as an
|
||||
* argument. No <code>Message</code> will be sent back as all of these
|
||||
* methods return <code>void</code>.
|
||||
*
|
||||
* <pre class="code">public interface MessageContentsDelegate {
|
||||
* void handleMessage(String text);
|
||||
* void handleMessage(Map map);
|
||||
* void handleMessage(byte[] bytes);
|
||||
* void handleMessage(Serializable obj);
|
||||
* }</pre>
|
||||
*
|
||||
* This next example handles all <code>Message</code> types and gets
|
||||
* passed the actual (raw) <code>Message</code> as an argument. Again, no
|
||||
* <code>Message</code> will be sent back as all of these methods return
|
||||
* <code>void</code>.
|
||||
*
|
||||
* <pre class="code">public interface RawMessageDelegate {
|
||||
* void handleMessage(TextMessage message);
|
||||
* void handleMessage(MapMessage message);
|
||||
* void handleMessage(BytesMessage message);
|
||||
* void handleMessage(ObjectMessage message);
|
||||
* }</pre>
|
||||
*
|
||||
* This next example illustrates a <code>Message</code> delegate
|
||||
* that just consumes the <code>String</code> contents of
|
||||
* {@link javax.jms.TextMessage TextMessages}. Notice also how the
|
||||
* name of the <code>Message</code> handling method is different from the
|
||||
* {@link #ORIGINAL_DEFAULT_LISTENER_METHOD original} (this will have to
|
||||
* be configured in the attandant bean definition). Again, no <code>Message</code>
|
||||
* will be sent back as the method returns <code>void</code>.
|
||||
*
|
||||
* <pre class="code">public interface TextMessageContentDelegate {
|
||||
* void onMessage(String text);
|
||||
* }</pre>
|
||||
*
|
||||
* This final example illustrates a <code>Message</code> delegate
|
||||
* that just consumes the <code>String</code> contents of
|
||||
* {@link javax.jms.TextMessage TextMessages}. Notice how the return type
|
||||
* of this method is <code>String</code>: This will result in the configured
|
||||
* {@link MessageListenerAdapter} sending a {@link javax.jms.TextMessage} in response.
|
||||
*
|
||||
* <pre class="code">public interface ResponsiveTextMessageContentDelegate {
|
||||
* String handleMessage(String text);
|
||||
* }</pre>
|
||||
*
|
||||
* For further examples and discussion please do refer to the Spring
|
||||
* reference documentation which describes this class (and it's attendant
|
||||
* XML configuration) in detail.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @see #setDelegate
|
||||
* @see #setDefaultListenerMethod
|
||||
* @see #setDefaultResponseDestination
|
||||
* @see #setMessageConverter
|
||||
* @see org.springframework.jms.support.converter.SimpleMessageConverter
|
||||
* @see org.springframework.jms.listener.SessionAwareMessageListener
|
||||
* @see org.springframework.jms.listener.AbstractMessageListenerContainer#setMessageListener
|
||||
*/
|
||||
public class MessageListenerAdapter implements MessageListener, SessionAwareMessageListener, SubscriptionNameProvider {
|
||||
|
||||
/**
|
||||
* Out-of-the-box value for the default listener method: "handleMessage".
|
||||
*/
|
||||
public static final String ORIGINAL_DEFAULT_LISTENER_METHOD = "handleMessage";
|
||||
|
||||
|
||||
/** Logger available to subclasses */
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private Object delegate;
|
||||
|
||||
private String defaultListenerMethod = ORIGINAL_DEFAULT_LISTENER_METHOD;
|
||||
|
||||
private Object defaultResponseDestination;
|
||||
|
||||
private DestinationResolver destinationResolver = new DynamicDestinationResolver();
|
||||
|
||||
private MessageConverter messageConverter;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link MessageListenerAdapter} with default settings.
|
||||
*/
|
||||
public MessageListenerAdapter() {
|
||||
initDefaultStrategies();
|
||||
this.delegate = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link MessageListenerAdapter} for the given delegate.
|
||||
* @param delegate the delegate object
|
||||
*/
|
||||
public MessageListenerAdapter(Object delegate) {
|
||||
initDefaultStrategies();
|
||||
setDelegate(delegate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a target object to delegate message listening to.
|
||||
* Specified listener methods have to be present on this target object.
|
||||
* <p>If no explicit delegate object has been specified, listener
|
||||
* methods are expected to present on this adapter instance, that is,
|
||||
* on a custom subclass of this adapter, defining listener methods.
|
||||
*/
|
||||
public void setDelegate(Object delegate) {
|
||||
Assert.notNull(delegate, "Delegate must not be null");
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the target object to delegate message listening to.
|
||||
*/
|
||||
protected Object getDelegate() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the name of the default listener method to delegate to,
|
||||
* for the case where no specific listener method has been determined.
|
||||
* Out-of-the-box value is {@link #ORIGINAL_DEFAULT_LISTENER_METHOD "handleMessage"}.
|
||||
* @see #getListenerMethodName
|
||||
*/
|
||||
public void setDefaultListenerMethod(String defaultListenerMethod) {
|
||||
this.defaultListenerMethod = defaultListenerMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the default listener method to delegate to.
|
||||
*/
|
||||
protected String getDefaultListenerMethod() {
|
||||
return this.defaultListenerMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default destination to send response messages to. This will be applied
|
||||
* in case of a request message that does not carry a "JMSReplyTo" field.
|
||||
* <p>Response destinations are only relevant for listener methods that return
|
||||
* result objects, which will be wrapped in a response message and sent to a
|
||||
* response destination.
|
||||
* <p>Alternatively, specify a "defaultResponseQueueName" or "defaultResponseTopicName",
|
||||
* to be dynamically resolved via the DestinationResolver.
|
||||
* @see #setDefaultResponseQueueName(String)
|
||||
* @see #setDefaultResponseTopicName(String)
|
||||
* @see #getResponseDestination
|
||||
*/
|
||||
public void setDefaultResponseDestination(Destination destination) {
|
||||
this.defaultResponseDestination = destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the default response queue to send response messages to.
|
||||
* This will be applied in case of a request message that does not carry a
|
||||
* "JMSReplyTo" field.
|
||||
* <p>Alternatively, specify a JMS Destination object as "defaultResponseDestination".
|
||||
* @see #setDestinationResolver
|
||||
* @see #setDefaultResponseDestination(javax.jms.Destination)
|
||||
*/
|
||||
public void setDefaultResponseQueueName(String destinationName) {
|
||||
this.defaultResponseDestination = new DestinationNameHolder(destinationName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the default response topic to send response messages to.
|
||||
* This will be applied in case of a request message that does not carry a
|
||||
* "JMSReplyTo" field.
|
||||
* <p>Alternatively, specify a JMS Destination object as "defaultResponseDestination".
|
||||
* @see #setDestinationResolver
|
||||
* @see #setDefaultResponseDestination(javax.jms.Destination)
|
||||
*/
|
||||
public void setDefaultResponseTopicName(String destinationName) {
|
||||
this.defaultResponseDestination = new DestinationNameHolder(destinationName, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DestinationResolver that should be used to resolve response
|
||||
* destination names for this adapter.
|
||||
* <p>The default resolver is a DynamicDestinationResolver. Specify a
|
||||
* JndiDestinationResolver for resolving destination names as JNDI locations.
|
||||
* @see org.springframework.jms.support.destination.DynamicDestinationResolver
|
||||
* @see org.springframework.jms.support.destination.JndiDestinationResolver
|
||||
*/
|
||||
public void setDestinationResolver(DestinationResolver destinationResolver) {
|
||||
Assert.notNull(destinationResolver, "DestinationResolver must not be null");
|
||||
this.destinationResolver = destinationResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DestinationResolver for this adapter.
|
||||
*/
|
||||
protected DestinationResolver getDestinationResolver() {
|
||||
return this.destinationResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the converter that will convert incoming JMS messages to
|
||||
* listener method arguments, and objects returned from listener
|
||||
* methods back to JMS messages.
|
||||
* <p>The default converter is a {@link SimpleMessageConverter}, which is able
|
||||
* to handle {@link javax.jms.BytesMessage BytesMessages},
|
||||
* {@link javax.jms.TextMessage TextMessages} and
|
||||
* {@link javax.jms.ObjectMessage ObjectMessages}.
|
||||
*/
|
||||
public void setMessageConverter(MessageConverter messageConverter) {
|
||||
this.messageConverter = messageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the converter that will convert incoming JMS messages to
|
||||
* listener method arguments, and objects returned from listener
|
||||
* methods back to JMS messages.
|
||||
*/
|
||||
protected MessageConverter getMessageConverter() {
|
||||
return this.messageConverter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Standard JMS {@link MessageListener} entry point.
|
||||
* <p>Delegates the message to the target listener method, with appropriate
|
||||
* conversion of the message argument. In case of an exception, the
|
||||
* {@link #handleListenerException(Throwable)} method will be invoked.
|
||||
* <p><b>Note:</b> Does not support sending response messages based on
|
||||
* result objects returned from listener methods. Use the
|
||||
* {@link SessionAwareMessageListener} entry point (typically through a Spring
|
||||
* message listener container) for handling result objects as well.
|
||||
* @param message the incoming JMS message
|
||||
* @see #handleListenerException
|
||||
* @see #onMessage(javax.jms.Message, javax.jms.Session)
|
||||
*/
|
||||
public void onMessage(Message message) {
|
||||
try {
|
||||
onMessage(message, null);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
handleListenerException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spring {@link SessionAwareMessageListener} entry point.
|
||||
* <p>Delegates the message to the target listener method, with appropriate
|
||||
* conversion of the message argument. If the target method returns a
|
||||
* non-null object, wrap in a JMS message and send it back.
|
||||
* @param message the incoming JMS message
|
||||
* @param session the JMS session to operate on
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
*/
|
||||
public void onMessage(Message message, Session session) throws JMSException {
|
||||
// Check whether the delegate is a MessageListener impl itself.
|
||||
// In that case, the adapter will simply act as a pass-through.
|
||||
Object delegate = getDelegate();
|
||||
if (delegate != this) {
|
||||
if (delegate instanceof SessionAwareMessageListener) {
|
||||
if (session != null) {
|
||||
((SessionAwareMessageListener) delegate).onMessage(message, session);
|
||||
return;
|
||||
}
|
||||
else if (!(delegate instanceof MessageListener)) {
|
||||
throw new javax.jms.IllegalStateException("MessageListenerAdapter cannot handle a " +
|
||||
"SessionAwareMessageListener delegate if it hasn't been invoked with a Session itself");
|
||||
}
|
||||
}
|
||||
if (delegate instanceof MessageListener) {
|
||||
((MessageListener) delegate).onMessage(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Regular case: find a handler method reflectively.
|
||||
Object convertedMessage = extractMessage(message);
|
||||
String methodName = getListenerMethodName(message, convertedMessage);
|
||||
if (methodName == null) {
|
||||
throw new javax.jms.IllegalStateException("No default listener method specified: " +
|
||||
"Either specify a non-null value for the 'defaultListenerMethod' property or " +
|
||||
"override the 'getListenerMethodName' method.");
|
||||
}
|
||||
|
||||
// Invoke the handler method with appropriate arguments.
|
||||
Object[] listenerArguments = buildListenerArguments(convertedMessage);
|
||||
Object result = invokeListenerMethod(methodName, listenerArguments);
|
||||
if (result != null) {
|
||||
handleResult(result, message, session);
|
||||
}
|
||||
else {
|
||||
logger.trace("No result object given - no result to handle");
|
||||
}
|
||||
}
|
||||
|
||||
public String getSubscriptionName() {
|
||||
if (this.delegate instanceof SubscriptionNameProvider) {
|
||||
return ((SubscriptionNameProvider) this.delegate).getSubscriptionName();
|
||||
}
|
||||
else {
|
||||
return this.delegate.getClass().getName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the default implementations for the adapter's strategies.
|
||||
* @see #setMessageConverter
|
||||
* @see org.springframework.jms.support.converter.SimpleMessageConverter
|
||||
*/
|
||||
protected void initDefaultStrategies() {
|
||||
setMessageConverter(new SimpleMessageConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the given exception that arose during listener execution.
|
||||
* The default implementation logs the exception at error level.
|
||||
* <p>This method only applies when used as standard JMS {@link MessageListener}.
|
||||
* In case of the Spring {@link SessionAwareMessageListener} mechanism,
|
||||
* exceptions get handled by the caller instead.
|
||||
* @param ex the exception to handle
|
||||
* @see #onMessage(javax.jms.Message)
|
||||
*/
|
||||
protected void handleListenerException(Throwable ex) {
|
||||
logger.error("Listener execution failed", ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the message body from the given JMS message.
|
||||
* @param message the JMS <code>Message</code>
|
||||
* @return the content of the message, to be passed into the
|
||||
* listener method as argument
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
*/
|
||||
protected Object extractMessage(Message message) throws JMSException {
|
||||
MessageConverter converter = getMessageConverter();
|
||||
if (converter != null) {
|
||||
return converter.fromMessage(message);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the name of the listener method that is supposed to
|
||||
* handle the given message.
|
||||
* <p>The default implementation simply returns the configured
|
||||
* default listener method, if any.
|
||||
* @param originalMessage the JMS request message
|
||||
* @param extractedMessage the converted JMS request message,
|
||||
* to be passed into the listener method as argument
|
||||
* @return the name of the listener method (never <code>null</code>)
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #setDefaultListenerMethod
|
||||
*/
|
||||
protected String getListenerMethodName(Message originalMessage, Object extractedMessage) throws JMSException {
|
||||
return getDefaultListenerMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an array of arguments to be passed into the target listener method.
|
||||
* Allows for multiple method arguments to be built from a single message object.
|
||||
* <p>The default implementation builds an array with the given message object
|
||||
* as sole element. This means that the extracted message will always be passed
|
||||
* into a <i>single</i> method argument, even if it is an array, with the target
|
||||
* method having a corresponding single argument of the array's type declared.
|
||||
* <p>This can be overridden to treat special message content such as arrays
|
||||
* differently, for example passing in each element of the message array
|
||||
* as distinct method argument.
|
||||
* @param extractedMessage the content of the message
|
||||
* @return the array of arguments to be passed into the
|
||||
* listener method (each element of the array corresponding
|
||||
* to a distinct method argument)
|
||||
*/
|
||||
protected Object[] buildListenerArguments(Object extractedMessage) {
|
||||
return new Object[] {extractedMessage};
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the specified listener method.
|
||||
* @param methodName the name of the listener method
|
||||
* @param arguments the message arguments to be passed in
|
||||
* @return the result returned from the listener method
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #getListenerMethodName
|
||||
* @see #buildListenerArguments
|
||||
*/
|
||||
protected Object invokeListenerMethod(String methodName, Object[] arguments) throws JMSException {
|
||||
try {
|
||||
MethodInvoker methodInvoker = new MethodInvoker();
|
||||
methodInvoker.setTargetObject(getDelegate());
|
||||
methodInvoker.setTargetMethod(methodName);
|
||||
methodInvoker.setArguments(arguments);
|
||||
methodInvoker.prepare();
|
||||
return methodInvoker.invoke();
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
Throwable targetEx = ex.getTargetException();
|
||||
if (targetEx instanceof JMSException) {
|
||||
throw (JMSException) targetEx;
|
||||
}
|
||||
else {
|
||||
throw new ListenerExecutionFailedException(
|
||||
"Listener method '" + methodName + "' threw exception", targetEx);
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new ListenerExecutionFailedException("Failed to invoke target method '" + methodName +
|
||||
"' with arguments " + ObjectUtils.nullSafeToString(arguments), ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the given result object returned from the listener method,
|
||||
* sending a response message back.
|
||||
* @param result the result object to handle (never <code>null</code>)
|
||||
* @param request the original request message
|
||||
* @param session the JMS Session to operate on (may be <code>null</code>)
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #buildMessage
|
||||
* @see #postProcessResponse
|
||||
* @see #getResponseDestination
|
||||
* @see #sendResponse
|
||||
*/
|
||||
protected void handleResult(Object result, Message request, Session session) throws JMSException {
|
||||
if (session != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Listener method returned result [" + result +
|
||||
"] - generating response message for it");
|
||||
}
|
||||
Message response = buildMessage(session, result);
|
||||
postProcessResponse(request, response);
|
||||
Destination destination = getResponseDestination(request, response, session);
|
||||
sendResponse(session, destination, response);
|
||||
}
|
||||
else {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Listener method returned result [" + result +
|
||||
"]: not generating response message for it because of no JMS Session given");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a JMS message to be sent as response based on the given result object.
|
||||
* @param session the JMS Session to operate on
|
||||
* @param result the content of the message, as returned from the listener method
|
||||
* @return the JMS <code>Message</code> (never <code>null</code>)
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #setMessageConverter
|
||||
*/
|
||||
protected Message buildMessage(Session session, Object result) throws JMSException {
|
||||
MessageConverter converter = getMessageConverter();
|
||||
if (converter != null) {
|
||||
return converter.toMessage(result, session);
|
||||
}
|
||||
else {
|
||||
if (!(result instanceof Message)) {
|
||||
throw new MessageConversionException(
|
||||
"No MessageConverter specified - cannot handle message [" + result + "]");
|
||||
}
|
||||
return (Message) result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the given response message before it will be sent.
|
||||
* <p>The default implementation sets the response's correlation id
|
||||
* to the request message's correlation id, if any; otherwise to the
|
||||
* request message id.
|
||||
* @param request the original incoming JMS message
|
||||
* @param response the outgoing JMS message about to be sent
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see javax.jms.Message#setJMSCorrelationID
|
||||
*/
|
||||
protected void postProcessResponse(Message request, Message response) throws JMSException {
|
||||
String correlation = request.getJMSCorrelationID();
|
||||
if (correlation == null) {
|
||||
correlation = request.getJMSMessageID();
|
||||
}
|
||||
response.setJMSCorrelationID(correlation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine a response destination for the given message.
|
||||
* <p>The default implementation first checks the JMS Reply-To
|
||||
* {@link Destination} of the supplied request; if that is not <code>null</code>
|
||||
* it is returned; if it is <code>null</code>, then the configured
|
||||
* {@link #resolveDefaultResponseDestination default response destination}
|
||||
* is returned; if this too is <code>null</code>, then an
|
||||
* {@link InvalidDestinationException} is thrown.
|
||||
* @param request the original incoming JMS message
|
||||
* @param response the outgoing JMS message about to be sent
|
||||
* @param session the JMS Session to operate on
|
||||
* @return the response destination (never <code>null</code>)
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @throws InvalidDestinationException if no {@link Destination} can be determined
|
||||
* @see #setDefaultResponseDestination
|
||||
* @see javax.jms.Message#getJMSReplyTo()
|
||||
*/
|
||||
protected Destination getResponseDestination(Message request, Message response, Session session)
|
||||
throws JMSException {
|
||||
|
||||
Destination replyTo = request.getJMSReplyTo();
|
||||
if (replyTo == null) {
|
||||
replyTo = resolveDefaultResponseDestination(session);
|
||||
if (replyTo == null) {
|
||||
throw new InvalidDestinationException("Cannot determine response destination: " +
|
||||
"Request message does not contain reply-to destination, and no default response destination set.");
|
||||
}
|
||||
}
|
||||
return replyTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the default response destination into a JMS {@link Destination}, using this
|
||||
* accessor's {@link DestinationResolver} in case of a destination name.
|
||||
* @return the located {@link Destination}
|
||||
* @throws javax.jms.JMSException if resolution failed
|
||||
* @see #setDefaultResponseDestination
|
||||
* @see #setDefaultResponseQueueName
|
||||
* @see #setDefaultResponseTopicName
|
||||
* @see #setDestinationResolver
|
||||
*/
|
||||
protected Destination resolveDefaultResponseDestination(Session session) throws JMSException {
|
||||
if (this.defaultResponseDestination instanceof Destination) {
|
||||
return (Destination) this.defaultResponseDestination;
|
||||
}
|
||||
if (this.defaultResponseDestination instanceof DestinationNameHolder) {
|
||||
DestinationNameHolder nameHolder = (DestinationNameHolder) this.defaultResponseDestination;
|
||||
return getDestinationResolver().resolveDestinationName(session, nameHolder.name, nameHolder.isTopic);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the given response message to the given destination.
|
||||
* @param response the JMS message to send
|
||||
* @param destination the JMS destination to send to
|
||||
* @param session the JMS session to operate on
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #postProcessProducer
|
||||
* @see javax.jms.Session#createProducer
|
||||
* @see javax.jms.MessageProducer#send
|
||||
*/
|
||||
protected void sendResponse(Session session, Destination destination, Message response) throws JMSException {
|
||||
MessageProducer producer = session.createProducer(destination);
|
||||
try {
|
||||
postProcessProducer(producer, response);
|
||||
producer.send(response);
|
||||
}
|
||||
finally {
|
||||
JmsUtils.closeMessageProducer(producer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the given message producer before using it to send the response.
|
||||
* <p>The default implementation is empty.
|
||||
* @param producer the JMS message producer that will be used to send the message
|
||||
* @param response the outgoing JMS message about to be sent
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
*/
|
||||
protected void postProcessProducer(MessageProducer producer, Message response) throws JMSException {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal class combining a destination name
|
||||
* and its target destination type (queue or topic).
|
||||
*/
|
||||
private static class DestinationNameHolder {
|
||||
|
||||
public final String name;
|
||||
|
||||
public final boolean isTopic;
|
||||
|
||||
public DestinationNameHolder(String name, boolean isTopic) {
|
||||
this.name = name;
|
||||
this.isTopic = isTopic;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener.adapter;
|
||||
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageProducer;
|
||||
import javax.jms.Queue;
|
||||
import javax.jms.QueueSender;
|
||||
import javax.jms.QueueSession;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.Topic;
|
||||
import javax.jms.TopicPublisher;
|
||||
import javax.jms.TopicSession;
|
||||
|
||||
import org.springframework.jms.support.JmsUtils;
|
||||
import org.springframework.jms.support.converter.SimpleMessageConverter102;
|
||||
|
||||
/**
|
||||
* A {@link MessageListenerAdapter} subclass for the JMS 1.0.2 specification,
|
||||
* not relying on JMS 1.1 methods like MessageListenerAdapter itself.
|
||||
*
|
||||
* <p>This class can be used for JMS 1.0.2 providers, offering the same facility
|
||||
* as MessageListenerAdapter does for JMS 1.1 providers.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Rick Evans
|
||||
* @since 2.0
|
||||
*/
|
||||
public class MessageListenerAdapter102 extends MessageListenerAdapter {
|
||||
|
||||
/**
|
||||
* Create a new instance of the {@link MessageListenerAdapter102} class
|
||||
* with the default settings.
|
||||
*/
|
||||
public MessageListenerAdapter102() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the {@link MessageListenerAdapter102} class
|
||||
* for the given delegate.
|
||||
* @param delegate the target object to delegate message listening to
|
||||
*/
|
||||
public MessageListenerAdapter102(Object delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the default implementations for the adapter's strategies:
|
||||
* SimpleMessageConverter102.
|
||||
* @see #setMessageConverter
|
||||
* @see org.springframework.jms.support.converter.SimpleMessageConverter102
|
||||
*/
|
||||
protected void initDefaultStrategies() {
|
||||
setMessageConverter(new SimpleMessageConverter102());
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the superclass method to use the JMS 1.0.2 API to send a response.
|
||||
* <p>Uses the JMS pub-sub API if the given destination is a topic,
|
||||
* else uses the JMS queue API.
|
||||
*/
|
||||
protected void sendResponse(Session session, Destination destination, Message response) throws JMSException {
|
||||
MessageProducer producer = null;
|
||||
try {
|
||||
if (destination instanceof Topic) {
|
||||
producer = ((TopicSession) session).createPublisher((Topic) destination);
|
||||
postProcessProducer(producer, response);
|
||||
((TopicPublisher) producer).publish(response);
|
||||
}
|
||||
else {
|
||||
producer = ((QueueSession) session).createSender((Queue) destination);
|
||||
postProcessProducer(producer, response);
|
||||
((QueueSender) producer).send(response);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
JmsUtils.closeMessageProducer(producer);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
Message listener adapter mechanism that delegates to target listener
|
||||
methods, converting messages to appropriate message content types
|
||||
(such as String or byte array) that get passed into listener methods.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener.endpoint;
|
||||
|
||||
import javax.jms.Session;
|
||||
|
||||
/**
|
||||
* Common configuration object for activating a JMS message endpoint.
|
||||
* Gets converted into a provider-specific JCA 1.5 ActivationSpec
|
||||
* object for activating the endpoint.
|
||||
*
|
||||
* <p>Typically used in combination with {@link JmsMessageEndpointManager},
|
||||
* but not tied to it.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
* @see JmsActivationSpecFactory
|
||||
* @see JmsMessageEndpointManager#setActivationSpecConfig
|
||||
* @see javax.resource.spi.ResourceAdapter#endpointActivation
|
||||
*/
|
||||
public class JmsActivationSpecConfig {
|
||||
|
||||
private String destinationName;
|
||||
|
||||
private boolean pubSubDomain = false;
|
||||
|
||||
private boolean subscriptionDurable = false;
|
||||
|
||||
private String durableSubscriptionName;
|
||||
|
||||
private String clientId;
|
||||
|
||||
private String messageSelector;
|
||||
|
||||
private int acknowledgeMode = Session.AUTO_ACKNOWLEDGE;
|
||||
|
||||
private int maxConcurrency = -1;
|
||||
|
||||
private int prefetchSize = -1;
|
||||
|
||||
|
||||
public void setDestinationName(String destinationName) {
|
||||
this.destinationName = destinationName;
|
||||
}
|
||||
|
||||
public String getDestinationName() {
|
||||
return this.destinationName;
|
||||
}
|
||||
|
||||
public void setPubSubDomain(boolean pubSubDomain) {
|
||||
this.pubSubDomain = pubSubDomain;
|
||||
}
|
||||
|
||||
public boolean isPubSubDomain() {
|
||||
return this.pubSubDomain;
|
||||
}
|
||||
|
||||
public void setSubscriptionDurable(boolean subscriptionDurable) {
|
||||
this.subscriptionDurable = subscriptionDurable;
|
||||
}
|
||||
|
||||
public boolean isSubscriptionDurable() {
|
||||
return this.subscriptionDurable;
|
||||
}
|
||||
|
||||
public void setDurableSubscriptionName(String durableSubscriptionName) {
|
||||
this.durableSubscriptionName = durableSubscriptionName;
|
||||
}
|
||||
|
||||
public String getDurableSubscriptionName() {
|
||||
return this.durableSubscriptionName;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
public void setMessageSelector(String messageSelector) {
|
||||
this.messageSelector = messageSelector;
|
||||
}
|
||||
|
||||
public String getMessageSelector() {
|
||||
return this.messageSelector;
|
||||
}
|
||||
|
||||
public void setAcknowledgeMode(int acknowledgeMode) {
|
||||
this.acknowledgeMode = acknowledgeMode;
|
||||
}
|
||||
|
||||
public int getAcknowledgeMode() {
|
||||
return this.acknowledgeMode;
|
||||
}
|
||||
|
||||
public void setMaxConcurrency(int maxConcurrency) {
|
||||
this.maxConcurrency = maxConcurrency;
|
||||
}
|
||||
|
||||
public int getMaxConcurrency() {
|
||||
return this.maxConcurrency;
|
||||
}
|
||||
|
||||
public void setPrefetchSize(int prefetchSize) {
|
||||
this.prefetchSize = prefetchSize;
|
||||
}
|
||||
|
||||
public int getPrefetchSize() {
|
||||
return this.prefetchSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener.endpoint;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Queue;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.Topic;
|
||||
import javax.resource.spi.ActivationSpec;
|
||||
import javax.resource.spi.ResourceAdapter;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.PropertyAccessorFactory;
|
||||
import org.springframework.jms.support.destination.DestinationResolutionException;
|
||||
import org.springframework.jms.support.destination.DestinationResolver;
|
||||
|
||||
/**
|
||||
* Standard implementation of the {@link JmsActivationSpecFactory} interface.
|
||||
* Supports the standard JMS properties as defined by the JMS 1.5 specification
|
||||
* (Appendix B); ignores Spring's "maxConcurrency" and "prefetchSize" settings.
|
||||
*
|
||||
* <p>The 'activationSpecClass' property is required, explicitly defining
|
||||
* the fully-qualified class name of the provider's ActivationSpec class
|
||||
* (e.g. "org.apache.activemq.ra.ActiveMQActivationSpec").
|
||||
*
|
||||
* <p>Check out {@link DefaultJmsActivationSpecFactory} for an extended variant
|
||||
* of this class, supporting some further default conventions beyond the plain
|
||||
* JMS 1.5 specification.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
* @see #setActivationSpecClass
|
||||
* @see DefaultJmsActivationSpecFactory
|
||||
*/
|
||||
public class StandardJmsActivationSpecFactory implements JmsActivationSpecFactory {
|
||||
|
||||
private Class activationSpecClass;
|
||||
|
||||
private Properties defaultProperties;
|
||||
|
||||
private DestinationResolver destinationResolver;
|
||||
|
||||
|
||||
/**
|
||||
* Specify the fully-qualified ActivationSpec class name for the target
|
||||
* provider (e.g. "org.apache.activemq.ra.ActiveMQActivationSpec").
|
||||
*/
|
||||
public void setActivationSpecClass(Class activationSpecClass) {
|
||||
this.activationSpecClass = activationSpecClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify custom default properties, with String keys and String values.
|
||||
* <p>Applied to each ActivationSpec object before it gets populated with
|
||||
* listener-specific settings. Allows for configuring vendor-specific properties
|
||||
* beyond the Spring-defined settings in {@link JmsActivationSpecConfig}.
|
||||
*/
|
||||
public void setDefaultProperties(Properties defaultProperties) {
|
||||
this.defaultProperties = defaultProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DestinationResolver to use for resolving destination names
|
||||
* into the JCA 1.5 ActivationSpec "destination" property.
|
||||
* <p>If not specified, destination names will simply be passed in as Strings.
|
||||
* If specified, destination names will be resolved into Destination objects first.
|
||||
* <p>Note that a DestinationResolver for use with this factory must be
|
||||
* able to work <i>without</i> an active JMS Session: e.g.
|
||||
* {@link org.springframework.jms.support.destination.JndiDestinationResolver}
|
||||
* or {@link org.springframework.jms.support.destination.BeanFactoryDestinationResolver}
|
||||
* but not {@link org.springframework.jms.support.destination.DynamicDestinationResolver}.
|
||||
*/
|
||||
public void setDestinationResolver(DestinationResolver destinationResolver) {
|
||||
this.destinationResolver = destinationResolver;
|
||||
}
|
||||
|
||||
|
||||
public ActivationSpec createActivationSpec(ResourceAdapter adapter, JmsActivationSpecConfig config) {
|
||||
Class activationSpecClassToUse = this.activationSpecClass;
|
||||
if (activationSpecClassToUse == null) {
|
||||
activationSpecClassToUse = determineActivationSpecClass(adapter);
|
||||
if (activationSpecClassToUse == null) {
|
||||
throw new IllegalStateException("Property 'activationSpecClass' is required");
|
||||
}
|
||||
}
|
||||
|
||||
ActivationSpec spec = (ActivationSpec) BeanUtils.instantiateClass(activationSpecClassToUse);
|
||||
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(spec);
|
||||
if (this.defaultProperties != null) {
|
||||
bw.setPropertyValues(this.defaultProperties);
|
||||
}
|
||||
populateActivationSpecProperties(bw, config);
|
||||
return spec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the ActivationSpec class for the given ResourceAdapter,
|
||||
* if possible. Called if no 'activationSpecClass' has been set explicitly
|
||||
* @param adapter the ResourceAdapter to check
|
||||
* @return the corresponding ActivationSpec class, or <code>null</code>
|
||||
* if not determinable
|
||||
* @see #setActivationSpecClass
|
||||
*/
|
||||
protected Class determineActivationSpecClass(ResourceAdapter adapter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the given ApplicationSpec object with the settings
|
||||
* defined in the given configuration object.
|
||||
* <p>This implementation applies all standard JMS settings, but ignores
|
||||
* "maxConcurrency" and "prefetchSize" - not supported in standard JCA 1.5.
|
||||
* @param bw the BeanWrapper wrapping the ActivationSpec object
|
||||
* @param config the configured object holding common JMS settings
|
||||
*/
|
||||
protected void populateActivationSpecProperties(BeanWrapper bw, JmsActivationSpecConfig config) {
|
||||
String destinationName = config.getDestinationName();
|
||||
boolean pubSubDomain = config.isPubSubDomain();
|
||||
Object destination = destinationName;
|
||||
if (this.destinationResolver != null) {
|
||||
try {
|
||||
destination = this.destinationResolver.resolveDestinationName(null, destinationName, pubSubDomain);
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
throw new DestinationResolutionException("Cannot resolve destination name [" + destinationName + "]", ex);
|
||||
}
|
||||
}
|
||||
bw.setPropertyValue("destination", destination);
|
||||
bw.setPropertyValue("destinationType", pubSubDomain ? Topic.class.getName() : Queue.class.getName());
|
||||
|
||||
if (bw.isWritableProperty("subscriptionDurability")) {
|
||||
bw.setPropertyValue("subscriptionDurability", config.isSubscriptionDurable() ? "Durable" : "NonDurable");
|
||||
}
|
||||
else if (config.isSubscriptionDurable()) {
|
||||
// Standard JCA 1.5 "subscriptionDurability" apparently not supported...
|
||||
throw new IllegalArgumentException(
|
||||
"Durable subscriptions not supported by underlying provider: " + this.activationSpecClass.getName());
|
||||
}
|
||||
if (config.getDurableSubscriptionName() != null) {
|
||||
bw.setPropertyValue("subscriptionName", config.getDurableSubscriptionName());
|
||||
}
|
||||
if (config.getClientId() != null) {
|
||||
bw.setPropertyValue("clientId", config.getClientId());
|
||||
}
|
||||
|
||||
if (config.getMessageSelector() != null) {
|
||||
bw.setPropertyValue("messageSelector", config.getMessageSelector());
|
||||
}
|
||||
|
||||
applyAcknowledgeMode(bw, config.getAcknowledgeMode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified acknowledge mode to the ActivationSpec object.
|
||||
* <p>This implementation applies the standard JCA 1.5 acknowledge modes
|
||||
* "Auto-acknowledge" and "Dups-ok-acknowledge". It throws an exception in
|
||||
* case of <code>CLIENT_ACKNOWLEDGE</code> or <code>SESSION_TRANSACTED</code>
|
||||
* having been requested.
|
||||
* @param bw the BeanWrapper wrapping the ActivationSpec object
|
||||
* @param ackMode the configured acknowledge mode
|
||||
* (according to the constants in {@link javax.jms.Session}
|
||||
* @see javax.jms.Session#AUTO_ACKNOWLEDGE
|
||||
* @see javax.jms.Session#DUPS_OK_ACKNOWLEDGE
|
||||
* @see javax.jms.Session#CLIENT_ACKNOWLEDGE
|
||||
* @see javax.jms.Session#SESSION_TRANSACTED
|
||||
*/
|
||||
protected void applyAcknowledgeMode(BeanWrapper bw, int ackMode) {
|
||||
if (ackMode == Session.SESSION_TRANSACTED) {
|
||||
throw new IllegalArgumentException("No support for SESSION_TRANSACTED: Only \"Auto-acknowledge\" " +
|
||||
"and \"Dups-ok-acknowledge\" supported in standard JCA 1.5");
|
||||
}
|
||||
else if (ackMode == Session.CLIENT_ACKNOWLEDGE) {
|
||||
throw new IllegalArgumentException("No support for CLIENT_ACKNOWLEDGE: Only \"Auto-acknowledge\" " +
|
||||
"and \"Dups-ok-acknowledge\" supported in standard JCA 1.5");
|
||||
}
|
||||
else if (bw.isWritableProperty("acknowledgeMode")) {
|
||||
bw.setPropertyValue("acknowledgeMode",
|
||||
ackMode == Session.DUPS_OK_ACKNOWLEDGE ? "Dups-ok-acknowledge" : "Auto-acknowledge");
|
||||
}
|
||||
else if (ackMode == Session.DUPS_OK_ACKNOWLEDGE) {
|
||||
// Standard JCA 1.5 "acknowledgeMode" apparently not supported (e.g. WebSphere MQ 6.0.2.1)
|
||||
throw new IllegalArgumentException(
|
||||
"Dups-ok-acknowledge not supported by underlying provider: " + this.activationSpecClass.getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
This package provides JCA-based endpoint management for JMS message listeners.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
This package contains the base message listener container facility.
|
||||
It also offers the DefaultMessageListenerContainer and SimpleMessageListenerContainer
|
||||
implementations, based on the plain JMS client API.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener.serversession;
|
||||
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.ServerSession;
|
||||
import javax.jms.Session;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.jms.support.JmsUtils;
|
||||
import org.springframework.scheduling.timer.TimerTaskExecutor;
|
||||
|
||||
/**
|
||||
* Abstract base class for ServerSessionFactory implementations
|
||||
* that pool ServerSessionFactory instances.
|
||||
*
|
||||
* <p>Provides a factory method that creates a poolable ServerSession
|
||||
* (to be added as new instance to a pool), a callback method invoked
|
||||
* when a ServerSession finished an execution of its listener (to return
|
||||
* an instance to the pool), and a method to destroy a ServerSession instance
|
||||
* (after removing an instance from the pool).
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
|
||||
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
|
||||
* @see org.springframework.jms.listener.serversession.CommonsPoolServerSessionFactory
|
||||
*/
|
||||
public abstract class AbstractPoolingServerSessionFactory implements ServerSessionFactory {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
private int maxSize;
|
||||
|
||||
|
||||
/**
|
||||
* Specify the TaskExecutor to use for executing ServerSessions
|
||||
* (and consequently, the underlying MessageListener).
|
||||
* <p>Default is a {@link org.springframework.scheduling.timer.TimerTaskExecutor}
|
||||
* for each pooled ServerSession, using one Thread per pooled JMS Session.
|
||||
* Alternatives are a shared TimerTaskExecutor, sharing a single Thread
|
||||
* for the execution of all ServerSessions, or a TaskExecutor
|
||||
* implementation backed by a thread pool.
|
||||
*/
|
||||
public void setTaskExecutor(TaskExecutor taskExecutor) {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the TaskExecutor to use for executing ServerSessions.
|
||||
*/
|
||||
protected TaskExecutor getTaskExecutor() {
|
||||
return this.taskExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum size of the pool.
|
||||
*/
|
||||
public void setMaxSize(int maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum size of the pool.
|
||||
*/
|
||||
public int getMaxSize() {
|
||||
return this.maxSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new poolable ServerSession.
|
||||
* To be called when a new instance should be added to the pool.
|
||||
* @param sessionManager the listener session manager to create the
|
||||
* poolable ServerSession for
|
||||
* @return the new poolable ServerSession
|
||||
* @throws JMSException if creation failed
|
||||
*/
|
||||
protected final ServerSession createServerSession(ListenerSessionManager sessionManager) throws JMSException {
|
||||
return new PoolableServerSession(sessionManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the given poolable ServerSession.
|
||||
* To be called when an instance got removed from the pool.
|
||||
* @param serverSession the poolable ServerSession to destroy
|
||||
*/
|
||||
protected final void destroyServerSession(ServerSession serverSession) {
|
||||
if (serverSession != null) {
|
||||
((PoolableServerSession) serverSession).close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Template method called by a ServerSession if it finished
|
||||
* execution of its listener and is ready to go back into the pool.
|
||||
* <p>Subclasses should implement the actual returning of the instance
|
||||
* to the pool.
|
||||
* @param serverSession the ServerSession that finished its execution
|
||||
* @param sessionManager the session manager that the ServerSession belongs to
|
||||
*/
|
||||
protected abstract void serverSessionFinished(
|
||||
ServerSession serverSession, ListenerSessionManager sessionManager);
|
||||
|
||||
|
||||
/**
|
||||
* ServerSession implementation designed to be pooled.
|
||||
* Creates a new JMS Session on instantiation, reuses it
|
||||
* for all executions, and closes it on <code>close</code>.
|
||||
* <p>Creates a TimerTaskExecutor (using a single Thread) per
|
||||
* ServerSession, unless given a specific TaskExecutor to use.
|
||||
*/
|
||||
private class PoolableServerSession implements ServerSession {
|
||||
|
||||
private final ListenerSessionManager sessionManager;
|
||||
|
||||
private final Session session;
|
||||
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
private TimerTaskExecutor internalExecutor;
|
||||
|
||||
public PoolableServerSession(final ListenerSessionManager sessionManager) throws JMSException {
|
||||
this.sessionManager = sessionManager;
|
||||
this.session = sessionManager.createListenerSession();
|
||||
this.taskExecutor = getTaskExecutor();
|
||||
if (this.taskExecutor == null) {
|
||||
this.internalExecutor = new TimerTaskExecutor();
|
||||
this.internalExecutor.afterPropertiesSet();
|
||||
this.taskExecutor = this.internalExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
public Session getSession() {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.taskExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
sessionManager.executeListenerSession(session);
|
||||
}
|
||||
finally {
|
||||
serverSessionFinished(PoolableServerSession.this, sessionManager);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (this.internalExecutor != null) {
|
||||
this.internalExecutor.destroy();
|
||||
}
|
||||
JmsUtils.closeSession(this.session);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener.serversession;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.ServerSession;
|
||||
|
||||
import org.apache.commons.pool.ObjectPool;
|
||||
import org.apache.commons.pool.PoolableObjectFactory;
|
||||
import org.apache.commons.pool.impl.GenericObjectPool;
|
||||
|
||||
/**
|
||||
* {@link ServerSessionFactory} implementation that holds JMS
|
||||
* <code>ServerSessions</code> in a configurable Jakarta Commons Pool.
|
||||
*
|
||||
* <p>By default, an instance of <code>GenericObjectPool</code> is created.
|
||||
* Subclasses may change the type of <code>ObjectPool</code> used by
|
||||
* overriding the <code>createObjectPool</code> method.
|
||||
*
|
||||
* <p>Provides many configuration properties mirroring those of the Commons Pool
|
||||
* <code>GenericObjectPool</code> class; these properties are passed to the
|
||||
* <code>GenericObjectPool</code> during construction. If creating a subclass of this
|
||||
* class to change the <code>ObjectPool</code> implementation type, pass in the values
|
||||
* of configuration properties that are relevant to your chosen implementation.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
|
||||
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
|
||||
* @see GenericObjectPool
|
||||
* @see #createObjectPool
|
||||
* @see #setMaxSize
|
||||
* @see #setMaxIdle
|
||||
* @see #setMinIdle
|
||||
* @see #setMaxWait
|
||||
*/
|
||||
public class CommonsPoolServerSessionFactory extends AbstractPoolingServerSessionFactory {
|
||||
|
||||
private int maxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;
|
||||
|
||||
private int minIdle = GenericObjectPool.DEFAULT_MIN_IDLE;
|
||||
|
||||
private long maxWait = GenericObjectPool.DEFAULT_MAX_WAIT;
|
||||
|
||||
private long timeBetweenEvictionRunsMillis = GenericObjectPool.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
|
||||
|
||||
private long minEvictableIdleTimeMillis = GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
|
||||
|
||||
private final Map serverSessionPools = Collections.synchronizedMap(new HashMap(1));
|
||||
|
||||
|
||||
/**
|
||||
* Create a CommonsPoolServerSessionFactory with default settings.
|
||||
* Default maximum size of the pool is 8.
|
||||
* @see #setMaxSize
|
||||
* @see GenericObjectPool#setMaxActive
|
||||
*/
|
||||
public CommonsPoolServerSessionFactory() {
|
||||
setMaxSize(GenericObjectPool.DEFAULT_MAX_ACTIVE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the maximum number of idle ServerSessions in the pool.
|
||||
* Default is 8.
|
||||
* @see GenericObjectPool#setMaxIdle
|
||||
*/
|
||||
public void setMaxIdle(int maxIdle) {
|
||||
this.maxIdle = maxIdle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum number of idle ServerSessions in the pool.
|
||||
*/
|
||||
public int getMaxIdle() {
|
||||
return this.maxIdle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum number of idle ServerSessions in the pool.
|
||||
* Default is 0.
|
||||
* @see GenericObjectPool#setMinIdle
|
||||
*/
|
||||
public void setMinIdle(int minIdle) {
|
||||
this.minIdle = minIdle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the minimum number of idle ServerSessions in the pool.
|
||||
*/
|
||||
public int getMinIdle() {
|
||||
return this.minIdle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum waiting time for fetching an ServerSession from the pool.
|
||||
* Default is -1, waiting forever.
|
||||
* @see GenericObjectPool#setMaxWait
|
||||
*/
|
||||
public void setMaxWait(long maxWait) {
|
||||
this.maxWait = maxWait;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum waiting time for fetching a ServerSession from the pool.
|
||||
*/
|
||||
public long getMaxWait() {
|
||||
return this.maxWait;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time between eviction runs that check idle ServerSessions
|
||||
* whether they have been idle for too long or have become invalid.
|
||||
* Default is -1, not performing any eviction.
|
||||
* @see GenericObjectPool#setTimeBetweenEvictionRunsMillis
|
||||
*/
|
||||
public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
|
||||
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the time between eviction runs that check idle ServerSessions.
|
||||
*/
|
||||
public long getTimeBetweenEvictionRunsMillis() {
|
||||
return this.timeBetweenEvictionRunsMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum time that an idle ServerSession can sit in the pool
|
||||
* before it becomes subject to eviction. Default is 1800000 (30 minutes).
|
||||
* <p>Note that eviction runs need to be performed to take this
|
||||
* setting into effect.
|
||||
* @see #setTimeBetweenEvictionRunsMillis
|
||||
* @see GenericObjectPool#setMinEvictableIdleTimeMillis
|
||||
*/
|
||||
public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
|
||||
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the minimum time that an idle ServerSession can sit in the pool.
|
||||
*/
|
||||
public long getMinEvictableIdleTimeMillis() {
|
||||
return this.minEvictableIdleTimeMillis;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a ServerSession from the pool, creating a new pool for the given
|
||||
* session manager if necessary.
|
||||
* @see #createObjectPool
|
||||
*/
|
||||
public ServerSession getServerSession(ListenerSessionManager sessionManager) throws JMSException {
|
||||
ObjectPool pool = null;
|
||||
synchronized (this.serverSessionPools) {
|
||||
pool = (ObjectPool) this.serverSessionPools.get(sessionManager);
|
||||
if (pool == null) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Creating Commons ServerSession pool for: " + sessionManager);
|
||||
}
|
||||
pool = createObjectPool(sessionManager);
|
||||
this.serverSessionPools.put(sessionManager, pool);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return (ServerSession) pool.borrowObject();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
JMSException jmsEx = new JMSException("Failed to borrow ServerSession from pool");
|
||||
jmsEx.setLinkedException(ex);
|
||||
throw jmsEx;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can override this if they want to return a specific Commons pool.
|
||||
* They should apply any configuration properties to the pool here.
|
||||
* <p>Default is a GenericObjectPool instance with the given pool size.
|
||||
* @param sessionManager the session manager to use for
|
||||
* creating and executing new listener sessions
|
||||
* @return an empty Commons <code>ObjectPool</code>.
|
||||
* @see org.apache.commons.pool.impl.GenericObjectPool
|
||||
* @see #setMaxSize
|
||||
*/
|
||||
protected ObjectPool createObjectPool(ListenerSessionManager sessionManager) {
|
||||
GenericObjectPool pool = new GenericObjectPool(createPoolableObjectFactory(sessionManager));
|
||||
pool.setMaxActive(getMaxSize());
|
||||
pool.setMaxIdle(getMaxIdle());
|
||||
pool.setMinIdle(getMinIdle());
|
||||
pool.setMaxWait(getMaxWait());
|
||||
pool.setTimeBetweenEvictionRunsMillis(getTimeBetweenEvictionRunsMillis());
|
||||
pool.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis());
|
||||
return pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Commons PoolableObjectFactory adapter for the given session manager.
|
||||
* Calls <code>createServerSession</code> and <code>destroyServerSession</code>
|
||||
* as defined by the AbstractPoolingServerSessionFactory class.
|
||||
* @param sessionManager the session manager to use for
|
||||
* creating and executing new listener sessions
|
||||
* @return the Commons PoolableObjectFactory
|
||||
* @see #createServerSession
|
||||
* @see #destroyServerSession
|
||||
*/
|
||||
protected PoolableObjectFactory createPoolableObjectFactory(final ListenerSessionManager sessionManager) {
|
||||
return new PoolableObjectFactory() {
|
||||
public Object makeObject() throws JMSException {
|
||||
return createServerSession(sessionManager);
|
||||
}
|
||||
public void destroyObject(Object obj) {
|
||||
destroyServerSession((ServerSession) obj);
|
||||
}
|
||||
public boolean validateObject(Object obj) {
|
||||
return true;
|
||||
}
|
||||
public void activateObject(Object obj) {
|
||||
}
|
||||
public void passivateObject(Object obj) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given ServerSession, which just finished an execution
|
||||
* of its listener, back to the pool.
|
||||
*/
|
||||
protected void serverSessionFinished(ServerSession serverSession, ListenerSessionManager sessionManager) {
|
||||
ObjectPool pool = (ObjectPool) this.serverSessionPools.get(sessionManager);
|
||||
if (pool == null) {
|
||||
throw new IllegalStateException("No pool found for session manager [" + sessionManager + "]");
|
||||
}
|
||||
try {
|
||||
pool.returnObject(serverSession);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.error("Failed to return ServerSession to pool", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes and removes the pool for the given session manager.
|
||||
*/
|
||||
public void close(ListenerSessionManager sessionManager) {
|
||||
ObjectPool pool = (ObjectPool) this.serverSessionPools.remove(sessionManager);
|
||||
if (pool != null) {
|
||||
try {
|
||||
pool.close();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.error("Failed to close ServerSession pool", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener.serversession;
|
||||
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Session;
|
||||
|
||||
/**
|
||||
* SPI interface for creating and executing JMS Sessions,
|
||||
* pre-populated with a specific MessageListener.
|
||||
* Implemented by ServerSessionMessageListenerContainer,
|
||||
* accessed by ServerSessionFactory implementations.
|
||||
*
|
||||
* <p>Effectively, an instance that implements this interface
|
||||
* represents a message listener container for a specific
|
||||
* listener and destination.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
|
||||
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
|
||||
* @see ServerSessionFactory
|
||||
* @see ServerSessionMessageListenerContainer
|
||||
*/
|
||||
public interface ListenerSessionManager {
|
||||
|
||||
/**
|
||||
* Create a new JMS Session, pre-populated with this manager's
|
||||
* MessageListener.
|
||||
* @return the new JMS Session
|
||||
* @throws JMSException if Session creation failed
|
||||
* @see javax.jms.Session#setMessageListener(javax.jms.MessageListener)
|
||||
*/
|
||||
Session createListenerSession() throws JMSException;
|
||||
|
||||
/**
|
||||
* Execute the given JMS Session, triggering its MessageListener
|
||||
* with pre-loaded messages.
|
||||
* @param session the JMS Session to invoke
|
||||
* @see javax.jms.Session#run()
|
||||
*/
|
||||
void executeListenerSession(Session session);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener.serversession;
|
||||
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.ServerSession;
|
||||
|
||||
import org.springframework.jms.listener.serversession.ListenerSessionManager;
|
||||
|
||||
/**
|
||||
* SPI interface to be implemented by components that manage
|
||||
* JMS ServerSessions. Usually, but not necessarily, an implementation
|
||||
* of this interface will hold a pool of ServerSessions.
|
||||
*
|
||||
* <p>The passed-in ListenerSessionManager has to be used for creating
|
||||
* and executing JMS Sessions. This session manager is responsible for
|
||||
* registering a MessageListener with all Sessions that it creates.
|
||||
* Consequently, the ServerSessionFactory implementation has to
|
||||
* concentrate on the actual lifecycle (e.g. pooling) of JMS Sessions,
|
||||
* but is not concerned about Session creation or execution.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
|
||||
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
|
||||
* @see org.springframework.jms.listener.serversession.ListenerSessionManager
|
||||
* @see org.springframework.jms.listener.serversession.ServerSessionMessageListenerContainer
|
||||
*/
|
||||
public interface ServerSessionFactory {
|
||||
|
||||
/**
|
||||
* Retrieve a JMS ServerSession for the given session manager.
|
||||
* @param sessionManager the session manager to use for
|
||||
* creating and executing new listener sessions
|
||||
* (implicitly indicating the target listener to invoke)
|
||||
* @return the JMS ServerSession
|
||||
* @throws JMSException if retrieval failed
|
||||
*/
|
||||
ServerSession getServerSession(ListenerSessionManager sessionManager) throws JMSException;
|
||||
|
||||
/**
|
||||
* Close all ServerSessions for the given session manager.
|
||||
* @param sessionManager the session manager used for
|
||||
* creating and executing new listener sessions
|
||||
* (implicitly indicating the target listener)
|
||||
*/
|
||||
void close(ListenerSessionManager sessionManager);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener.serversession;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionConsumer;
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageListener;
|
||||
import javax.jms.ServerSession;
|
||||
import javax.jms.ServerSessionPool;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.Topic;
|
||||
|
||||
import org.springframework.jms.listener.AbstractMessageListenerContainer;
|
||||
import org.springframework.jms.support.JmsUtils;
|
||||
|
||||
/**
|
||||
* Message listener container that builds on the {@link javax.jms.ServerSessionPool}
|
||||
* SPI, creating JMS ServerSession instances through a pluggable
|
||||
* {@link ServerSessionFactory}.
|
||||
*
|
||||
* <p><b>NOTE:</b> This class requires a JMS 1.1+ provider, because it builds on the
|
||||
* domain-independent API. <b>Use the {@link ServerSessionMessageListenerContainer102}
|
||||
* subclass for a JMS 1.0.2 provider, e.g. when running on a J2EE 1.3 server.</b>
|
||||
*
|
||||
* <p>The default ServerSessionFactory is a {@link SimpleServerSessionFactory},
|
||||
* which will create a new ServerSession for each listener execution.
|
||||
* Consider specifying a {@link CommonsPoolServerSessionFactory} to reuse JMS
|
||||
* Sessions and/or to limit the number of concurrent ServerSession executions.
|
||||
*
|
||||
* <p>See the {@link AbstractMessageListenerContainer} javadoc for details
|
||||
* on acknowledge modes and other configuration options.
|
||||
*
|
||||
* <p><b>This is an 'advanced' (special-purpose) message listener container.</b>
|
||||
* For a simpler message listener container, in particular when using
|
||||
* a JMS provider without ServerSessionPool support, consider using
|
||||
* {@link org.springframework.jms.listener.SimpleMessageListenerContainer}.
|
||||
* For a general one-stop shop that is nevertheless very flexible, consider
|
||||
* {@link org.springframework.jms.listener.DefaultMessageListenerContainer}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
|
||||
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
|
||||
* @see org.springframework.jms.listener.SimpleMessageListenerContainer
|
||||
* @see org.springframework.jms.listener.endpoint.JmsMessageEndpointManager
|
||||
*/
|
||||
public class ServerSessionMessageListenerContainer extends AbstractMessageListenerContainer
|
||||
implements ListenerSessionManager {
|
||||
|
||||
private ServerSessionFactory serverSessionFactory = new SimpleServerSessionFactory();
|
||||
|
||||
private int maxMessagesPerTask = 1;
|
||||
|
||||
private ConnectionConsumer consumer;
|
||||
|
||||
|
||||
/**
|
||||
* Set the Spring ServerSessionFactory to use.
|
||||
* <p>Default is a plain SimpleServerSessionFactory.
|
||||
* Consider using a CommonsPoolServerSessionFactory to reuse JMS Sessions
|
||||
* and/or to limit the number of concurrent ServerSession executions.
|
||||
* @see SimpleServerSessionFactory
|
||||
* @see CommonsPoolServerSessionFactory
|
||||
*/
|
||||
public void setServerSessionFactory(ServerSessionFactory serverSessionFactory) {
|
||||
this.serverSessionFactory =
|
||||
(serverSessionFactory != null ? serverSessionFactory : new SimpleServerSessionFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Spring ServerSessionFactory to use.
|
||||
*/
|
||||
protected ServerSessionFactory getServerSessionFactory() {
|
||||
return this.serverSessionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of messages to load into a JMS Session.
|
||||
* Default is 1.
|
||||
* <p>See the corresponding JMS <code>createConnectionConsumer</code>
|
||||
* argument for details.
|
||||
* @see javax.jms.Connection#createConnectionConsumer
|
||||
*/
|
||||
public void setMaxMessagesPerTask(int maxMessagesPerTask) {
|
||||
this.maxMessagesPerTask = maxMessagesPerTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum number of messages to load into a JMS Session.
|
||||
*/
|
||||
protected int getMaxMessagesPerTask() {
|
||||
return this.maxMessagesPerTask;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Implementation of AbstractMessageListenerContainer's template methods
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Always use a shared JMS Connection.
|
||||
*/
|
||||
protected final boolean sharedConnectionEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JMS ServerSessionPool for the specified listener and registers
|
||||
* it with a JMS ConnectionConsumer for the specified destination.
|
||||
* @see #createServerSessionPool
|
||||
* @see #createConsumer
|
||||
*/
|
||||
protected void doInitialize() throws JMSException {
|
||||
establishSharedConnection();
|
||||
|
||||
Connection con = getSharedConnection();
|
||||
Destination destination = getDestination();
|
||||
if (destination == null) {
|
||||
Session session = createSession(con);
|
||||
try {
|
||||
destination = resolveDestinationName(session, getDestinationName());
|
||||
}
|
||||
finally {
|
||||
JmsUtils.closeSession(session);
|
||||
}
|
||||
}
|
||||
ServerSessionPool pool = createServerSessionPool();
|
||||
this.consumer = createConsumer(con, destination, pool);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JMS ServerSessionPool for the specified message listener,
|
||||
* via this container's ServerSessionFactory.
|
||||
* <p>This message listener container implements the ListenerSessionManager
|
||||
* interface, hence can be passed to the ServerSessionFactory itself.
|
||||
* @return the ServerSessionPool
|
||||
* @throws JMSException if creation of the ServerSessionPool failed
|
||||
* @see #setServerSessionFactory
|
||||
* @see ServerSessionFactory#getServerSession(ListenerSessionManager)
|
||||
*/
|
||||
protected ServerSessionPool createServerSessionPool() throws JMSException {
|
||||
return new ServerSessionPool() {
|
||||
public ServerSession getServerSession() throws JMSException {
|
||||
logger.debug("JMS ConnectionConsumer requests ServerSession");
|
||||
return getServerSessionFactory().getServerSession(ServerSessionMessageListenerContainer.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JMS ConnectionConsumer used by this message listener container.
|
||||
* Available after initialization.
|
||||
*/
|
||||
protected final ConnectionConsumer getConsumer() {
|
||||
return this.consumer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the JMS ServerSessionPool for the specified message listener,
|
||||
* via this container's ServerSessionFactory, and subsequently also
|
||||
* this container's JMS ConnectionConsumer.
|
||||
* <p>This message listener container implements the ListenerSessionManager
|
||||
* interface, hence can be passed to the ServerSessionFactory itself.
|
||||
* @see #setServerSessionFactory
|
||||
* @see ServerSessionFactory#getServerSession(ListenerSessionManager)
|
||||
*/
|
||||
protected void doShutdown() throws JMSException {
|
||||
logger.debug("Closing ServerSessionFactory");
|
||||
getServerSessionFactory().close(this);
|
||||
logger.debug("Closing JMS ConnectionConsumer");
|
||||
this.consumer.close();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Implementation of the ListenerSessionManager interface
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create a JMS Session with the specified listener registered.
|
||||
* Listener execution is delegated to the <code>executeListener</code> method.
|
||||
* <p>Default implementation simply calls <code>setMessageListener</code>
|
||||
* on a newly created JMS Session, according to the JMS specification's
|
||||
* ServerSessionPool section.
|
||||
* @return the JMS Session
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
* @see #executeListener
|
||||
*/
|
||||
public Session createListenerSession() throws JMSException {
|
||||
final Session session = createSession(getSharedConnection());
|
||||
|
||||
session.setMessageListener(new MessageListener() {
|
||||
public void onMessage(Message message) {
|
||||
executeListener(session, message);
|
||||
}
|
||||
});
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given JMS Session, triggering invocation
|
||||
* of its listener.
|
||||
* <p>Default implementation simply calls <code>run()</code>
|
||||
* on the JMS Session, according to the JMS specification's
|
||||
* ServerSessionPool section.
|
||||
* @param session the JMS Session to execute
|
||||
*/
|
||||
public void executeListenerSession(Session session) {
|
||||
session.run();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// JMS 1.1 factory methods, potentially overridden for JMS 1.0.2
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create a JMS ConnectionConsumer for the given Connection.
|
||||
* <p>This implementation uses JMS 1.1 API.
|
||||
* @param con the JMS Connection to create a Session for
|
||||
* @param destination the JMS Destination to listen to
|
||||
* @param pool the ServerSessionpool to use
|
||||
* @return the new JMS Session
|
||||
* @throws JMSException if thrown by JMS API methods
|
||||
*/
|
||||
protected ConnectionConsumer createConsumer(Connection con, Destination destination, ServerSessionPool pool)
|
||||
throws JMSException {
|
||||
|
||||
if (isSubscriptionDurable() && destination instanceof Topic) {
|
||||
return con.createDurableConnectionConsumer(
|
||||
(Topic) destination, getDurableSubscriptionName(), getMessageSelector(), pool, getMaxMessagesPerTask());
|
||||
}
|
||||
else {
|
||||
return con.createConnectionConsumer(destination, getMessageSelector(), pool, getMaxMessagesPerTask());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener.serversession;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionConsumer;
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Queue;
|
||||
import javax.jms.QueueConnection;
|
||||
import javax.jms.QueueConnectionFactory;
|
||||
import javax.jms.ServerSessionPool;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.Topic;
|
||||
import javax.jms.TopicConnection;
|
||||
import javax.jms.TopicConnectionFactory;
|
||||
|
||||
/**
|
||||
* A subclass of {@link ServerSessionMessageListenerContainer} for the JMS 1.0.2 specification,
|
||||
* not relying on JMS 1.1 methods like ServerSessionMessageListenerContainer itself.
|
||||
*
|
||||
* <p>This class can be used for JMS 1.0.2 providers, offering the same facility as
|
||||
* ServerSessionMessageListenerContainer does for JMS 1.1 providers.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
|
||||
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
|
||||
*/
|
||||
public class ServerSessionMessageListenerContainer102 extends ServerSessionMessageListenerContainer {
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected Connection createConnection() throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicConnectionFactory) getConnectionFactory()).createTopicConnection();
|
||||
}
|
||||
else {
|
||||
return ((QueueConnectionFactory) getConnectionFactory()).createQueueConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected ConnectionConsumer createConsumer(Connection con, Destination destination, ServerSessionPool pool)
|
||||
throws JMSException {
|
||||
|
||||
if (isPubSubDomain()) {
|
||||
if (isSubscriptionDurable()) {
|
||||
return ((TopicConnection) con).createDurableConnectionConsumer(
|
||||
(Topic) destination, getDurableSubscriptionName(), getMessageSelector(), pool, getMaxMessagesPerTask());
|
||||
}
|
||||
else {
|
||||
return ((TopicConnection) con).createConnectionConsumer(
|
||||
(Topic) destination, getMessageSelector(), pool, getMaxMessagesPerTask());
|
||||
}
|
||||
}
|
||||
else {
|
||||
return ((QueueConnection) con).createConnectionConsumer(
|
||||
(Queue) destination, getMessageSelector(), pool, getMaxMessagesPerTask());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to use JMS 1.0.2 API.
|
||||
*/
|
||||
protected Session createSession(Connection con) throws JMSException {
|
||||
if (isPubSubDomain()) {
|
||||
return ((TopicConnection) con).createTopicSession(isSessionTransacted(), getSessionAcknowledgeMode());
|
||||
}
|
||||
else {
|
||||
return ((QueueConnection) con).createQueueSession(isSessionTransacted(), getSessionAcknowledgeMode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation overrides the superclass method to avoid using
|
||||
* JMS 1.1's Session <code>getAcknowledgeMode()</code> method.
|
||||
* The best we can do here is to check the setting on the listener container.
|
||||
*/
|
||||
protected boolean isClientAcknowledge(Session session) throws JMSException {
|
||||
return (getSessionAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener.serversession;
|
||||
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.ServerSession;
|
||||
import javax.jms.Session;
|
||||
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.jms.support.JmsUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* The simplest possible implementation of the ServerSessionFactory SPI:
|
||||
* creating a new ServerSession with a new JMS Session every time.
|
||||
* This is the default used by ServerSessionMessageListenerContainer.
|
||||
*
|
||||
* <p>The execution of a ServerSession (and its MessageListener) gets delegated
|
||||
* to a TaskExecutor. By default, a SimpleAsyncTaskExecutor will be used,
|
||||
* creating a new Thread for every execution attempt. Alternatives are a
|
||||
* TimerTaskExecutor, sharing a single Thread for the execution of all
|
||||
* ServerSessions, or a TaskExecutor implementation backed by a thread pool.
|
||||
*
|
||||
* <p>To reuse JMS Sessions and/or to limit the number of concurrent
|
||||
* ServerSession executions, consider using a pooling ServerSessionFactory:
|
||||
* for example, CommonsPoolServerSessionFactory.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @deprecated as of Spring 2.5, in favor of DefaultMessageListenerContainer
|
||||
* and JmsMessageEndpointManager. To be removed in Spring 3.0.
|
||||
* @see org.springframework.core.task.TaskExecutor
|
||||
* @see org.springframework.core.task.SimpleAsyncTaskExecutor
|
||||
* @see org.springframework.scheduling.timer.TimerTaskExecutor
|
||||
* @see CommonsPoolServerSessionFactory
|
||||
* @see ServerSessionMessageListenerContainer
|
||||
*/
|
||||
public class SimpleServerSessionFactory implements ServerSessionFactory {
|
||||
|
||||
/**
|
||||
* Default thread name prefix: "SimpleServerSessionFactory-".
|
||||
*/
|
||||
public static final String DEFAULT_THREAD_NAME_PREFIX =
|
||||
ClassUtils.getShortName(SimpleServerSessionFactory.class) + "-";
|
||||
|
||||
|
||||
private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(DEFAULT_THREAD_NAME_PREFIX);
|
||||
|
||||
|
||||
/**
|
||||
* Specify the TaskExecutor to use for executing ServerSessions
|
||||
* (and consequently, the underlying MessageListener).
|
||||
* <p>Default is a SimpleAsyncTaskExecutor, creating a new Thread for
|
||||
* every execution attempt. Alternatives are a TimerTaskExecutor,
|
||||
* sharing a single Thread for the execution of all ServerSessions,
|
||||
* or a TaskExecutor implementation backed by a thread pool.
|
||||
* @see org.springframework.core.task.SimpleAsyncTaskExecutor
|
||||
* @see org.springframework.scheduling.timer.TimerTaskExecutor
|
||||
*/
|
||||
public void setTaskExecutor(TaskExecutor taskExecutor) {
|
||||
Assert.notNull(taskExecutor, "taskExecutor is required");
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the TaskExecutor to use for executing ServerSessions.
|
||||
*/
|
||||
protected TaskExecutor getTaskExecutor() {
|
||||
return taskExecutor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new SimpleServerSession with a new JMS Session
|
||||
* for every call.
|
||||
*/
|
||||
public ServerSession getServerSession(ListenerSessionManager sessionManager) throws JMSException {
|
||||
return new SimpleServerSession(sessionManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation is empty, as there is no state held for
|
||||
* each ListenerSessionManager.
|
||||
*/
|
||||
public void close(ListenerSessionManager sessionManager) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ServerSession implementation that simply creates a new
|
||||
* JMS Session and executes it via the specified TaskExecutor.
|
||||
*/
|
||||
private class SimpleServerSession implements ServerSession {
|
||||
|
||||
private final ListenerSessionManager sessionManager;
|
||||
|
||||
private final Session session;
|
||||
|
||||
public SimpleServerSession(ListenerSessionManager sessionManager) throws JMSException {
|
||||
this.sessionManager = sessionManager;
|
||||
this.session = sessionManager.createListenerSession();
|
||||
}
|
||||
|
||||
public Session getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
getTaskExecutor().execute(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
sessionManager.executeListenerSession(session);
|
||||
}
|
||||
finally {
|
||||
JmsUtils.closeSession(session);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
This package contains the ServerSessionMessageListenerContainer implementation,
|
||||
based on the standard JMS ServerSessionPool API.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
This package contains integration classes for JMS,
|
||||
allowing for Spring-style JMS access.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,442 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.remoting;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageConsumer;
|
||||
import javax.jms.MessageFormatException;
|
||||
import javax.jms.MessageProducer;
|
||||
import javax.jms.Queue;
|
||||
import javax.jms.QueueConnection;
|
||||
import javax.jms.QueueConnectionFactory;
|
||||
import javax.jms.QueueSender;
|
||||
import javax.jms.QueueSession;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.TemporaryQueue;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.jms.connection.ConnectionFactoryUtils;
|
||||
import org.springframework.jms.support.JmsUtils;
|
||||
import org.springframework.jms.support.converter.MessageConverter;
|
||||
import org.springframework.jms.support.converter.SimpleMessageConverter;
|
||||
import org.springframework.jms.support.destination.DestinationResolver;
|
||||
import org.springframework.jms.support.destination.DynamicDestinationResolver;
|
||||
import org.springframework.remoting.RemoteAccessException;
|
||||
import org.springframework.remoting.RemoteInvocationFailureException;
|
||||
import org.springframework.remoting.support.DefaultRemoteInvocationFactory;
|
||||
import org.springframework.remoting.support.RemoteInvocation;
|
||||
import org.springframework.remoting.support.RemoteInvocationFactory;
|
||||
import org.springframework.remoting.support.RemoteInvocationResult;
|
||||
|
||||
/**
|
||||
* {@link org.aopalliance.intercept.MethodInterceptor} for accessing a
|
||||
* JMS-based remote service.
|
||||
*
|
||||
* <p>Serializes remote invocation objects and deserializes remote invocation
|
||||
* result objects. Uses Java serialization just like RMI, but with the JMS
|
||||
* provider as communication infrastructure.
|
||||
*
|
||||
* <p>To be configured with a {@link javax.jms.QueueConnectionFactory} and a
|
||||
* target queue (either as {@link javax.jms.Queue} reference or as queue name).
|
||||
*
|
||||
* <p>Thanks to James Strachan for the original prototype that this
|
||||
* JMS invoker mechanism was inspired by!
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author James Strachan
|
||||
* @since 2.0
|
||||
* @see #setConnectionFactory
|
||||
* @see #setQueue
|
||||
* @see #setQueueName
|
||||
* @see org.springframework.jms.remoting.JmsInvokerServiceExporter
|
||||
* @see org.springframework.jms.remoting.JmsInvokerProxyFactoryBean
|
||||
*/
|
||||
public class JmsInvokerClientInterceptor implements MethodInterceptor, InitializingBean {
|
||||
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
private Object queue;
|
||||
|
||||
private DestinationResolver destinationResolver = new DynamicDestinationResolver();
|
||||
|
||||
private RemoteInvocationFactory remoteInvocationFactory = new DefaultRemoteInvocationFactory();
|
||||
|
||||
private MessageConverter messageConverter = new SimpleMessageConverter();
|
||||
|
||||
private long receiveTimeout = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Set the QueueConnectionFactory to use for obtaining JMS QueueConnections.
|
||||
*/
|
||||
public void setConnectionFactory(ConnectionFactory connectionFactory) {
|
||||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the QueueConnectionFactory to use for obtaining JMS QueueConnections.
|
||||
*/
|
||||
protected ConnectionFactory getConnectionFactory() {
|
||||
return this.connectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the target Queue to send invoker requests to.
|
||||
*/
|
||||
public void setQueue(Queue queue) {
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of target queue to send invoker requests to.
|
||||
* The specified name will be dynamically resolved via the
|
||||
* {@link #setDestinationResolver DestinationResolver}.
|
||||
*/
|
||||
public void setQueueName(String queueName) {
|
||||
this.queue = queueName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DestinationResolver that is to be used to resolve Queue
|
||||
* references for this accessor.
|
||||
* <p>The default resolver is a DynamicDestinationResolver. Specify a
|
||||
* JndiDestinationResolver for resolving destination names as JNDI locations.
|
||||
* @see org.springframework.jms.support.destination.DynamicDestinationResolver
|
||||
* @see org.springframework.jms.support.destination.JndiDestinationResolver
|
||||
*/
|
||||
public void setDestinationResolver(DestinationResolver destinationResolver) {
|
||||
this.destinationResolver =
|
||||
(destinationResolver != null ? destinationResolver : new DynamicDestinationResolver());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the RemoteInvocationFactory to use for this accessor.
|
||||
* Default is a {@link org.springframework.remoting.support.DefaultRemoteInvocationFactory}.
|
||||
* <p>A custom invocation factory can add further context information
|
||||
* to the invocation, for example user credentials.
|
||||
*/
|
||||
public void setRemoteInvocationFactory(RemoteInvocationFactory remoteInvocationFactory) {
|
||||
this.remoteInvocationFactory =
|
||||
(remoteInvocationFactory != null ? remoteInvocationFactory : new DefaultRemoteInvocationFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the MessageConverter to use for turning
|
||||
* {@link org.springframework.remoting.support.RemoteInvocation}
|
||||
* objects into request messages, as well as response messages into
|
||||
* {@link org.springframework.remoting.support.RemoteInvocationResult} objects.
|
||||
* <p>Default is a {@link org.springframework.jms.support.converter.SimpleMessageConverter},
|
||||
* using a standard JMS {@link javax.jms.ObjectMessage} for each invocation /
|
||||
* invocation result object.
|
||||
* <p>Custom implementations may generally adapt Serializables into
|
||||
* special kinds of messages, or might be specifically tailored for
|
||||
* translating RemoteInvocation(Result)s into specific kinds of messages.
|
||||
*/
|
||||
public void setMessageConverter(MessageConverter messageConverter) {
|
||||
this.messageConverter = (messageConverter != null ? messageConverter : new SimpleMessageConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timeout to use for receiving the response message for a request
|
||||
* (in milliseconds).
|
||||
* <p>The default is 0, which indicates a blocking receive without timeout.
|
||||
* @see javax.jms.MessageConsumer#receive(long)
|
||||
* @see javax.jms.MessageConsumer#receive()
|
||||
*/
|
||||
public void setReceiveTimeout(long receiveTimeout) {
|
||||
this.receiveTimeout = receiveTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the timeout to use for receiving the response message for a request
|
||||
* (in milliseconds).
|
||||
*/
|
||||
protected long getReceiveTimeout() {
|
||||
return this.receiveTimeout;
|
||||
}
|
||||
|
||||
|
||||
public void afterPropertiesSet() {
|
||||
if (getConnectionFactory() == null) {
|
||||
throw new IllegalArgumentException("Property 'connectionFactory' is required");
|
||||
}
|
||||
if (this.queue == null) {
|
||||
throw new IllegalArgumentException("'queue' or 'queueName' is required");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
|
||||
if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
|
||||
return "JMS invoker proxy for queue [" + this.queue + "]";
|
||||
}
|
||||
|
||||
RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
|
||||
RemoteInvocationResult result = null;
|
||||
try {
|
||||
result = executeRequest(invocation);
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
throw convertJmsInvokerAccessException(ex);
|
||||
}
|
||||
try {
|
||||
return recreateRemoteInvocationResult(result);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
if (result.hasInvocationTargetException()) {
|
||||
throw ex;
|
||||
}
|
||||
else {
|
||||
throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() +
|
||||
"] failed in JMS invoker remote service at queue [" + this.queue + "]", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RemoteInvocation object for the given AOP method invocation.
|
||||
* The default implementation delegates to the RemoteInvocationFactory.
|
||||
* <p>Can be overridden in subclasses to provide custom RemoteInvocation
|
||||
* subclasses, containing additional invocation parameters like user credentials.
|
||||
* Note that it is preferable to use a custom RemoteInvocationFactory which
|
||||
* is a reusable strategy.
|
||||
* @param methodInvocation the current AOP method invocation
|
||||
* @return the RemoteInvocation object
|
||||
* @see RemoteInvocationFactory#createRemoteInvocation
|
||||
*/
|
||||
protected RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
|
||||
return this.remoteInvocationFactory.createRemoteInvocation(methodInvocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given remote invocation, sending an invoker request message
|
||||
* to this accessor's target queue and waiting for a corresponding response.
|
||||
* @param invocation the RemoteInvocation to execute
|
||||
* @return the RemoteInvocationResult object
|
||||
* @throws JMSException in case of JMS failure
|
||||
* @see #doExecuteRequest
|
||||
*/
|
||||
protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws JMSException {
|
||||
Connection con = createConnection();
|
||||
Session session = null;
|
||||
try {
|
||||
session = createSession(con);
|
||||
Queue queueToUse = resolveQueue(session);
|
||||
Message requestMessage = createRequestMessage(session, invocation);
|
||||
con.start();
|
||||
Message responseMessage = doExecuteRequest(session, queueToUse, requestMessage);
|
||||
return extractInvocationResult(responseMessage);
|
||||
}
|
||||
finally {
|
||||
JmsUtils.closeSession(session);
|
||||
ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory(), true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JMS Connection for this JMS invoker,
|
||||
* ideally a <code>javax.jms.QueueConnection</code>.
|
||||
* <p>The default implementation uses the
|
||||
* <code>javax.jms.QueueConnectionFactory</code> API if available,
|
||||
* falling back to a standard JMS 1.1 ConnectionFactory otherwise.
|
||||
* This is necessary for working with generic JMS 1.1 connection pools
|
||||
* (such as ActiveMQ's <code>org.apache.activemq.pool.PooledConnectionFactory</code>).
|
||||
*/
|
||||
protected Connection createConnection() throws JMSException {
|
||||
ConnectionFactory cf = getConnectionFactory();
|
||||
if (cf instanceof QueueConnectionFactory) {
|
||||
return ((QueueConnectionFactory) cf).createQueueConnection();
|
||||
}
|
||||
else {
|
||||
return cf.createConnection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JMS Session for this JMS invoker,
|
||||
* ideally a <code>javax.jms.QueueSession</code>.
|
||||
* <p>The default implementation uses the
|
||||
* <code>javax.jms.QueueConnection</code> API if available,
|
||||
* falling back to a standard JMS 1.1 Connection otherwise.
|
||||
* This is necessary for working with generic JMS 1.1 connection pools
|
||||
* (such as ActiveMQ's <code>org.apache.activemq.pool.PooledConnectionFactory</code>).
|
||||
*/
|
||||
protected Session createSession(Connection con) throws JMSException {
|
||||
if (con instanceof QueueConnection) {
|
||||
return ((QueueConnection) con).createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||
}
|
||||
else {
|
||||
return con.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve this accessor's target queue.
|
||||
* @param session the current JMS Session
|
||||
* @return the resolved target Queue
|
||||
* @throws JMSException if resolution failed
|
||||
*/
|
||||
protected Queue resolveQueue(Session session) throws JMSException {
|
||||
if (this.queue instanceof Queue) {
|
||||
return (Queue) this.queue;
|
||||
}
|
||||
else if (this.queue instanceof String) {
|
||||
return resolveQueueName(session, (String) this.queue);
|
||||
}
|
||||
else {
|
||||
throw new javax.jms.IllegalStateException(
|
||||
"Queue object [" + this.queue + "] is neither a [javax.jms.Queue] nor a queue name String");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given queue name into a JMS {@link javax.jms.Queue},
|
||||
* via this accessor's {@link DestinationResolver}.
|
||||
* @param session the current JMS Session
|
||||
* @param queueName the name of the queue
|
||||
* @return the located Queue
|
||||
* @throws JMSException if resolution failed
|
||||
* @see #setDestinationResolver
|
||||
*/
|
||||
protected Queue resolveQueueName(Session session, String queueName) throws JMSException {
|
||||
return (Queue) this.destinationResolver.resolveDestinationName(session, queueName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the invoker request message.
|
||||
* <p>The default implementation creates a JMS ObjectMessage
|
||||
* for the given RemoteInvocation object.
|
||||
* @param session the current JMS Session
|
||||
* @param invocation the remote invocation to send
|
||||
* @return the JMS Message to send
|
||||
* @throws JMSException if the message could not be created
|
||||
*/
|
||||
protected Message createRequestMessage(Session session, RemoteInvocation invocation) throws JMSException {
|
||||
return this.messageConverter.toMessage(invocation, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually execute the given request, sending the invoker request message
|
||||
* to the specified target queue and waiting for a corresponding response.
|
||||
* <p>The default implementation is based on standard JMS send/receive,
|
||||
* using a {@link javax.jms.TemporaryQueue} for receiving the response.
|
||||
* @param session the JMS Session to use
|
||||
* @param queue the resolved target Queue to send to
|
||||
* @param requestMessage the JMS Message to send
|
||||
* @return the RemoteInvocationResult object
|
||||
* @throws JMSException in case of JMS failure
|
||||
*/
|
||||
protected Message doExecuteRequest(Session session, Queue queue, Message requestMessage) throws JMSException {
|
||||
TemporaryQueue responseQueue = null;
|
||||
MessageProducer producer = null;
|
||||
MessageConsumer consumer = null;
|
||||
try {
|
||||
if (session instanceof QueueSession) {
|
||||
// Perform all calls on QueueSession reference for JMS 1.0.2 compatibility...
|
||||
QueueSession queueSession = (QueueSession) session;
|
||||
responseQueue = queueSession.createTemporaryQueue();
|
||||
QueueSender sender = queueSession.createSender(queue);
|
||||
producer = sender;
|
||||
consumer = queueSession.createReceiver(responseQueue);
|
||||
requestMessage.setJMSReplyTo(responseQueue);
|
||||
sender.send(requestMessage);
|
||||
}
|
||||
else {
|
||||
// Standard JMS 1.1 API usage...
|
||||
responseQueue = session.createTemporaryQueue();
|
||||
producer = session.createProducer(queue);
|
||||
consumer = session.createConsumer(responseQueue);
|
||||
requestMessage.setJMSReplyTo(responseQueue);
|
||||
producer.send(requestMessage);
|
||||
}
|
||||
long timeout = getReceiveTimeout();
|
||||
return (timeout > 0 ? consumer.receive(timeout) : consumer.receive());
|
||||
}
|
||||
finally {
|
||||
JmsUtils.closeMessageConsumer(consumer);
|
||||
JmsUtils.closeMessageProducer(producer);
|
||||
if (responseQueue != null) {
|
||||
responseQueue.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the invocation result from the response message.
|
||||
* <p>The default implementation expects a JMS ObjectMessage carrying
|
||||
* a RemoteInvocationResult object. If an invalid response message is
|
||||
* encountered, the <code>onInvalidResponse</code> callback gets invoked.
|
||||
* @param responseMessage the response message
|
||||
* @return the invocation result
|
||||
* @throws JMSException is thrown if a JMS exception occurs
|
||||
* @see #onInvalidResponse
|
||||
*/
|
||||
protected RemoteInvocationResult extractInvocationResult(Message responseMessage) throws JMSException {
|
||||
Object content = this.messageConverter.fromMessage(responseMessage);
|
||||
if (content instanceof RemoteInvocationResult) {
|
||||
return (RemoteInvocationResult) content;
|
||||
}
|
||||
return onInvalidResponse(responseMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that is invoked by <code>extractInvocationResult</code>
|
||||
* when it encounters an invalid response message.
|
||||
* <p>The default implementation throws a MessageFormatException.
|
||||
* @param responseMessage the invalid response message
|
||||
* @return an alternative invocation result that should be
|
||||
* returned to the caller (if desired)
|
||||
* @throws JMSException if the invalid response should lead
|
||||
* to an infrastructure exception propagated to the caller
|
||||
* @see #extractInvocationResult
|
||||
*/
|
||||
protected RemoteInvocationResult onInvalidResponse(Message responseMessage) throws JMSException {
|
||||
throw new MessageFormatException("Invalid response message: " + responseMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreate the invocation result contained in the given RemoteInvocationResult
|
||||
* object. The default implementation calls the default recreate method.
|
||||
* <p>Can be overridden in subclass to provide custom recreation, potentially
|
||||
* processing the returned result object.
|
||||
* @param result the RemoteInvocationResult to recreate
|
||||
* @return a return value if the invocation result is a successful return
|
||||
* @throws Throwable if the invocation result is an exception
|
||||
* @see org.springframework.remoting.support.RemoteInvocationResult#recreate()
|
||||
*/
|
||||
protected Object recreateRemoteInvocationResult(RemoteInvocationResult result) throws Throwable {
|
||||
return result.recreate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given JMS invoker access exception to an appropriate
|
||||
* Spring RemoteAccessException.
|
||||
* @param ex the exception to convert
|
||||
* @return the RemoteAccessException to throw
|
||||
*/
|
||||
protected RemoteAccessException convertJmsInvokerAccessException(JMSException ex) {
|
||||
throw new RemoteAccessException("Could not access JMS invoker queue [" + this.queue + "]", ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.remoting;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* FactoryBean for JMS invoker proxies. Exposes the proxied service for use
|
||||
* as a bean reference, using the specified service interface.
|
||||
*
|
||||
* <p>Serializes remote invocation objects and deserializes remote invocation
|
||||
* result objects. Uses Java serialization just like RMI, but with the JMS
|
||||
* provider as communication infrastructure.
|
||||
*
|
||||
* <p>To be configured with a {@link javax.jms.QueueConnectionFactory} and a
|
||||
* target queue (either as {@link javax.jms.Queue} reference or as queue name).
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
* @see #setConnectionFactory
|
||||
* @see #setQueueName
|
||||
* @see #setServiceInterface
|
||||
* @see org.springframework.jms.remoting.JmsInvokerClientInterceptor
|
||||
* @see org.springframework.jms.remoting.JmsInvokerServiceExporter
|
||||
*/
|
||||
public class JmsInvokerProxyFactoryBean extends JmsInvokerClientInterceptor
|
||||
implements FactoryBean, BeanClassLoaderAware {
|
||||
|
||||
private Class serviceInterface;
|
||||
|
||||
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
|
||||
|
||||
private Object serviceProxy;
|
||||
|
||||
|
||||
/**
|
||||
* Set the interface that the proxy must implement.
|
||||
* @param serviceInterface the interface that the proxy must implement
|
||||
* @throws IllegalArgumentException if the supplied <code>serviceInterface</code>
|
||||
* is <code>null</code>, or if the supplied <code>serviceInterface</code>
|
||||
* is not an interface type
|
||||
*/
|
||||
public void setServiceInterface(Class serviceInterface) {
|
||||
if (serviceInterface == null || !serviceInterface.isInterface()) {
|
||||
throw new IllegalArgumentException("'serviceInterface' must be an interface");
|
||||
}
|
||||
this.serviceInterface = serviceInterface;
|
||||
}
|
||||
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
if (this.serviceInterface == null) {
|
||||
throw new IllegalArgumentException("Property 'serviceInterface' is required");
|
||||
}
|
||||
this.serviceProxy = new ProxyFactory(this.serviceInterface, this).getProxy(this.beanClassLoader);
|
||||
}
|
||||
|
||||
|
||||
public Object getObject() {
|
||||
return this.serviceProxy;
|
||||
}
|
||||
|
||||
public Class getObjectType() {
|
||||
return this.serviceInterface;
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.remoting;
|
||||
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageFormatException;
|
||||
import javax.jms.MessageProducer;
|
||||
import javax.jms.Session;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.jms.listener.SessionAwareMessageListener;
|
||||
import org.springframework.jms.support.JmsUtils;
|
||||
import org.springframework.jms.support.converter.MessageConverter;
|
||||
import org.springframework.jms.support.converter.SimpleMessageConverter;
|
||||
import org.springframework.remoting.support.RemoteInvocation;
|
||||
import org.springframework.remoting.support.RemoteInvocationBasedExporter;
|
||||
import org.springframework.remoting.support.RemoteInvocationResult;
|
||||
|
||||
/**
|
||||
* JMS message listener that exports the specified service bean as a
|
||||
* JMS service endpoint, accessible via a JMS invoker proxy.
|
||||
*
|
||||
* <p>Note that this class implements Spring's
|
||||
* {@link org.springframework.jms.listener.SessionAwareMessageListener}
|
||||
* interface, since it requires access to the active JMS Session.
|
||||
* Hence, this class can only be used with message listener containers
|
||||
* which support the SessionAwareMessageListener interface (e.g. Spring's
|
||||
* {@link org.springframework.jms.listener.DefaultMessageListenerContainer}).
|
||||
*
|
||||
* <p>Thanks to James Strachan for the original prototype that this
|
||||
* JMS invoker mechanism was inspired by!
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author James Strachan
|
||||
* @since 2.0
|
||||
* @see JmsInvokerClientInterceptor
|
||||
* @see JmsInvokerProxyFactoryBean
|
||||
*/
|
||||
public class JmsInvokerServiceExporter extends RemoteInvocationBasedExporter
|
||||
implements SessionAwareMessageListener, InitializingBean {
|
||||
|
||||
private MessageConverter messageConverter = new SimpleMessageConverter();
|
||||
|
||||
private boolean ignoreInvalidRequests = true;
|
||||
|
||||
private Object proxy;
|
||||
|
||||
|
||||
/**
|
||||
* Specify the MessageConverter to use for turning request messages into
|
||||
* {@link org.springframework.remoting.support.RemoteInvocation} objects,
|
||||
* as well as {@link org.springframework.remoting.support.RemoteInvocationResult}
|
||||
* objects into response messages.
|
||||
* <p>Default is a {@link org.springframework.jms.support.converter.SimpleMessageConverter},
|
||||
* using a standard JMS {@link javax.jms.ObjectMessage} for each invocation /
|
||||
* invocation result object.
|
||||
* <p>Custom implementations may generally adapt Serializables into
|
||||
* special kinds of messages, or might be specifically tailored for
|
||||
* translating RemoteInvocation(Result)s into specific kinds of messages.
|
||||
*/
|
||||
public void setMessageConverter(MessageConverter messageConverter) {
|
||||
this.messageConverter = (messageConverter != null ? messageConverter : new SimpleMessageConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether invalidly formatted messages should be discarded.
|
||||
* Default is "true".
|
||||
* <p>Switch this flag to "false" to throw an exception back to the
|
||||
* listener container. This will typically lead to redelivery of
|
||||
* the message, which is usually undesirable - since the message
|
||||
* content will be the same (that is, still invalid).
|
||||
*/
|
||||
public void setIgnoreInvalidRequests(boolean ignoreInvalidRequests) {
|
||||
this.ignoreInvalidRequests = ignoreInvalidRequests;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() {
|
||||
this.proxy = getProxyForService();
|
||||
}
|
||||
|
||||
|
||||
public void onMessage(Message requestMessage, Session session) throws JMSException {
|
||||
RemoteInvocation invocation = readRemoteInvocation(requestMessage);
|
||||
if (invocation != null) {
|
||||
RemoteInvocationResult result = invokeAndCreateResult(invocation, this.proxy);
|
||||
writeRemoteInvocationResult(requestMessage, session, result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a RemoteInvocation from the given JMS message.
|
||||
* @param requestMessage current request message
|
||||
* @return the RemoteInvocation object (or <code>null</code>
|
||||
* in case of an invalid message that will simply be ignored)
|
||||
* @throws javax.jms.JMSException in case of message access failure
|
||||
*/
|
||||
protected RemoteInvocation readRemoteInvocation(Message requestMessage) throws JMSException {
|
||||
Object content = this.messageConverter.fromMessage(requestMessage);
|
||||
if (content instanceof RemoteInvocation) {
|
||||
return (RemoteInvocation) content;
|
||||
}
|
||||
return onInvalidRequest(requestMessage);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send the given RemoteInvocationResult as a JMS message to the originator.
|
||||
* @param requestMessage current request message
|
||||
* @param session the JMS Session to use
|
||||
* @param result the RemoteInvocationResult object
|
||||
* @throws javax.jms.JMSException if thrown by trying to send the message
|
||||
*/
|
||||
protected void writeRemoteInvocationResult(
|
||||
Message requestMessage, Session session, RemoteInvocationResult result) throws JMSException {
|
||||
|
||||
Message response = createResponseMessage(requestMessage, session, result);
|
||||
MessageProducer producer = session.createProducer(requestMessage.getJMSReplyTo());
|
||||
try {
|
||||
producer.send(response);
|
||||
}
|
||||
finally {
|
||||
JmsUtils.closeMessageProducer(producer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the invocation result response message.
|
||||
* <p>The default implementation creates a JMS ObjectMessage for the given
|
||||
* RemoteInvocationResult object. It sets the response's correlation id
|
||||
* to the request message's correlation id, if any; otherwise to the
|
||||
* request message id.
|
||||
* @param request the original request message
|
||||
* @param session the JMS session to use
|
||||
* @param result the invocation result
|
||||
* @return the message response to send
|
||||
* @throws javax.jms.JMSException if creating the messsage failed
|
||||
*/
|
||||
protected Message createResponseMessage(Message request, Session session, RemoteInvocationResult result)
|
||||
throws JMSException {
|
||||
|
||||
Message response = this.messageConverter.toMessage(result, session);
|
||||
String correlation = request.getJMSCorrelationID();
|
||||
if (correlation == null) {
|
||||
correlation = request.getJMSMessageID();
|
||||
}
|
||||
response.setJMSCorrelationID(correlation);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that is invoked by {@link #readRemoteInvocation}
|
||||
* when it encounters an invalid request message.
|
||||
* <p>The default implementation either discards the invalid message or
|
||||
* throws a MessageFormatException - according to the "ignoreInvalidRequests"
|
||||
* flag, which is set to "true" (that is, discard invalid messages) by default.
|
||||
* @param requestMessage the invalid request message
|
||||
* @return the RemoteInvocation to expose for the invalid request (typically
|
||||
* <code>null</code> in case of an invalid message that will simply be ignored)
|
||||
* @throws javax.jms.JMSException in case of the invalid request supposed
|
||||
* to lead to an exception (instead of ignoring it)
|
||||
* @see #readRemoteInvocation
|
||||
* @see #setIgnoreInvalidRequests
|
||||
*/
|
||||
protected RemoteInvocation onInvalidRequest(Message requestMessage) throws JMSException {
|
||||
if (this.ignoreInvalidRequests) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Invalid request message will be discarded: " + requestMessage);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
throw new MessageFormatException("Invalid request message: " + requestMessage);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
Remoting classes for transparent Java-to-Java remoting via a JMS provider.
|
||||
|
||||
<p>Allows the target service to be load-balanced across a number of queue
|
||||
receivers, and provides a level of indirection between the client and the
|
||||
service: They only need to agree on a queue name and a service interface.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.support;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.MessageConsumer;
|
||||
import javax.jms.MessageProducer;
|
||||
import javax.jms.QueueBrowser;
|
||||
import javax.jms.QueueRequestor;
|
||||
import javax.jms.Session;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.jms.InvalidClientIDException;
|
||||
import org.springframework.jms.InvalidDestinationException;
|
||||
import org.springframework.jms.InvalidSelectorException;
|
||||
import org.springframework.jms.JmsException;
|
||||
import org.springframework.jms.JmsSecurityException;
|
||||
import org.springframework.jms.MessageEOFException;
|
||||
import org.springframework.jms.MessageFormatException;
|
||||
import org.springframework.jms.MessageNotReadableException;
|
||||
import org.springframework.jms.MessageNotWriteableException;
|
||||
import org.springframework.jms.ResourceAllocationException;
|
||||
import org.springframework.jms.TransactionInProgressException;
|
||||
import org.springframework.jms.TransactionRolledBackException;
|
||||
import org.springframework.jms.UncategorizedJmsException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Generic utility methods for working with JMS. Mainly for internal use
|
||||
* within the framework, but also useful for custom JMS access code.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
*/
|
||||
public abstract class JmsUtils {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(JmsUtils.class);
|
||||
|
||||
|
||||
/**
|
||||
* Close the given JMS Connection and ignore any thrown exception.
|
||||
* This is useful for typical <code>finally</code> blocks in manual JMS code.
|
||||
* @param con the JMS Connection to close (may be <code>null</code>)
|
||||
*/
|
||||
public static void closeConnection(Connection con) {
|
||||
closeConnection(con, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the given JMS Connection and ignore any thrown exception.
|
||||
* This is useful for typical <code>finally</code> blocks in manual JMS code.
|
||||
* @param con the JMS Connection to close (may be <code>null</code>)
|
||||
* @param stop whether to call <code>stop()</code> before closing
|
||||
*/
|
||||
public static void closeConnection(Connection con, boolean stop) {
|
||||
if (con != null) {
|
||||
try {
|
||||
if (stop) {
|
||||
try {
|
||||
con.stop();
|
||||
}
|
||||
finally {
|
||||
con.close();
|
||||
}
|
||||
}
|
||||
else {
|
||||
con.close();
|
||||
}
|
||||
}
|
||||
catch (javax.jms.IllegalStateException ex) {
|
||||
logger.debug("Ignoring Connection state exception - assuming already closed: " + ex);
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
logger.debug("Could not close JMS Connection", ex);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// We don't trust the JMS provider: It might throw RuntimeException or Error.
|
||||
logger.debug("Unexpected exception on closing JMS Connection", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the given JMS Session and ignore any thrown exception.
|
||||
* This is useful for typical <code>finally</code> blocks in manual JMS code.
|
||||
* @param session the JMS Session to close (may be <code>null</code>)
|
||||
*/
|
||||
public static void closeSession(Session session) {
|
||||
if (session != null) {
|
||||
try {
|
||||
session.close();
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
logger.trace("Could not close JMS Session", ex);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// We don't trust the JMS provider: It might throw RuntimeException or Error.
|
||||
logger.trace("Unexpected exception on closing JMS Session", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the given JMS MessageProducer and ignore any thrown exception.
|
||||
* This is useful for typical <code>finally</code> blocks in manual JMS code.
|
||||
* @param producer the JMS MessageProducer to close (may be <code>null</code>)
|
||||
*/
|
||||
public static void closeMessageProducer(MessageProducer producer) {
|
||||
if (producer != null) {
|
||||
try {
|
||||
producer.close();
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
logger.trace("Could not close JMS MessageProducer", ex);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// We don't trust the JMS provider: It might throw RuntimeException or Error.
|
||||
logger.trace("Unexpected exception on closing JMS MessageProducer", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the given JMS MessageConsumer and ignore any thrown exception.
|
||||
* This is useful for typical <code>finally</code> blocks in manual JMS code.
|
||||
* @param consumer the JMS MessageConsumer to close (may be <code>null</code>)
|
||||
*/
|
||||
public static void closeMessageConsumer(MessageConsumer consumer) {
|
||||
if (consumer != null) {
|
||||
// Clear interruptions to ensure that the consumer closes successfully...
|
||||
// (working around misbehaving JMS providers such as ActiveMQ)
|
||||
boolean wasInterrupted = Thread.interrupted();
|
||||
try {
|
||||
consumer.close();
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
logger.trace("Could not close JMS MessageConsumer", ex);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// We don't trust the JMS provider: It might throw RuntimeException or Error.
|
||||
logger.trace("Unexpected exception on closing JMS MessageConsumer", ex);
|
||||
}
|
||||
finally {
|
||||
if (wasInterrupted) {
|
||||
// Reset the interrupted flag as it was before.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the given JMS QueueBrowser and ignore any thrown exception.
|
||||
* This is useful for typical <code>finally</code> blocks in manual JMS code.
|
||||
* @param browser the JMS QueueBrowser to close (may be <code>null</code>)
|
||||
*/
|
||||
public static void closeQueueBrowser(QueueBrowser browser) {
|
||||
if (browser != null) {
|
||||
try {
|
||||
browser.close();
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
logger.trace("Could not close JMS QueueBrowser", ex);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// We don't trust the JMS provider: It might throw RuntimeException or Error.
|
||||
logger.trace("Unexpected exception on closing JMS QueueBrowser", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the given JMS QueueRequestor and ignore any thrown exception.
|
||||
* This is useful for typical <code>finally</code> blocks in manual JMS code.
|
||||
* @param requestor the JMS QueueRequestor to close (may be <code>null</code>)
|
||||
*/
|
||||
public static void closeQueueRequestor(QueueRequestor requestor) {
|
||||
if (requestor != null) {
|
||||
try {
|
||||
requestor.close();
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
logger.trace("Could not close JMS QueueRequestor", ex);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// We don't trust the JMS provider: It might throw RuntimeException or Error.
|
||||
logger.trace("Unexpected exception on closing JMS QueueRequestor", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the Session if not within a JTA transaction.
|
||||
* @param session the JMS Session to commit
|
||||
* @throws JMSException if committing failed
|
||||
*/
|
||||
public static void commitIfNecessary(Session session) throws JMSException {
|
||||
Assert.notNull(session, "Session must not be null");
|
||||
try {
|
||||
session.commit();
|
||||
}
|
||||
catch (javax.jms.TransactionInProgressException ex) {
|
||||
// Ignore -> can only happen in case of a JTA transaction.
|
||||
}
|
||||
catch (javax.jms.IllegalStateException ex) {
|
||||
// Ignore -> can only happen in case of a JTA transaction.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the Session if not within a JTA transaction.
|
||||
* @param session the JMS Session to rollback
|
||||
* @throws JMSException if committing failed
|
||||
*/
|
||||
public static void rollbackIfNecessary(Session session) throws JMSException {
|
||||
Assert.notNull(session, "Session must not be null");
|
||||
try {
|
||||
session.rollback();
|
||||
}
|
||||
catch (javax.jms.TransactionInProgressException ex) {
|
||||
// Ignore -> can only happen in case of a JTA transaction.
|
||||
}
|
||||
catch (javax.jms.IllegalStateException ex) {
|
||||
// Ignore -> can only happen in case of a JTA transaction.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a descriptive exception message for the given JMSException,
|
||||
* incorporating a linked exception's message if appropriate.
|
||||
* @param ex the JMSException to build a message for
|
||||
* @return the descriptive message String
|
||||
* @see javax.jms.JMSException#getLinkedException()
|
||||
*/
|
||||
public static String buildExceptionMessage(JMSException ex) {
|
||||
String message = ex.getMessage();
|
||||
Exception linkedEx = ex.getLinkedException();
|
||||
if (linkedEx != null && message.indexOf(linkedEx.getMessage()) == -1) {
|
||||
message = message + "; nested exception is " + linkedEx;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the specified checked {@link javax.jms.JMSException JMSException} to a
|
||||
* Spring runtime {@link org.springframework.jms.JmsException JmsException} equivalent.
|
||||
* @param ex the original checked JMSException to convert
|
||||
* @return the Spring runtime JmsException wrapping the given exception
|
||||
*/
|
||||
public static JmsException convertJmsAccessException(JMSException ex) {
|
||||
Assert.notNull(ex, "JMSException must not be null");
|
||||
|
||||
if (ex instanceof javax.jms.IllegalStateException) {
|
||||
return new org.springframework.jms.IllegalStateException((javax.jms.IllegalStateException) ex);
|
||||
}
|
||||
if (ex instanceof javax.jms.InvalidClientIDException) {
|
||||
return new InvalidClientIDException((javax.jms.InvalidClientIDException) ex);
|
||||
}
|
||||
if (ex instanceof javax.jms.InvalidDestinationException) {
|
||||
return new InvalidDestinationException((javax.jms.InvalidDestinationException) ex);
|
||||
}
|
||||
if (ex instanceof javax.jms.InvalidSelectorException) {
|
||||
return new InvalidSelectorException((javax.jms.InvalidSelectorException) ex);
|
||||
}
|
||||
if (ex instanceof javax.jms.JMSSecurityException) {
|
||||
return new JmsSecurityException((javax.jms.JMSSecurityException) ex);
|
||||
}
|
||||
if (ex instanceof javax.jms.MessageEOFException) {
|
||||
return new MessageEOFException((javax.jms.MessageEOFException) ex);
|
||||
}
|
||||
if (ex instanceof javax.jms.MessageFormatException) {
|
||||
return new MessageFormatException((javax.jms.MessageFormatException) ex);
|
||||
}
|
||||
if (ex instanceof javax.jms.MessageNotReadableException) {
|
||||
return new MessageNotReadableException((javax.jms.MessageNotReadableException) ex);
|
||||
}
|
||||
if (ex instanceof javax.jms.MessageNotWriteableException) {
|
||||
return new MessageNotWriteableException((javax.jms.MessageNotWriteableException) ex);
|
||||
}
|
||||
if (ex instanceof javax.jms.ResourceAllocationException) {
|
||||
return new ResourceAllocationException((javax.jms.ResourceAllocationException) ex);
|
||||
}
|
||||
if (ex instanceof javax.jms.TransactionInProgressException) {
|
||||
return new TransactionInProgressException((javax.jms.TransactionInProgressException) ex);
|
||||
}
|
||||
if (ex instanceof javax.jms.TransactionRolledBackException) {
|
||||
return new TransactionRolledBackException((javax.jms.TransactionRolledBackException) ex);
|
||||
}
|
||||
|
||||
// fallback
|
||||
return new UncategorizedJmsException(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* Copyright 2002-2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.support.converter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.jms.BytesMessage;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.MapMessage;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.ObjectMessage;
|
||||
import javax.jms.Session;
|
||||
import javax.jms.TextMessage;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A simple message converter which is able to handle TextMessages, BytesMessages,
|
||||
* MapMessages, and ObjectMessages. Used as default conversion strategy
|
||||
* by {@link org.springframework.jms.core.JmsTemplate}, for
|
||||
* <code>convertAndSend</code> and <code>receiveAndConvert</code> operations.
|
||||
*
|
||||
* <p>Converts a String to a {@link javax.jms.TextMessage}, a byte array to a
|
||||
* {@link javax.jms.BytesMessage}, a Map to a {@link javax.jms.MapMessage}, and
|
||||
* a Serializable object to a {@link javax.jms.ObjectMessage} (or vice versa).
|
||||
*
|
||||
* <p>This converter implementation works for both JMS 1.1 and JMS 1.0.2,
|
||||
* except when extracting a byte array from a BytesMessage. So for converting
|
||||
* BytesMessages with a JMS 1.0.2 provider, use {@link SimpleMessageConverter102}.
|
||||
* (As you would expect, {@link org.springframework.jms.core.JmsTemplate102}
|
||||
* uses SimpleMessageConverter102 as default.)
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
* @see org.springframework.jms.core.JmsTemplate#convertAndSend
|
||||
* @see org.springframework.jms.core.JmsTemplate#receiveAndConvert
|
||||
*/
|
||||
public class SimpleMessageConverter implements MessageConverter {
|
||||
|
||||
/**
|
||||
* This implementation creates a TextMessage for a String, a
|
||||
* BytesMessage for a byte array, a MapMessage for a Map,
|
||||
* and an ObjectMessage for a Serializable object.
|
||||
* @see #createMessageForString
|
||||
* @see #createMessageForByteArray
|
||||
* @see #createMessageForMap
|
||||
* @see #createMessageForSerializable
|
||||
*/
|
||||
public Message toMessage(Object object, Session session) throws JMSException, MessageConversionException {
|
||||
if (object instanceof Message) {
|
||||
return (Message) object;
|
||||
}
|
||||
else if (object instanceof String) {
|
||||
return createMessageForString((String) object, session);
|
||||
}
|
||||
else if (object instanceof byte[]) {
|
||||
return createMessageForByteArray((byte[]) object, session);
|
||||
}
|
||||
else if (object instanceof Map) {
|
||||
return createMessageForMap((Map) object, session);
|
||||
}
|
||||
else if (object instanceof Serializable) {
|
||||
return createMessageForSerializable(((Serializable) object), session);
|
||||
}
|
||||
else {
|
||||
throw new MessageConversionException("Cannot convert object of type [" +
|
||||
ObjectUtils.nullSafeClassName(object) + "] to JMS message. Supported message " +
|
||||
"payloads are: String, byte array, Map<String,?>, Serializable object.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation converts a TextMessage back to a String, a
|
||||
* ByteMessage back to a byte array, a MapMessage back to a Map,
|
||||
* and an ObjectMessage back to a Serializable object. Returns
|
||||
* the plain Message object in case of an unknown message type.
|
||||
* @see #extractStringFromMessage
|
||||
* @see #extractByteArrayFromMessage
|
||||
* @see #extractMapFromMessage
|
||||
* @see #extractSerializableFromMessage
|
||||
*/
|
||||
public Object fromMessage(Message message) throws JMSException, MessageConversionException {
|
||||
if (message instanceof TextMessage) {
|
||||
return extractStringFromMessage((TextMessage) message);
|
||||
}
|
||||
else if (message instanceof BytesMessage) {
|
||||
return extractByteArrayFromMessage((BytesMessage) message);
|
||||
}
|
||||
else if (message instanceof MapMessage) {
|
||||
return extractMapFromMessage((MapMessage) message);
|
||||
}
|
||||
else if (message instanceof ObjectMessage) {
|
||||
return extractSerializableFromMessage((ObjectMessage) message);
|
||||
}
|
||||
else {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a JMS TextMessage for the given String.
|
||||
* @param text the String to convert
|
||||
* @param session current JMS session
|
||||
* @return the resulting message
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
* @see javax.jms.Session#createTextMessage
|
||||
*/
|
||||
protected TextMessage createMessageForString(String text, Session session) throws JMSException {
|
||||
return session.createTextMessage(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JMS BytesMessage for the given byte array.
|
||||
* @param bytes the byyte array to convert
|
||||
* @param session current JMS session
|
||||
* @return the resulting message
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
* @see javax.jms.Session#createBytesMessage
|
||||
*/
|
||||
protected BytesMessage createMessageForByteArray(byte[] bytes, Session session) throws JMSException {
|
||||
BytesMessage message = session.createBytesMessage();
|
||||
message.writeBytes(bytes);
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JMS MapMessage for the given Map.
|
||||
* @param map the Map to convert
|
||||
* @param session current JMS session
|
||||
* @return the resulting message
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
* @see javax.jms.Session#createMapMessage
|
||||
*/
|
||||
protected MapMessage createMessageForMap(Map map, Session session) throws JMSException {
|
||||
MapMessage message = session.createMapMessage();
|
||||
for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry) it.next();
|
||||
if (!(entry.getKey() instanceof String)) {
|
||||
throw new MessageConversionException("Cannot convert non-String key of type [" +
|
||||
ObjectUtils.nullSafeClassName(entry.getKey()) + "] to JMS MapMessage entry");
|
||||
}
|
||||
message.setObject((String) entry.getKey(), entry.getValue());
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JMS ObjectMessage for the given Serializable object.
|
||||
* @param object the Serializable object to convert
|
||||
* @param session current JMS session
|
||||
* @return the resulting message
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
* @see javax.jms.Session#createObjectMessage
|
||||
*/
|
||||
protected ObjectMessage createMessageForSerializable(Serializable object, Session session) throws JMSException {
|
||||
return session.createObjectMessage(object);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a String from the given TextMessage.
|
||||
* @param message the message to convert
|
||||
* @return the resulting String
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
*/
|
||||
protected String extractStringFromMessage(TextMessage message) throws JMSException {
|
||||
return message.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a byte array from the given {@link BytesMessage}.
|
||||
* @param message the message to convert
|
||||
* @return the resulting byte array
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
*/
|
||||
protected byte[] extractByteArrayFromMessage(BytesMessage message) throws JMSException {
|
||||
byte[] bytes = new byte[(int) message.getBodyLength()];
|
||||
message.readBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a Map from the given {@link MapMessage}.
|
||||
* @param message the message to convert
|
||||
* @return the resulting Map
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
*/
|
||||
protected Map extractMapFromMessage(MapMessage message) throws JMSException {
|
||||
Map map = new HashMap();
|
||||
Enumeration en = message.getMapNames();
|
||||
while (en.hasMoreElements()) {
|
||||
String key = (String) en.nextElement();
|
||||
map.put(key, message.getObject(key));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a Serializable object from the given {@link ObjectMessage}.
|
||||
* @param message the message to convert
|
||||
* @return the resulting Serializable object
|
||||
* @throws JMSException if thrown by JMS methods
|
||||
*/
|
||||
protected Serializable extractSerializableFromMessage(ObjectMessage message) throws JMSException {
|
||||
return message.getObject();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.support.converter;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import javax.jms.BytesMessage;
|
||||
import javax.jms.JMSException;
|
||||
|
||||
/**
|
||||
* A subclass of {@link SimpleMessageConverter} for the JMS 1.0.2 specification,
|
||||
* not relying on JMS 1.1 methods like SimpleMessageConverter itself.
|
||||
* This class can be used for JMS 1.0.2 providers, offering the same functionality
|
||||
* as SimpleMessageConverter does for JMS 1.1 providers.
|
||||
*
|
||||
* <p>The only difference to the default SimpleMessageConverter is that BytesMessage
|
||||
* is handled differently: namely, without using the <code>getBodyLength()</code>
|
||||
* method which has been introduced in JMS 1.1 and is therefore not available on a
|
||||
* JMS 1.0.2 provider.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1.1
|
||||
* @see javax.jms.BytesMessage#getBodyLength()
|
||||
*/
|
||||
public class SimpleMessageConverter102 extends SimpleMessageConverter {
|
||||
|
||||
public static final int BUFFER_SIZE = 4096;
|
||||
|
||||
|
||||
/**
|
||||
* Overrides superclass method to copy bytes from the message into a
|
||||
* ByteArrayOutputStream, using a buffer, to avoid using the
|
||||
* <code>getBodyLength()</code> method which has been introduced in
|
||||
* JMS 1.1 and is therefore not available on a JMS 1.0.2 provider.
|
||||
* @see javax.jms.BytesMessage#getBodyLength()
|
||||
*/
|
||||
protected byte[] extractByteArrayFromMessage(BytesMessage message) throws JMSException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(BUFFER_SIZE);
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int bufferCount = -1;
|
||||
while ((bufferCount = message.readBytes(buffer)) >= 0) {
|
||||
baos.write(buffer, 0, bufferCount);
|
||||
if (bufferCount < BUFFER_SIZE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
Provides a MessageConverter abstraction to convert
|
||||
between Java objects and JMS messages.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.support.destination;
|
||||
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Session;
|
||||
|
||||
import org.springframework.jms.support.JmsAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class for {@link org.springframework.jms.core.JmsTemplate} and other
|
||||
* JMS-accessing gateway helpers, adding destination-related properties to
|
||||
* {@link JmsAccessor JmsAccessor's} common properties.
|
||||
*
|
||||
* <p>Not intended to be used directly.
|
||||
* See {@link org.springframework.jms.core.JmsTemplate}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.2.5
|
||||
* @see org.springframework.jms.support.JmsAccessor
|
||||
* @see org.springframework.jms.core.JmsTemplate
|
||||
*/
|
||||
public abstract class JmsDestinationAccessor extends JmsAccessor {
|
||||
|
||||
private DestinationResolver destinationResolver = new DynamicDestinationResolver();
|
||||
|
||||
private boolean pubSubDomain = false;
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@link DestinationResolver} that is to be used to resolve
|
||||
* {@link javax.jms.Destination} references for this accessor.
|
||||
* <p>The default resolver is a DynamicDestinationResolver. Specify a
|
||||
* JndiDestinationResolver for resolving destination names as JNDI locations.
|
||||
* @see org.springframework.jms.support.destination.DynamicDestinationResolver
|
||||
* @see org.springframework.jms.support.destination.JndiDestinationResolver
|
||||
*/
|
||||
public void setDestinationResolver(DestinationResolver destinationResolver) {
|
||||
Assert.notNull(destinationResolver, "'destinationResolver' must not be null");
|
||||
this.destinationResolver = destinationResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DestinationResolver for this accessor (never <code>null</code>).
|
||||
*/
|
||||
public DestinationResolver getDestinationResolver() {
|
||||
return this.destinationResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the destination accessor with knowledge of the JMS domain used.
|
||||
* Default is Point-to-Point (Queues).
|
||||
* <p>For JMS 1.0.2 based accessors, this tells the JMS provider which class hierarchy
|
||||
* to use in the implementation of its operations. For JMS 1.1 based accessors, this
|
||||
* setting does usually not affect operations. However, for both JMS versions, this
|
||||
* setting tells what type of destination to resolve if dynamic destinations are enabled.
|
||||
* @param pubSubDomain "true" for the Publish/Subscribe domain ({@link javax.jms.Topic Topics}),
|
||||
* "false" for the Point-to-Point domain ({@link javax.jms.Queue Queues})
|
||||
* @see #setDestinationResolver
|
||||
*/
|
||||
public void setPubSubDomain(boolean pubSubDomain) {
|
||||
this.pubSubDomain = pubSubDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the Publish/Subscribe domain ({@link javax.jms.Topic Topics}) is used.
|
||||
* Otherwise, the Point-to-Point domain ({@link javax.jms.Queue Queues}) is used.
|
||||
*/
|
||||
public boolean isPubSubDomain() {
|
||||
return this.pubSubDomain;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolve the given destination name into a JMS {@link Destination},
|
||||
* via this accessor's {@link DestinationResolver}.
|
||||
* @param session the current JMS {@link Session}
|
||||
* @param destinationName the name of the destination
|
||||
* @return the located {@link Destination}
|
||||
* @throws javax.jms.JMSException if resolution failed
|
||||
* @see #setDestinationResolver
|
||||
*/
|
||||
protected Destination resolveDestinationName(Session session, String destinationName) throws JMSException {
|
||||
return getDestinationResolver().resolveDestinationName(session, destinationName, isPubSubDomain());
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue