001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.jms.connection;
018:
019: import javax.jms.Connection;
020: import javax.jms.ConnectionFactory;
021: import javax.jms.JMSException;
022: import javax.jms.Session;
023: import javax.jms.TransactionRolledBackException;
024:
025: import org.springframework.transaction.CannotCreateTransactionException;
026: import org.springframework.transaction.InvalidIsolationLevelException;
027: import org.springframework.transaction.TransactionDefinition;
028: import org.springframework.transaction.TransactionSystemException;
029: import org.springframework.transaction.UnexpectedRollbackException;
030: import org.springframework.transaction.support.AbstractPlatformTransactionManager;
031: import org.springframework.transaction.support.DefaultTransactionStatus;
032: import org.springframework.transaction.support.SmartTransactionObject;
033: import org.springframework.transaction.support.TransactionSynchronizationManager;
034: import org.springframework.transaction.support.ResourceTransactionManager;
035: import org.springframework.beans.factory.InitializingBean;
036:
037: /**
038: * {@link org.springframework.transaction.PlatformTransactionManager} implementation
039: * for a single JMS {@link javax.jms.ConnectionFactory}. Binds a JMS
040: * Connection/Session pair from the specified ConnectionFactory to the thread,
041: * potentially allowing for one thread-bound Session per ConnectionFactory.
042: *
043: * <p>This local strategy is an alternative to executing JMS operations within
044: * JTA transactions. Its advantage is that it is able to work in any environment,
045: * for example a standalone application or a test suite, with any message broker
046: * as target. However, this strategy is <i>not</i> able to provide XA transactions,
047: * for example in order to share transactions between messaging and database access.
048: * A full JTA/XA setup is required for XA transactions, typically using Spring's
049: * {@link org.springframework.transaction.jta.JtaTransactionManager} as strategy.
050: *
051: * <p>Application code is required to retrieve the transactional JMS Session via
052: * {@link ConnectionFactoryUtils#getTransactionalSession} instead of a standard
053: * J2EE-style {@link ConnectionFactory#createConnection()} call with subsequent
054: * Session creation. Spring's {@link org.springframework.jms.core.JmsTemplate}
055: * will autodetect a thread-bound Session and automatically participate in it.
056: *
057: * <p>Alternatively, you can allow application code to work with the standard
058: * J2EE-style lookup pattern on a ConnectionFactory, for example for legacy code
059: * that is not aware of Spring at all. In that case, define a
060: * {@link TransactionAwareConnectionFactoryProxy} for your target ConnectionFactory,
061: * which will automatically participate in Spring-managed transactions.
062: *
063: * <p>This transaction strategy will typically be used in combination with
064: * {@link SingleConnectionFactory}, which uses a single JMS Connection for all
065: * JMS access in order to avoid the overhead of repeated Connection creation,
066: * typically in a standalone application. Each transaction will then share the
067: * same JMS Connection, while still using its own individual JMS Session.
068: *
069: * <p>Transaction synchronization is turned off by default, as this manager might
070: * be used alongside a datastore-based Spring transaction manager such as the
071: * JDBC {@link org.springframework.jdbc.datasource.DataSourceTransactionManager},
072: * which has stronger needs for synchronization.
073: *
074: * @author Juergen Hoeller
075: * @since 1.1
076: * @see ConnectionFactoryUtils#getTransactionalSession
077: * @see TransactionAwareConnectionFactoryProxy
078: * @see org.springframework.jms.core.JmsTemplate
079: */
080: public class JmsTransactionManager extends
081: AbstractPlatformTransactionManager implements
082: ResourceTransactionManager, InitializingBean {
083:
084: private ConnectionFactory connectionFactory;
085:
086: /**
087: * Create a new JmsTransactionManager for bean-style usage.
088: * <p>Note: The ConnectionFactory has to be set before using the instance.
089: * This constructor can be used to prepare a JmsTemplate via a BeanFactory,
090: * typically setting the ConnectionFactory via setConnectionFactory.
091: * <p>Turns off transaction synchronization by default, as this manager might
092: * be used alongside a datastore-based Spring transaction manager like
093: * DataSourceTransactionManager, which has stronger needs for synchronization.
094: * Only one manager is allowed to drive synchronization at any point of time.
095: * @see #setConnectionFactory
096: * @see #setTransactionSynchronization
097: */
098: public JmsTransactionManager() {
099: setTransactionSynchronization(SYNCHRONIZATION_NEVER);
100: }
101:
102: /**
103: * Create a new JmsTransactionManager, given a ConnectionFactory.
104: * @param connectionFactory the ConnectionFactory to obtain connections from
105: */
106: public JmsTransactionManager(ConnectionFactory connectionFactory) {
107: this ();
108: setConnectionFactory(connectionFactory);
109: afterPropertiesSet();
110: }
111:
112: /**
113: * Set the JMS ConnectionFactory that this instance should manage transactions for.
114: */
115: public void setConnectionFactory(ConnectionFactory cf) {
116: if (cf instanceof TransactionAwareConnectionFactoryProxy) {
117: // If we got a TransactionAwareConnectionFactoryProxy, we need to perform transactions
118: // for its underlying target ConnectionFactory, else JMS access code won't see
119: // properly exposed transactions (i.e. transactions for the target ConnectionFactory).
120: this .connectionFactory = ((TransactionAwareConnectionFactoryProxy) cf)
121: .getTargetConnectionFactory();
122: } else {
123: this .connectionFactory = cf;
124: }
125: }
126:
127: /**
128: * Return the JMS ConnectionFactory that this instance should manage transactions for.
129: */
130: public ConnectionFactory getConnectionFactory() {
131: return this .connectionFactory;
132: }
133:
134: /**
135: * Make sure the ConnectionFactory has been set.
136: */
137: public void afterPropertiesSet() {
138: if (getConnectionFactory() == null) {
139: throw new IllegalArgumentException(
140: "Property 'connectionFactory' is required");
141: }
142: }
143:
144: public Object getResourceFactory() {
145: return getConnectionFactory();
146: }
147:
148: protected Object doGetTransaction() {
149: JmsTransactionObject txObject = new JmsTransactionObject();
150: txObject
151: .setResourceHolder((JmsResourceHolder) TransactionSynchronizationManager
152: .getResource(getConnectionFactory()));
153: return txObject;
154: }
155:
156: protected boolean isExistingTransaction(Object transaction) {
157: JmsTransactionObject txObject = (JmsTransactionObject) transaction;
158: return (txObject.getResourceHolder() != null);
159: }
160:
161: protected void doBegin(Object transaction,
162: TransactionDefinition definition) {
163: if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
164: throw new InvalidIsolationLevelException(
165: "JMS does not support an isolation level concept");
166: }
167: JmsTransactionObject txObject = (JmsTransactionObject) transaction;
168: Connection con = null;
169: Session session = null;
170: try {
171: con = createConnection();
172: session = createSession(con);
173: con.start();
174: if (logger.isDebugEnabled()) {
175: logger.debug("Created JMS transaction on Session ["
176: + session + "] from Connection [" + con + "]");
177: }
178: txObject.setResourceHolder(new JmsResourceHolder(
179: getConnectionFactory(), con, session));
180: txObject.getResourceHolder()
181: .setSynchronizedWithTransaction(true);
182: int timeout = determineTimeout(definition);
183: if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
184: txObject.getResourceHolder().setTimeoutInSeconds(
185: timeout);
186: }
187: TransactionSynchronizationManager.bindResource(
188: getConnectionFactory(), txObject
189: .getResourceHolder());
190: } catch (JMSException ex) {
191: if (session != null) {
192: try {
193: session.close();
194: } catch (Throwable ex2) {
195: // ignore
196: }
197: }
198: if (con != null) {
199: try {
200: con.close();
201: } catch (Throwable ex2) {
202: // ignore
203: }
204: }
205: throw new CannotCreateTransactionException(
206: "Could not create JMS transaction", ex);
207: }
208: }
209:
210: protected Object doSuspend(Object transaction) {
211: JmsTransactionObject txObject = (JmsTransactionObject) transaction;
212: txObject.setResourceHolder(null);
213: return TransactionSynchronizationManager
214: .unbindResource(getConnectionFactory());
215: }
216:
217: protected void doResume(Object transaction,
218: Object suspendedResources) {
219: JmsResourceHolder conHolder = (JmsResourceHolder) suspendedResources;
220: TransactionSynchronizationManager.bindResource(
221: getConnectionFactory(), conHolder);
222: }
223:
224: protected void doCommit(DefaultTransactionStatus status) {
225: JmsTransactionObject txObject = (JmsTransactionObject) status
226: .getTransaction();
227: Session session = txObject.getResourceHolder().getSession();
228: try {
229: if (status.isDebug()) {
230: logger.debug("Committing JMS transaction on Session ["
231: + session + "]");
232: }
233: session.commit();
234: } catch (TransactionRolledBackException ex) {
235: throw new UnexpectedRollbackException(
236: "JMS transaction rolled back", ex);
237: } catch (JMSException ex) {
238: throw new TransactionSystemException(
239: "Could not commit JMS transaction", ex);
240: }
241: }
242:
243: protected void doRollback(DefaultTransactionStatus status) {
244: JmsTransactionObject txObject = (JmsTransactionObject) status
245: .getTransaction();
246: Session session = txObject.getResourceHolder().getSession();
247: try {
248: if (status.isDebug()) {
249: logger
250: .debug("Rolling back JMS transaction on Session ["
251: + session + "]");
252: }
253: session.rollback();
254: } catch (JMSException ex) {
255: throw new TransactionSystemException(
256: "Could not roll back JMS transaction", ex);
257: }
258: }
259:
260: protected void doSetRollbackOnly(DefaultTransactionStatus status) {
261: JmsTransactionObject txObject = (JmsTransactionObject) status
262: .getTransaction();
263: txObject.getResourceHolder().setRollbackOnly();
264: }
265:
266: protected void doCleanupAfterCompletion(Object transaction) {
267: JmsTransactionObject txObject = (JmsTransactionObject) transaction;
268: TransactionSynchronizationManager
269: .unbindResource(getConnectionFactory());
270: txObject.getResourceHolder().closeAll();
271: txObject.getResourceHolder().clear();
272: }
273:
274: //-------------------------------------------------------------------------
275: // JMS 1.1 factory methods, potentially overridden for JMS 1.0.2
276: //-------------------------------------------------------------------------
277:
278: /**
279: * Create a JMS Connection via this template's ConnectionFactory.
280: * <p>This implementation uses JMS 1.1 API.
281: * @return the new JMS Connection
282: * @throws javax.jms.JMSException if thrown by JMS API methods
283: */
284: protected Connection createConnection() throws JMSException {
285: return getConnectionFactory().createConnection();
286: }
287:
288: /**
289: * Create a JMS Session for the given Connection.
290: * <p>This implementation uses JMS 1.1 API.
291: * @param con the JMS Connection to create a Session for
292: * @return the new JMS Session
293: * @throws javax.jms.JMSException if thrown by JMS API methods
294: */
295: protected Session createSession(Connection con) throws JMSException {
296: return con.createSession(true, Session.AUTO_ACKNOWLEDGE);
297: }
298:
299: /**
300: * JMS transaction object, representing a JmsResourceHolder.
301: * Used as transaction object by JmsTransactionManager.
302: * @see JmsResourceHolder
303: */
304: private static class JmsTransactionObject implements
305: SmartTransactionObject {
306:
307: private JmsResourceHolder resourceHolder;
308:
309: public void setResourceHolder(JmsResourceHolder resourceHolder) {
310: this .resourceHolder = resourceHolder;
311: }
312:
313: public JmsResourceHolder getResourceHolder() {
314: return resourceHolder;
315: }
316:
317: public boolean isRollbackOnly() {
318: return getResourceHolder().isRollbackOnly();
319: }
320: }
321:
322: }
|