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