001/*
002 * Copyright 2002-2020 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.transaction.jta;
018
019import java.util.List;
020import javax.naming.NamingException;
021
022import com.ibm.websphere.uow.UOWSynchronizationRegistry;
023import com.ibm.wsspi.uow.UOWAction;
024import com.ibm.wsspi.uow.UOWActionException;
025import com.ibm.wsspi.uow.UOWException;
026import com.ibm.wsspi.uow.UOWManager;
027import com.ibm.wsspi.uow.UOWManagerFactory;
028
029import org.springframework.transaction.IllegalTransactionStateException;
030import org.springframework.transaction.InvalidTimeoutException;
031import org.springframework.transaction.NestedTransactionNotSupportedException;
032import org.springframework.transaction.TransactionDefinition;
033import org.springframework.transaction.TransactionException;
034import org.springframework.transaction.TransactionSystemException;
035import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager;
036import org.springframework.transaction.support.DefaultTransactionDefinition;
037import org.springframework.transaction.support.DefaultTransactionStatus;
038import org.springframework.transaction.support.SmartTransactionObject;
039import org.springframework.transaction.support.TransactionCallback;
040import org.springframework.transaction.support.TransactionSynchronization;
041import org.springframework.transaction.support.TransactionSynchronizationManager;
042import org.springframework.transaction.support.TransactionSynchronizationUtils;
043import org.springframework.util.ReflectionUtils;
044
045/**
046 * WebSphere-specific PlatformTransactionManager implementation that delegates
047 * to a {@link com.ibm.wsspi.uow.UOWManager} instance, obtained from WebSphere's
048 * JNDI environment. This allows Spring to leverage the full power of the WebSphere
049 * transaction coordinator, including transaction suspension, in a manner that is
050 * perfectly compliant with officially supported WebSphere API.
051 *
052 * <p>The {@link CallbackPreferringPlatformTransactionManager} interface
053 * implemented by this class indicates that callers should preferably pass in
054 * a {@link TransactionCallback} through the {@link #execute} method, which
055 * will be handled through the callback-based WebSphere UOWManager API instead
056 * of through standard JTA API (UserTransaction / TransactionManager). This avoids
057 * the use of the non-public {@code javax.transaction.TransactionManager}
058 * API on WebSphere, staying within supported WebSphere API boundaries.
059 *
060 * <p>This transaction manager implementation derives from Spring's standard
061 * {@link JtaTransactionManager}, inheriting the capability to support programmatic
062 * transaction demarcation via {@code getTransaction} / {@code commit} /
063 * {@code rollback} calls through a JTA UserTransaction handle, for callers
064 * that do not use the TransactionCallback-based {@link #execute} method. However,
065 * transaction suspension is <i>not</i> supported in this {@code getTransaction}
066 * style (unless you explicitly specify a {@link #setTransactionManager} reference,
067 * despite the official WebSphere recommendations). Use the {@link #execute} style
068 * for any code that might require transaction suspension.
069 *
070 * <p>This transaction manager is compatible with WebSphere 6.1.0.9 and above.
071 * The default JNDI location for the UOWManager is "java:comp/websphere/UOWManager".
072 * If the location happens to differ according to your WebSphere documentation,
073 * simply specify the actual location through this transaction manager's
074 * "uowManagerName" bean property.
075 *
076 * <p><b>NOTE: This JtaTransactionManager is intended to refine specific transaction
077 * demarcation behavior on Spring's side. It will happily co-exist with independently
078 * configured WebSphere transaction strategies in your persistence provider, with no
079 * need to specifically connect those setups in any way.</b>
080 *
081 * @author Juergen Hoeller
082 * @since 2.5
083 * @see #setUowManager
084 * @see #setUowManagerName
085 * @see com.ibm.wsspi.uow.UOWManager
086 */
087@SuppressWarnings("serial")
088public class WebSphereUowTransactionManager extends JtaTransactionManager
089                implements CallbackPreferringPlatformTransactionManager {
090
091        /**
092         * Default JNDI location for the WebSphere UOWManager.
093         * @see #setUowManagerName
094         */
095        public static final String DEFAULT_UOW_MANAGER_NAME = "java:comp/websphere/UOWManager";
096
097
098        private UOWManager uowManager;
099
100        private String uowManagerName;
101
102
103        /**
104         * Create a new WebSphereUowTransactionManager.
105         */
106        public WebSphereUowTransactionManager() {
107                setAutodetectTransactionManager(false);
108        }
109
110        /**
111         * Create a new WebSphereUowTransactionManager for the given UOWManager.
112         * @param uowManager the WebSphere UOWManager to use as direct reference
113         */
114        public WebSphereUowTransactionManager(UOWManager uowManager) {
115                this();
116                this.uowManager = uowManager;
117        }
118
119
120        /**
121         * Set the WebSphere UOWManager to use as direct reference.
122         * <p>Typically just used for test setups; in a Java EE environment,
123         * the UOWManager will always be fetched from JNDI.
124         * @see #setUserTransactionName
125         */
126        public void setUowManager(UOWManager uowManager) {
127                this.uowManager = uowManager;
128        }
129
130        /**
131         * Set the JNDI name of the WebSphere UOWManager.
132         * The default "java:comp/websphere/UOWManager" is used if not set.
133         * @see #DEFAULT_USER_TRANSACTION_NAME
134         * @see #setUowManager
135         */
136        public void setUowManagerName(String uowManagerName) {
137                this.uowManagerName = uowManagerName;
138        }
139
140
141        @Override
142        public void afterPropertiesSet() throws TransactionSystemException {
143                initUserTransactionAndTransactionManager();
144
145                // Fetch UOWManager handle from JNDI, if necessary.
146                if (this.uowManager == null) {
147                        if (this.uowManagerName != null) {
148                                this.uowManager = lookupUowManager(this.uowManagerName);
149                        }
150                        else {
151                                this.uowManager = lookupDefaultUowManager();
152                        }
153                }
154        }
155
156        /**
157         * Look up the WebSphere UOWManager in JNDI via the configured name.
158         * @param uowManagerName the JNDI name of the UOWManager
159         * @return the UOWManager object
160         * @throws TransactionSystemException if the JNDI lookup failed
161         * @see #setJndiTemplate
162         * @see #setUowManagerName
163         */
164        protected UOWManager lookupUowManager(String uowManagerName) throws TransactionSystemException {
165                try {
166                        if (logger.isDebugEnabled()) {
167                                logger.debug("Retrieving WebSphere UOWManager from JNDI location [" + uowManagerName + "]");
168                        }
169                        return getJndiTemplate().lookup(uowManagerName, UOWManager.class);
170                }
171                catch (NamingException ex) {
172                        throw new TransactionSystemException(
173                                        "WebSphere UOWManager is not available at JNDI location [" + uowManagerName + "]", ex);
174                }
175        }
176
177        /**
178         * Obtain the WebSphere UOWManager from the default JNDI location
179         * "java:comp/websphere/UOWManager".
180         * @return the UOWManager object
181         * @throws TransactionSystemException if the JNDI lookup failed
182         * @see #setJndiTemplate
183         */
184        protected UOWManager lookupDefaultUowManager() throws TransactionSystemException {
185                try {
186                        logger.debug("Retrieving WebSphere UOWManager from default JNDI location [" + DEFAULT_UOW_MANAGER_NAME + "]");
187                        return getJndiTemplate().lookup(DEFAULT_UOW_MANAGER_NAME, UOWManager.class);
188                }
189                catch (NamingException ex) {
190                        logger.debug("WebSphere UOWManager is not available at default JNDI location [" +
191                                        DEFAULT_UOW_MANAGER_NAME + "] - falling back to UOWManagerFactory lookup");
192                        return UOWManagerFactory.getUOWManager();
193                }
194        }
195
196        /**
197         * Registers the synchronizations as interposed JTA Synchronization on the UOWManager.
198         */
199        @Override
200        protected void doRegisterAfterCompletionWithJtaTransaction(
201                        JtaTransactionObject txObject, List<TransactionSynchronization> synchronizations) {
202
203                this.uowManager.registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations));
204        }
205
206        /**
207         * Returns {@code true} since WebSphere ResourceAdapters (as exposed in JNDI)
208         * implicitly perform transaction enlistment if the MessageEndpointFactory's
209         * {@code isDeliveryTransacted} method returns {@code true}.
210         * In that case we'll simply skip the {@link #createTransaction} call.
211         * @see javax.resource.spi.endpoint.MessageEndpointFactory#isDeliveryTransacted
212         * @see org.springframework.jca.endpoint.AbstractMessageEndpointFactory
213         * @see TransactionFactory#createTransaction
214         */
215        @Override
216        public boolean supportsResourceAdapterManagedTransactions() {
217                return true;
218        }
219
220
221        @Override
222        public <T> T execute(TransactionDefinition definition, TransactionCallback<T> callback) throws TransactionException {
223                if (definition == null) {
224                        // Use defaults if no transaction definition given.
225                        definition = new DefaultTransactionDefinition();
226                }
227
228                if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
229                        throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
230                }
231                int pb = definition.getPropagationBehavior();
232                boolean existingTx = (this.uowManager.getUOWStatus() != UOWSynchronizationRegistry.UOW_STATUS_NONE &&
233                                this.uowManager.getUOWType() != UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION);
234
235                int uowType = UOWSynchronizationRegistry.UOW_TYPE_GLOBAL_TRANSACTION;
236                boolean joinTx = false;
237                boolean newSynch = false;
238
239                if (existingTx) {
240                        if (pb == TransactionDefinition.PROPAGATION_NEVER) {
241                                throw new IllegalTransactionStateException(
242                                                "Transaction propagation 'never' but existing transaction found");
243                        }
244                        if (pb == TransactionDefinition.PROPAGATION_NESTED) {
245                                throw new NestedTransactionNotSupportedException(
246                                                "Transaction propagation 'nested' not supported for WebSphere UOW transactions");
247                        }
248                        if (pb == TransactionDefinition.PROPAGATION_SUPPORTS ||
249                                        pb == TransactionDefinition.PROPAGATION_REQUIRED ||
250                                        pb == TransactionDefinition.PROPAGATION_MANDATORY) {
251                                joinTx = true;
252                                newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
253                        }
254                        else if (pb == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
255                                uowType = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION;
256                                newSynch = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
257                        }
258                        else {
259                                newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
260                        }
261                }
262                else {
263                        if (pb == TransactionDefinition.PROPAGATION_MANDATORY) {
264                                throw new IllegalTransactionStateException(
265                                                "Transaction propagation 'mandatory' but no existing transaction found");
266                        }
267                        if (pb == TransactionDefinition.PROPAGATION_SUPPORTS ||
268                                        pb == TransactionDefinition.PROPAGATION_NOT_SUPPORTED ||
269                                        pb == TransactionDefinition.PROPAGATION_NEVER) {
270                                uowType = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION;
271                                newSynch = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
272                        }
273                        else {
274                                newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
275                        }
276                }
277
278                boolean debug = logger.isDebugEnabled();
279                if (debug) {
280                        logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
281                }
282                SuspendedResourcesHolder suspendedResources = (!joinTx ? suspend(null) : null);
283                UOWActionAdapter<T> action = null;
284                try {
285                        boolean actualTransaction = (uowType == UOWManager.UOW_TYPE_GLOBAL_TRANSACTION);
286                        if (actualTransaction && definition.getTimeout() > TransactionDefinition.TIMEOUT_DEFAULT) {
287                                this.uowManager.setUOWTimeout(uowType, definition.getTimeout());
288                        }
289                        if (debug) {
290                                logger.debug("Invoking WebSphere UOW action: type=" + uowType + ", join=" + joinTx);
291                        }
                        action = new UOWActionAdapter<T>(definition, callback, actualTransaction, !joinTx, newSynch, debug);
293                        this.uowManager.runUnderUOW(uowType, joinTx, action);
294                        if (debug) {
295                                logger.debug("Returned from WebSphere UOW action: type=" + uowType + ", join=" + joinTx);
296                        }
297                        return action.getResult();
298                }
299                catch (UOWException ex) {
300                        TransactionSystemException tse =
301                                        new TransactionSystemException("UOWManager transaction processing failed", ex);
302                        Throwable appEx = action.getException();
303                        if (appEx != null) {
304                                logger.error("Application exception overridden by rollback exception", appEx);
305                                tse.initApplicationException(appEx);
306                        }
307                        throw tse;
308                }
309                catch (UOWActionException ex) {
310                        TransactionSystemException tse =
311                                        new TransactionSystemException("UOWManager transaction processing failed", ex);
312                        Throwable appEx = action.getException();
313                        if (appEx != null) {
314                                logger.error("Application exception overridden by rollback exception", appEx);
315                                tse.initApplicationException(appEx);
316                        }
317                        throw tse;
318                }
319                finally {
320                        if (suspendedResources != null) {
321                                resume(null, suspendedResources);
322                        }
323                }
324        }
325
326
327        /**
328         * Adapter that executes the given Spring transaction within the WebSphere UOWAction shape.
329         */
330        private class UOWActionAdapter<T> implements UOWAction, SmartTransactionObject {
331
332                private final TransactionDefinition definition;
333
334                private final TransactionCallback<T> callback;
335
336                private final boolean actualTransaction;
337
338                private final boolean newTransaction;
339
340                private final boolean newSynchronization;
341
342                private boolean debug;
343
344                private T result;
345
346                private Throwable exception;
347
348                public UOWActionAdapter(TransactionDefinition definition, TransactionCallback<T> callback,
349                                boolean actualTransaction, boolean newTransaction, boolean newSynchronization, boolean debug) {
350
351                        this.definition = definition;
352                        this.callback = callback;
353                        this.actualTransaction = actualTransaction;
354                        this.newTransaction = newTransaction;
355                        this.newSynchronization = newSynchronization;
356                        this.debug = debug;
357                }
358
359                @Override
360                public void run() {
361                        DefaultTransactionStatus status = prepareTransactionStatus(
362                                        this.definition, (this.actualTransaction ? this : null),
363                                        this.newTransaction, this.newSynchronization, this.debug, null);
364                        try {
365                                this.result = this.callback.doInTransaction(status);
366                                triggerBeforeCommit(status);
367                        }
368                        catch (Throwable ex) {
369                                this.exception = ex;
370                                if (status.isDebug()) {
371                                        logger.debug("Rolling back on application exception from transaction callback", ex);
372                                }
373                                uowManager.setRollbackOnly();
374                        }
375                        finally {
376                                if (status.isLocalRollbackOnly()) {
377                                        if (status.isDebug()) {
378                                                logger.debug("Transaction callback has explicitly requested rollback");
379                                        }
380                                        uowManager.setRollbackOnly();
381                                }
382                                triggerBeforeCompletion(status);
383                                if (status.isNewSynchronization()) {
384                                        List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations();
385                                        TransactionSynchronizationManager.clear();
386                                        if (!synchronizations.isEmpty()) {
387                                                uowManager.registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations));
388                                        }
389                                }
390                        }
391                }
392
393                public T getResult() {
394                        if (this.exception != null) {
395                                ReflectionUtils.rethrowRuntimeException(this.exception);
396                        }
397                        return this.result;
398                }
399
400                public Throwable getException() {
401                        return this.exception;
402                }
403
404                @Override
405                public boolean isRollbackOnly() {
406                        return uowManager.getRollbackOnly();
407                }
408
409                @Override
410                public void flush() {
411                        TransactionSynchronizationUtils.triggerFlush();
412                }
413        }
414
415}