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