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