001/*
002 * Copyright 2002-2017 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 *      https://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
017package org.springframework.jca.cci.connection;
018
019import javax.resource.NotSupportedException;
020import javax.resource.ResourceException;
021import javax.resource.cci.Connection;
022import javax.resource.cci.ConnectionFactory;
023import javax.resource.spi.LocalTransactionException;
024
025import org.springframework.beans.factory.InitializingBean;
026import org.springframework.transaction.CannotCreateTransactionException;
027import org.springframework.transaction.TransactionDefinition;
028import org.springframework.transaction.TransactionException;
029import org.springframework.transaction.TransactionSystemException;
030import org.springframework.transaction.support.AbstractPlatformTransactionManager;
031import org.springframework.transaction.support.DefaultTransactionStatus;
032import org.springframework.transaction.support.ResourceTransactionManager;
033import org.springframework.transaction.support.TransactionSynchronizationManager;
034
035/**
036 * {@link org.springframework.transaction.PlatformTransactionManager} implementation
037 * that manages local transactions for a single CCI ConnectionFactory.
038 * Binds a CCI Connection from the specified ConnectionFactory to the thread,
039 * potentially allowing for one thread-bound Connection per ConnectionFactory.
040 *
041 * <p>Application code is required to retrieve the CCI Connection via
042 * {@link ConnectionFactoryUtils#getConnection(ConnectionFactory)} instead of a standard
043 * Java EE-style {@link ConnectionFactory#getConnection()} call. Spring classes such as
044 * {@link org.springframework.jca.cci.core.CciTemplate} use this strategy implicitly.
045 * If not used in combination with this transaction manager, the
046 * {@link ConnectionFactoryUtils} lookup strategy behaves exactly like the native
047 * DataSource lookup; it can thus be used in a portable fashion.
048 *
049 * <p>Alternatively, you can allow application code to work with the standard
050 * Java EE lookup pattern {@link ConnectionFactory#getConnection()}, for example
051 * for legacy code that is not aware of Spring at all. In that case, define a
052 * {@link TransactionAwareConnectionFactoryProxy} for your target ConnectionFactory,
053 * which will automatically participate in Spring-managed transactions.
054 *
055 * @author Thierry Templier
056 * @author Juergen Hoeller
057 * @since 1.2
058 * @see ConnectionFactoryUtils#getConnection(javax.resource.cci.ConnectionFactory)
059 * @see ConnectionFactoryUtils#releaseConnection
060 * @see TransactionAwareConnectionFactoryProxy
061 * @see org.springframework.jca.cci.core.CciTemplate
062 */
063@SuppressWarnings("serial")
064public class CciLocalTransactionManager extends AbstractPlatformTransactionManager
065                implements ResourceTransactionManager, InitializingBean {
066
067        private ConnectionFactory connectionFactory;
068
069
070        /**
071         * Create a new CciLocalTransactionManager instance.
072         * A ConnectionFactory has to be set to be able to use it.
073         * @see #setConnectionFactory
074         */
075        public CciLocalTransactionManager() {
076        }
077
078        /**
079         * Create a new CciLocalTransactionManager instance.
080         * @param connectionFactory CCI ConnectionFactory to manage local transactions for
081         */
082        public CciLocalTransactionManager(ConnectionFactory connectionFactory) {
083                setConnectionFactory(connectionFactory);
084                afterPropertiesSet();
085        }
086
087
088        /**
089         * Set the CCI ConnectionFactory that this instance should manage local
090         * transactions for.
091         */
092        public void setConnectionFactory(ConnectionFactory cf) {
093                if (cf instanceof TransactionAwareConnectionFactoryProxy) {
094                        // If we got a TransactionAwareConnectionFactoryProxy, we need to perform transactions
095                        // for its underlying target ConnectionFactory, else JMS access code won't see
096                        // properly exposed transactions (i.e. transactions for the target ConnectionFactory).
097                        this.connectionFactory = ((TransactionAwareConnectionFactoryProxy) cf).getTargetConnectionFactory();
098                }
099                else {
100                        this.connectionFactory = cf;
101                }
102        }
103
104        /**
105         * Return the CCI ConnectionFactory that this instance manages local
106         * transactions for.
107         */
108        public ConnectionFactory getConnectionFactory() {
109                return this.connectionFactory;
110        }
111
112        @Override
113        public void afterPropertiesSet() {
114                if (getConnectionFactory() == null) {
115                        throw new IllegalArgumentException("Property 'connectionFactory' is required");
116                }
117        }
118
119
120        @Override
121        public Object getResourceFactory() {
122                return getConnectionFactory();
123        }
124
125        @Override
126        protected Object doGetTransaction() {
127                CciLocalTransactionObject txObject = new CciLocalTransactionObject();
128                ConnectionHolder conHolder =
129                                (ConnectionHolder) TransactionSynchronizationManager.getResource(getConnectionFactory());
130                txObject.setConnectionHolder(conHolder);
131                return txObject;
132        }
133
134        @Override
135        protected boolean isExistingTransaction(Object transaction) {
136                CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction;
137                // Consider a pre-bound connection as transaction.
138                return txObject.hasConnectionHolder();
139        }
140
141        @Override
142        protected void doBegin(Object transaction, TransactionDefinition definition) {
143                CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction;
144                Connection con = null;
145
146                try {
147                        con = getConnectionFactory().getConnection();
148                        if (logger.isDebugEnabled()) {
149                                logger.debug("Acquired Connection [" + con + "] for local CCI transaction");
150                        }
151
152                        txObject.setConnectionHolder(new ConnectionHolder(con));
153                        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
154
155                        con.getLocalTransaction().begin();
156                        int timeout = determineTimeout(definition);
157                        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
158                                txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
159                        }
160                        TransactionSynchronizationManager.bindResource(getConnectionFactory(), txObject.getConnectionHolder());
161                }
162                catch (NotSupportedException ex) {
163                        ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory());
164                        throw new CannotCreateTransactionException("CCI Connection does not support local transactions", ex);
165                }
166                catch (LocalTransactionException ex) {
167                        ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory());
168                        throw new CannotCreateTransactionException("Could not begin local CCI transaction", ex);
169                }
170                catch (Throwable ex) {
171                        ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory());
172                        throw new TransactionSystemException("Unexpected failure on begin of CCI local transaction", ex);
173                }
174        }
175
176        @Override
177        protected Object doSuspend(Object transaction) {
178                CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction;
179                txObject.setConnectionHolder(null);
180                return TransactionSynchronizationManager.unbindResource(getConnectionFactory());
181        }
182
183        @Override
184        protected void doResume(Object transaction, Object suspendedResources) {
185                ConnectionHolder conHolder = (ConnectionHolder) suspendedResources;
186                TransactionSynchronizationManager.bindResource(getConnectionFactory(), conHolder);
187        }
188
189        protected boolean isRollbackOnly(Object transaction) throws TransactionException {
190                CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction;
191                return txObject.getConnectionHolder().isRollbackOnly();
192        }
193
194        @Override
195        protected void doCommit(DefaultTransactionStatus status) {
196                CciLocalTransactionObject txObject = (CciLocalTransactionObject) status.getTransaction();
197                Connection con = txObject.getConnectionHolder().getConnection();
198                if (status.isDebug()) {
199                        logger.debug("Committing CCI local transaction on Connection [" + con + "]");
200                }
201                try {
202                        con.getLocalTransaction().commit();
203                }
204                catch (LocalTransactionException ex) {
205                        throw new TransactionSystemException("Could not commit CCI local transaction", ex);
206                }
207                catch (ResourceException ex) {
208                        throw new TransactionSystemException("Unexpected failure on commit of CCI local transaction", ex);
209                }
210        }
211
212        @Override
213        protected void doRollback(DefaultTransactionStatus status) {
214                CciLocalTransactionObject txObject = (CciLocalTransactionObject) status.getTransaction();
215                Connection con = txObject.getConnectionHolder().getConnection();
216                if (status.isDebug()) {
217                        logger.debug("Rolling back CCI local transaction on Connection [" + con + "]");
218                }
219                try {
220                        con.getLocalTransaction().rollback();
221                }
222                catch (LocalTransactionException ex) {
223                        throw new TransactionSystemException("Could not roll back CCI local transaction", ex);
224                }
225                catch (ResourceException ex) {
226                        throw new TransactionSystemException("Unexpected failure on rollback of CCI local transaction", ex);
227                }
228        }
229
230        @Override
231        protected void doSetRollbackOnly(DefaultTransactionStatus status) {
232                CciLocalTransactionObject txObject = (CciLocalTransactionObject) status.getTransaction();
233                if (status.isDebug()) {
234                        logger.debug("Setting CCI local transaction [" + txObject.getConnectionHolder().getConnection() +
235                                        "] rollback-only");
236                }
237                txObject.getConnectionHolder().setRollbackOnly();
238        }
239
240        @Override
241        protected void doCleanupAfterCompletion(Object transaction) {
242                CciLocalTransactionObject txObject = (CciLocalTransactionObject) transaction;
243
244                // Remove the connection holder from the thread.
245                TransactionSynchronizationManager.unbindResource(getConnectionFactory());
246                txObject.getConnectionHolder().clear();
247
248                Connection con = txObject.getConnectionHolder().getConnection();
249                if (logger.isDebugEnabled()) {
250                        logger.debug("Releasing CCI Connection [" + con + "] after transaction");
251                }
252                ConnectionFactoryUtils.releaseConnection(con, getConnectionFactory());
253        }
254
255
256        /**
257         * CCI local transaction object, representing a ConnectionHolder.
258         * Used as transaction object by CciLocalTransactionManager.
259         * @see ConnectionHolder
260         */
261        private static class CciLocalTransactionObject {
262
263                private ConnectionHolder connectionHolder;
264
265                public void setConnectionHolder(ConnectionHolder connectionHolder) {
266                        this.connectionHolder = connectionHolder;
267                }
268
269                public ConnectionHolder getConnectionHolder() {
270                        return this.connectionHolder;
271                }
272
273                public boolean hasConnectionHolder() {
274                        return (this.connectionHolder != null);
275                }
276        }
277
278}